[longomatch/gameunits] Add base class for timelines



commit 18b283ac53a4e91b119ed8d50435a6610a966cdf
Author: Andoni Morales Alastruey <ylatuya gmail com>
Date:   Wed Nov 23 22:53:02 2011 +0100

    Add base class for timelines

 LongoMatch.Core/Interfaces/ITimelineNode.cs      |   38 +++
 LongoMatch.Core/LongoMatch.Core.mdp              |    2 +
 LongoMatch.Core/Makefile.am                      |    1 +
 LongoMatch.Core/Store/GameUnit.cs                |    2 +-
 LongoMatch.Core/Store/Play.cs                    |    3 +-
 LongoMatch.Core/Store/TimelineNode.cs            |  119 ++++++++
 LongoMatch.GUI/Gui/Base/TimeScaleBase.cs         |  355 ++++++++++++++++++++++
 LongoMatch.GUI/Gui/Component/TimeScale.cs        |  329 ++-------------------
 LongoMatch.GUI/LongoMatch.GUI.mdp                |    3 +
 LongoMatch.GUI/Makefile.am                       |    1 +
 LongoMatch.GUI/gtk-gui/objects.xml               |  177 ++++-------
 LongoMatch.Services/Services/GameUnitsManager.cs |    4 +-
 LongoMatch/LongoMatch.mdp                        |   36 ---
 13 files changed, 615 insertions(+), 455 deletions(-)
---
diff --git a/LongoMatch.Core/Interfaces/ITimelineNode.cs b/LongoMatch.Core/Interfaces/ITimelineNode.cs
new file mode 100644
index 0000000..d0ff33d
--- /dev/null
+++ b/LongoMatch.Core/Interfaces/ITimelineNode.cs
@@ -0,0 +1,38 @@
+// 
+//  Copyright (C) 2011 Andoni Morales Alastruey
+// 
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation; either version 2 of the License, or
+//  (at your option) any later version.
+// 
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+//  GNU General Public License for more details.
+//  
+//  You should have received a copy of the GNU General Public License
+//  along with this program; if not, write to the Free Software
+//  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+// 
+using System;
+using LongoMatch.Store;
+
+namespace LongoMatch.Interfaces
+{
+	public interface ITimelineNode
+	{	
+		string Name {get;set;}
+		Time Start {get;set;}
+		Time Stop {get;set;}
+		uint StartFrame {get; set;}
+		uint StopFrame {get; set;}
+		bool Selected {get; set;}
+		uint CentralFrame {get;}
+		uint TotalFrames {get;}
+		uint KeyFrame {get;}
+		bool HasDrawings {get;}
+		bool HasFrame(int frame);
+	}
+}
+
diff --git a/LongoMatch.Core/LongoMatch.Core.mdp b/LongoMatch.Core/LongoMatch.Core.mdp
index 11ce898..6536d9c 100644
--- a/LongoMatch.Core/LongoMatch.Core.mdp
+++ b/LongoMatch.Core/LongoMatch.Core.mdp
@@ -72,6 +72,8 @@
     <File subtype="Code" buildaction="Compile" name="Interfaces/IPlaylistWidget.cs" />
     <File subtype="Code" buildaction="Compile" name="Store/GameUnit.cs" />
     <File subtype="Code" buildaction="Compile" name="Store/GameUnitsList.cs" />
+    <File subtype="Code" buildaction="Compile" name="Store/TimelineNode.cs" />
+    <File subtype="Code" buildaction="Compile" name="Interfaces/ITimelineNode.cs" />
   </Contents>
   <References>
     <ProjectReference type="Gac" localcopy="True" refto="System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
diff --git a/LongoMatch.Core/Makefile.am b/LongoMatch.Core/Makefile.am
index 69c2389..efd2da3 100644
--- a/LongoMatch.Core/Makefile.am
+++ b/LongoMatch.Core/Makefile.am
@@ -33,6 +33,7 @@ SOURCES = \
 	Store/Drawing.cs \
 	Store/GameUnit.cs \
 	Store/GameUnitsList.cs \
+	Store/TimelineNode.cs \
 	Store/HotKey.cs \
 	Store/MediaFile.cs \
 	Store/PixbufTimeNode.cs \
diff --git a/LongoMatch.Core/Store/GameUnit.cs b/LongoMatch.Core/Store/GameUnit.cs
index eaa9885..f9ce9b9 100644
--- a/LongoMatch.Core/Store/GameUnit.cs
+++ b/LongoMatch.Core/Store/GameUnit.cs
@@ -21,7 +21,7 @@ using System.Collections.Generic;
 namespace LongoMatch.Store
 {
 	[Serializable]
-	public class GameUnit: List<TimeNode>
+	public class GameUnit: List<TimelineNode>
 	{
 		
 		public GameUnit (string name)
diff --git a/LongoMatch.Core/Store/Play.cs b/LongoMatch.Core/Store/Play.cs
index baf9713..967aeef 100644
--- a/LongoMatch.Core/Store/Play.cs
+++ b/LongoMatch.Core/Store/Play.cs
@@ -24,6 +24,7 @@ using System.Linq;
 using Mono.Unix;
 using Gdk;
 using LongoMatch.Common;
+using LongoMatch.Interfaces;
 
 namespace LongoMatch.Store
 {
@@ -33,7 +34,7 @@ namespace LongoMatch.Store
 	/// </summary>
 
 	[Serializable]
-	public class  Play : PixbufTimeNode
+	public class  Play : PixbufTimeNode, ITimelineNode
 	{
 
 		#region Constructors
diff --git a/LongoMatch.Core/Store/TimelineNode.cs b/LongoMatch.Core/Store/TimelineNode.cs
new file mode 100644
index 0000000..928d613
--- /dev/null
+++ b/LongoMatch.Core/Store/TimelineNode.cs
@@ -0,0 +1,119 @@
+// 
+//  Copyright (C) 2011 Andoni Morales Alastruey
+// 
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation; either version 2 of the License, or
+//  (at your option) any later version.
+// 
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+//  GNU General Public License for more details.
+//  
+//  You should have received a copy of the GNU General Public License
+//  along with this program; if not, write to the Free Software
+//  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+// 
+using System;
+using LongoMatch.Interfaces;
+
+namespace LongoMatch.Store
+{
+    /* FIXME: Code duplicated from Play, unfortunately we can't
+      * modify the class hierachy */
+	public class TimelineNode: TimeNode, ITimelineNode
+	{
+		public TimelineNode ()
+		{
+		}
+		
+		/// <summary>
+		/// Video framerate in frames per second. This value is taken from the
+		/// video file properties and used to translate from seconds
+		/// to frames: second 100 is equivalent to frame 100*fps
+		/// </summary>
+		public uint Fps {
+			get;
+			set;
+		}
+
+		/// <summary>
+		/// Start frame number
+		/// </summary>
+		public uint StartFrame {
+			get {
+				return (uint)(Start.MSeconds * Fps / 1000);
+			}
+			set {
+				Start = new Time {MSeconds = (int)(1000 * value / Fps)};
+			}
+		}
+
+		/// <summary>
+		/// Stop frame number
+		/// </summary>
+		public uint StopFrame {
+			get {
+				return (uint)(Stop.MSeconds * Fps / 1000);
+			}
+			set {
+				Stop = new Time {MSeconds = (int)(1000 * value / Fps)};
+			}
+		}
+
+		/// <summary>
+		/// Get/Set wheter this play is actually loaded. Used in  <see cref="LongoMatch.Gui.Component.TimeScale">
+		/// </summary>
+		public bool Selected {
+			get;
+			set;
+		}
+
+		/// <summary>
+		/// Central frame number using (stopFrame-startFrame)/2
+		/// </summary>
+		public uint CentralFrame {
+			get {
+				return StopFrame-((TotalFrames)/2);
+			}
+		}
+
+		/// <summary>
+		/// Number of frames inside the play's boundaries
+		/// </summary>
+		public uint TotalFrames {
+			get {
+				return StopFrame-StartFrame;
+			}
+		}
+		
+		/// <summary>
+		/// Get the key frame number if this play as key frame drawing or 0
+		/// </summary>
+		public uint KeyFrame {
+			get {
+				return 0;
+			}
+		}
+		
+		public bool HasDrawings {
+			get;
+			set;
+		}
+
+		/// <summary>
+		/// Check if the frame number is inside the play boundaries
+		/// </summary>
+		/// <param name="frame">
+		/// A <see cref="System.Int32"/> with the frame number
+		/// </param>
+		/// <returns>
+		/// A <see cref="System.Boolean"/>
+		/// </returns>
+		public bool HasFrame(int frame) {
+			return (frame>=StartFrame && frame<StopFrame);
+		}
+	}
+}
+
diff --git a/LongoMatch.GUI/Gui/Base/TimeScaleBase.cs b/LongoMatch.GUI/Gui/Base/TimeScaleBase.cs
new file mode 100644
index 0000000..c2c1fa8
--- /dev/null
+++ b/LongoMatch.GUI/Gui/Base/TimeScaleBase.cs
@@ -0,0 +1,355 @@
+// TimeScale.cs
+//
+//  Copyright (C) 2007-2009 Andoni Morales Alastruey
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+//
+//
+
+using System;
+using System.Collections.Generic;
+using Cairo;
+using Gdk;
+using Gtk;
+using Pango;
+using Mono.Unix;
+using LongoMatch.Common;
+using LongoMatch.Handlers;
+using LongoMatch.Interfaces;
+using LongoMatch.Store;
+
+namespace LongoMatch.Gui.Base
+{
+
+
+	[System.ComponentModel.Category("LongoMatch")]
+	[System.ComponentModel.ToolboxItem(true)]
+	public abstract class TimeScaleBase<T>: Gtk.DrawingArea where T:ITimelineNode
+	{
+		const int SECTION_HEIGHT = 30;
+		const double ALPHA = 0.6;
+
+		uint frames;
+		uint pixelRatio=10;
+
+		Cairo.Color color;
+		List<T> list;
+
+		T candidateTN;
+		bool candidateStart;
+		bool movingLimit;
+		uint lastTime=0;
+		uint currentFrame;
+		T selected= default(T);
+		
+		Menu deleteMenu;
+		Menu menu;
+		MenuItem delete;
+		Dictionary<MenuItem,T> dic;
+
+		Pango.Layout layout;
+		
+		protected string elementName = "";
+		protected int cursorFrame;
+
+		public TimeScaleBase(List<T> list, uint frames)
+		{
+			this.frames = frames;
+			this.list = list;
+			this.color = new Cairo.Color(0, 0, 1);
+			this.color.A = ALPHA;
+			HeightRequest= SECTION_HEIGHT;
+			Size((int)(frames/pixelRatio),SECTION_HEIGHT);
+			Events = EventMask.PointerMotionMask | EventMask.ButtonPressMask | EventMask.ButtonReleaseMask ;
+
+			dic = new Dictionary<MenuItem, T>();
+			
+			layout =  new Pango.Layout(PangoContext);
+			layout.Wrap = Pango.WrapMode.Char;
+			layout.Alignment = Pango.Alignment.Left;
+
+			SetMenu();
+		}
+
+		public uint PixelRatio {
+			get {
+				return pixelRatio;
+			}
+			set {
+				pixelRatio = value;
+				Size((int)(frames/pixelRatio),SECTION_HEIGHT);
+			}
+		}
+
+		public uint CurrentFrame {
+			get {
+				return currentFrame;
+			}
+			set {
+				currentFrame = value;
+			}
+		}
+
+		public T SelectedTimeNode {
+			get {
+				return selected;
+			}
+			set {
+				selected = value;
+			}
+		}
+
+		public void AddTimeNode(T timeNode) {
+			list.Add(timeNode);
+		}
+
+		public void RemoveTimeNode(T timeNode) {
+			list.Remove(timeNode);
+		}
+
+		public void ReDraw() {
+			Gdk.Region region = GdkWindow.ClipRegion;
+			GdkWindow.InvalidateRegion(region,true);
+			GdkWindow.ProcessUpdates(true);
+		}
+
+		abstract protected void HandleTimeNodeDeleted(List<T> timenode);
+		
+		abstract protected void HandleTimeNodeChanged(T timenode, Time time);
+		
+		abstract protected void HandleTimeNodeSelected(T timenode);
+		
+		abstract protected void AddNewTimeNode();
+		
+		void SetMenu() {
+			MenuItem newTimeNode;
+
+			menu = new Menu();
+			delete = new MenuItem(Catalog.GetString("Delete ") + elementName);
+			newTimeNode = new MenuItem(Catalog.GetString("Add new ") + elementName);
+
+			menu.Append(newTimeNode);
+			menu.Append(delete);
+
+			newTimeNode.Activated += new EventHandler(OnNewTimeNode);
+
+			menu.ShowAll();
+		}
+		
+		void DrawTimeNodes(Gdk.Window win) {
+			bool hasSelectedTimeNode=false;
+			
+			using(Cairo.Context g = Gdk.CairoHelper.Create(win)) {
+				int height;
+				int width;
+				
+				win.Resize((int)(frames/pixelRatio), Allocation.Height);
+				win.GetSize(out width, out height);
+				
+				g.Operator = Operator.Over;
+				
+				foreach(T tn in list) {
+					if(!tn.Equals(selected)) {
+						Cairo.Color borderColor = new Cairo.Color(color.R+0.1, color.G+0.1,color.B+0.1, 1);
+						CairoUtils.DrawRoundedRectangle(g,tn.StartFrame/pixelRatio,3,
+							tn.TotalFrames/pixelRatio,height-6,
+							SECTION_HEIGHT/7, color, borderColor);
+					}
+					else {
+						hasSelectedTimeNode = true;
+					}
+				}
+				//Then we draw the selected TimeNode over the others
+				if(hasSelectedTimeNode) {
+					Cairo.Color borderColor = new Cairo.Color(0, 0, 0, 1);
+					CairoUtils.DrawRoundedRectangle(g,selected.StartFrame/pixelRatio,3,
+						selected.TotalFrames/pixelRatio,height-6,
+						SECTION_HEIGHT/7, color, borderColor);
+					if(selected.HasDrawings) {
+						g.MoveTo(selected.KeyFrame/pixelRatio,3);
+						g.LineTo(selected.KeyFrame/pixelRatio,SECTION_HEIGHT-3);
+						g.StrokePreserve();
+					}
+				}
+				DrawLines(win,g,height,width);
+			}
+		}
+
+		void DrawLines(Gdk.Window win, Cairo.Context g, int height, int width) {
+			if(Environment.OSVersion.Platform == PlatformID.Unix) {
+				Cairo.Color color = new Cairo.Color(0,0,0);
+				CairoUtils.DrawLine(g, currentFrame/pixelRatio,0,currentFrame/pixelRatio,height,
+				                    1, color);
+				CairoUtils.DrawLine(g,0 ,0, width, 0, 1, color);
+				CairoUtils.DrawLine(g,0 ,height, width, height, 1, color);
+			}
+
+			else {
+				win.DrawLine(Style.DarkGC(StateType.Normal),0,0,width,0);
+				win.DrawLine(Style.DarkGC(StateType.Normal),
+				             (int)(currentFrame/pixelRatio),0,
+				             (int)(currentFrame/pixelRatio),height);
+			}
+		}
+
+
+		void DrawTimeNodesName() {
+			foreach(T tn in list) {
+				layout.Width = Pango.Units.FromPixels((int)(tn.TotalFrames/pixelRatio));
+				layout.SetMarkup(tn.Name);
+				GdkWindow.DrawLayout(Style.TextGC(StateType.Normal),
+					(int)(tn.StartFrame/pixelRatio)+2, 2, layout);
+			}
+		}
+
+		void ProcessButton3(double X) {
+			cursorFrame =(int)(X*pixelRatio);
+			deleteMenu = new Menu();
+			delete.Submenu=deleteMenu;
+			dic.Clear();
+			foreach(T tn in list) {
+				//We scan all the time Nodes looking for one matching the cursor selectcio
+				//And we add them to the delete menu
+				if(tn.HasFrame(cursorFrame)) {
+					MenuItem del = new MenuItem(Catalog.GetString("Delete "+tn.Name));
+					del.Activated += new EventHandler(OnDelete);
+					deleteMenu.Append(del);
+					dic.Add(del,tn);
+				}
+			}
+			menu.ShowAll();
+			menu.Popup();
+		}
+
+		void ProcessButton1(EventButton evnt) {
+			if(lastTime != evnt.Time) {
+				candidateTN = default(T);
+				foreach(T tn in list) {
+					int pos = (int)(evnt.X*pixelRatio);
+					//Moving from the right side
+					if(Math.Abs(pos-tn.StopFrame) < 3*pixelRatio) {
+						candidateStart = false;
+						candidateTN = tn;
+						movingLimit = true;
+						GdkWindow.Cursor = new Gdk.Cursor(CursorType.SbHDoubleArrow);
+						HandleTimeNodeChanged(tn, tn.Stop);
+						ReDraw();
+						break;
+					}
+					//Moving from the left side
+					else if(Math.Abs(pos-tn.StartFrame) < 3*pixelRatio) {
+						candidateStart =true;
+						candidateTN = tn;
+						movingLimit = true;
+						GdkWindow.Cursor = new Gdk.Cursor(CursorType.SbHDoubleArrow);
+						HandleTimeNodeChanged(tn, tn.Start);
+						ReDraw();
+						break;
+					}
+				}
+			}
+			//On Double Click
+			else {
+				foreach(T tn in list) {
+					int pos = (int)(evnt.X*pixelRatio);
+					if(tn.HasFrame(pos)) {
+						HandleTimeNodeSelected(tn);
+						break;
+					}
+				}
+			}
+		}
+		
+		void OnNewTimeNode(object obj, EventArgs args) {
+			AddNewTimeNode();
+		}
+
+		void OnDelete(object obj, EventArgs args) {
+			T tNode;
+			dic.TryGetValue((MenuItem)obj, out tNode);
+			if(tNode != null) {
+				var list = new List<T>();
+				list.Add(tNode);
+				HandleTimeNodeDeleted(list);
+			}
+		}
+
+		protected override bool OnExposeEvent(EventExpose evnt)
+		{
+			if(Visible) {
+				DrawTimeNodes(evnt.Window);
+				//We don't need the draw the Sections Names if we also draw the TimeNode name
+				//DrawSectionName();
+				DrawTimeNodesName();
+			}
+			return base.OnExposeEvent(evnt);
+		}
+
+		protected override bool OnMotionNotifyEvent(EventMotion evnt)
+		{
+			int pos = (int)(evnt.X*pixelRatio);
+
+			//If not moving don't do anything
+			if(!movingLimit) {
+			}
+			//Moving Start time
+			else if(candidateStart) {
+				if(candidateTN.HasDrawings && pos > 0 && pos > candidateTN.KeyFrame-10)
+					candidateTN.StartFrame = candidateTN.KeyFrame-10;
+				//Check not to go under start time nor 0
+				else if(pos  > 0 && pos < candidateTN.StopFrame-10)
+					candidateTN.StartFrame = (uint)pos;
+				HandleTimeNodeChanged(candidateTN, candidateTN.Start);
+			}
+			//Moving Stop time
+			else if(!candidateStart) {
+				if(candidateTN.HasDrawings &&  pos < candidateTN.KeyFrame+10)
+					candidateTN.StopFrame = candidateTN.KeyFrame+10;
+				//Check not to go under start time nor 0
+				else if(pos < frames && pos > candidateTN.StartFrame+10)
+					candidateTN.StopFrame = (uint) pos;
+				HandleTimeNodeChanged(candidateTN, candidateTN.Stop);
+			}
+
+			Gdk.Region region = GdkWindow.ClipRegion;
+			GdkWindow.InvalidateRegion(region,true);
+			GdkWindow.ProcessUpdates(true);
+
+			return base.OnMotionNotifyEvent(evnt);
+		}
+
+		protected override bool OnButtonPressEvent(EventButton evnt)
+		{
+			if(evnt.Button == 1)
+				ProcessButton1(evnt);
+			// On Right button pressed
+			else if(evnt.Button == 3)
+				ProcessButton3(evnt.X);
+			lastTime = evnt.Time;
+			return base.OnButtonPressEvent(evnt);
+		}
+
+		protected override bool OnButtonReleaseEvent(EventButton evnt)
+		{
+			if(movingLimit) {
+				movingLimit = false;
+				candidateTN.Selected = false;
+				GdkWindow.Cursor = new Gdk.Cursor(CursorType.Arrow);
+				ReDraw();
+			}
+			return base.OnButtonReleaseEvent(evnt);
+		}
+	}
+}
diff --git a/LongoMatch.GUI/Gui/Component/TimeScale.cs b/LongoMatch.GUI/Gui/Component/TimeScale.cs
index 36d7601..be7f1ed 100644
--- a/LongoMatch.GUI/Gui/Component/TimeScale.cs
+++ b/LongoMatch.GUI/Gui/Component/TimeScale.cs
@@ -26,342 +26,61 @@ using Gtk;
 using Pango;
 using Mono.Unix;
 using LongoMatch.Common;
+using LongoMatch.Gui.Base;
 using LongoMatch.Handlers;
 using LongoMatch.Store;
 
+
 namespace LongoMatch.Gui.Component
 {
 
 
 	[System.ComponentModel.Category("LongoMatch")]
 	[System.ComponentModel.ToolboxItem(true)]
-	public class TimeScale : Gtk.DrawingArea
+	public class TimeScale : TimeScaleBase<Play>
 	{
-		private const int SECTION_HEIGHT = 30;
-		private const double ALPHA = 0.6;
-
-		private uint frames;
-		private uint pixelRatio=10;
-
-		private object locker;
-
 		private Category category;
 		private Cairo.Color color;
-		private List<Play> list;
-
-		private Play candidateTN;
-		private bool candidateStart;
-		private bool movingLimit;
-		private Play selected=null;
-
-		private uint lastTime=0;
-		private uint currentFrame;
-
-		private Menu deleteMenu;
-		private Menu menu;
-		private MenuItem delete;
-		private int cursorFrame;
-		private Dictionary<MenuItem,Play> dic;
-
-		private Pango.Layout layout;
-
+		
 		public event NewTagAtFrameHandler NewMarkAtFrameEvent;
 		public event TimeNodeChangedHandler TimeNodeChanged;
 		public event PlaySelectedHandler TimeNodeSelected;
 		public event PlaysDeletedHandler TimeNodeDeleted;
 
 
-		public TimeScale(Category category, List<Play> list, uint frames)
+		public TimeScale(Category category, List<Play> list, uint frames): base(list, frames)
 		{
 			this.category = category;
-			this.frames = frames;
-			this.list = list;
-			HeightRequest= SECTION_HEIGHT;
-			Size((int)(frames/pixelRatio),SECTION_HEIGHT);
-			this.color = new Cairo.Color(0, 0, 1);
-			this.color.A = ALPHA;
-			Events = EventMask.PointerMotionMask | EventMask.ButtonPressMask | EventMask.ButtonReleaseMask ;
-
-			dic = new Dictionary<MenuItem,Play>();
-
-			layout =  new Pango.Layout(PangoContext);
-			layout.Wrap = Pango.WrapMode.Char;
-			layout.Alignment = Pango.Alignment.Left;
-
-			SetMenu();
-			locker = new object();
-		}
-
-		public uint PixelRatio {
-			get {
-				return pixelRatio;
-			}
-			set {
-				lock(locker) {
-					pixelRatio = value;
-					Size((int)(frames/pixelRatio),SECTION_HEIGHT);
-				}
-			}
-		}
-
-		public uint CurrentFrame {
-			get {
-				return currentFrame;
-			}
-			set {
-				currentFrame = value;
-			}
-		}
-
-		public Play SelectedTimeNode {
-			get {
-				return selected;
-			}
-			set {
-				selected = value;
-			}
+			elementName = Catalog.GetString("play");
 		}
 
 		public void AddPlay(Play play) {
-			list.Add(play);
+			AddTimeNode(play);
 		}
 
 		public void RemovePlay(Play play) {
-			list.Remove(play);
+			RemoveTimeNode(play);
 		}
 
-		public void ReDraw() {
-			Gdk.Region region = GdkWindow.ClipRegion;
-			GdkWindow.InvalidateRegion(region,true);
-			GdkWindow.ProcessUpdates(true);
+		override protected void HandleTimeNodeChanged(Play play, Time time) {
+			if (TimeNodeChanged != null)
+				TimeNodeChanged(play, time);
 		}
-
-
-		private void SetMenu() {
-			MenuItem newPlay;
-
-			menu = new Menu();
-			delete = new MenuItem(Catalog.GetString("Delete Play"));
-			newPlay = new MenuItem(Catalog.GetString("Add New Play"));
-
-			menu.Append(newPlay);
-			menu.Append(delete);
-
-			newPlay.Activated += new EventHandler(OnNewPlay);
-
-			menu.ShowAll();
+		
+		override protected void HandleTimeNodeSelected(Play play) {
+			if (TimeNodeSelected != null)
+				TimeNodeSelected(play);
 		}
-
-		private void DrawTimeNodes(Gdk.Window win) {
-			lock(locker) {
-				bool hasSelectedTimeNode=false;
-
-				using(Cairo.Context g = Gdk.CairoHelper.Create(win)) {
-					int height;
-					int width;
-
-					win.Resize((int)(frames/pixelRatio), Allocation.Height);
-					win.GetSize(out width, out height);
-
-					g.Operator = Operator.Over;
-
-					foreach(Play tn in list) {
-						if(tn != selected) {
-							Cairo.Color borderColor = new Cairo.Color(color.R+0.1, color.G+0.1,color.B+0.1, 1);
-							CairoUtils.DrawRoundedRectangle(g,tn.StartFrame/pixelRatio,3,
-							                                tn.TotalFrames/pixelRatio,height-6,
-							                                SECTION_HEIGHT/7, color, borderColor);
-						}
-						else {
-							hasSelectedTimeNode = true;
-						}
-					}
-					//Then we draw the selected TimeNode over the others
-					if(hasSelectedTimeNode) {
-						Cairo.Color borderColor = new Cairo.Color(0, 0, 0, 1);
-						CairoUtils.DrawRoundedRectangle(g,selected.StartFrame/pixelRatio,3,
-						                                selected.TotalFrames/pixelRatio,height-6,
-						                                SECTION_HEIGHT/7, color, borderColor);
-						if(selected.HasDrawings) {
-							g.MoveTo(selected.KeyFrame/pixelRatio,3);
-							g.LineTo(selected.KeyFrame/pixelRatio,SECTION_HEIGHT-3);
-							g.StrokePreserve();
-						}
-					}
-					DrawLines(win,g,height,width);
-				}
-			}
+		
+		override protected void HandleTimeNodeDeleted(List<Play> plays) {
+			if (TimeNodeDeleted != null)
+				TimeNodeDeleted(plays);
 		}
-
-		private void DrawLines(Gdk.Window win, Cairo.Context g, int height, int width) {
-			if(Environment.OSVersion.Platform == PlatformID.Unix) {
-				Cairo.Color color = new Cairo.Color(0,0,0);
-				CairoUtils.DrawLine(g, currentFrame/pixelRatio,0,currentFrame/pixelRatio,height,
-				                    1, color);
-				CairoUtils.DrawLine(g,0 ,0, width, 0, 1, color);
-				CairoUtils.DrawLine(g,0 ,height, width, height, 1, color);
-			}
-
-			else {
-				win.DrawLine(Style.DarkGC(StateType.Normal),0,0,width,0);
-				win.DrawLine(Style.DarkGC(StateType.Normal),
-				             (int)(currentFrame/pixelRatio),0,
-				             (int)(currentFrame/pixelRatio),height);
-			}
-		}
-
-
-		private void DrawTimeNodesName() {
-			lock(locker) {
-				foreach(Play tn in list) {
-					layout.Width = Pango.Units.FromPixels((int)(tn.TotalFrames/pixelRatio));
-					layout.SetMarkup(tn.Name);
-					GdkWindow.DrawLayout(Style.TextGC(StateType.Normal),
-					                     (int)(tn.StartFrame/pixelRatio)+2,
-					                     2,layout);
-				}
-			}
-		}
-
-		private void ProcessButton3(double X) {
-			cursorFrame =(int)(X*pixelRatio);
-			deleteMenu = new Menu();
-			delete.Submenu=deleteMenu;
-			dic.Clear();
-			foreach(Play tn in list) {
-				//We scan all the time Nodes looking for one matching the cursor selectcio
-				//And we add them to the delete menu
-				if(tn.HasFrame(cursorFrame)) {
-					MenuItem del = new MenuItem(Catalog.GetString("Delete "+tn.Name));
-					del.Activated += new EventHandler(OnDelete);
-					deleteMenu.Append(del);
-					dic.Add(del,tn);
-				}
-			}
-			menu.ShowAll();
-			menu.Popup();
-		}
-
-		private void ProcessButton1(EventButton evnt) {
-			if(lastTime != evnt.Time) {
-				candidateTN = null;
-				foreach(Play tn in list) {
-					int pos = (int)(evnt.X*pixelRatio);
-					//Moving from the right side
-					if(Math.Abs(pos-tn.StopFrame) < 3*pixelRatio) {
-						candidateStart = false;
-						candidateTN = tn;
-						movingLimit = true;
-						GdkWindow.Cursor = new Gdk.Cursor(CursorType.SbHDoubleArrow);
-						TimeNodeChanged(tn,tn.Stop);
-						ReDraw();
-						break;
-					}
-					//Moving from the left side
-					else if(Math.Abs(pos-tn.StartFrame) < 3*pixelRatio) {
-						candidateStart =true;
-						candidateTN = tn;
-						movingLimit = true;
-						GdkWindow.Cursor = new Gdk.Cursor(CursorType.SbHDoubleArrow);
-						TimeNodeChanged(tn,tn.Start);
-						ReDraw();
-						break;
-					}
-				}
-			}
-			//On Double Click
-			else {
-				foreach(Play tn in list) {
-					int pos = (int)(evnt.X*pixelRatio);
-					if(TimeNodeSelected!= null && tn.HasFrame(pos)) {
-						TimeNodeSelected(tn);
-						break;
-					}
-				}
-			}
-		}
-
-		protected void OnNewPlay(object obj, EventArgs args) {
-			if(NewMarkAtFrameEvent != null)
-				NewMarkAtFrameEvent(category,cursorFrame);
-		}
-
-		protected void OnDelete(object obj, EventArgs args) {
-			Play tNode;
-			dic.TryGetValue((MenuItem)obj, out tNode);
-			if(TimeNodeDeleted != null && tNode != null) {
-				var list = new List<Play>();
-				list.Add(tNode);
-				TimeNodeDeleted(list);
-			}
-		}
-
-		protected override bool OnExposeEvent(EventExpose evnt)
-		{
-			if(Visible) {
-				DrawTimeNodes(evnt.Window);
-				//We don't need the draw the Sections Names if we also draw the TimeNode name
-				//DrawSectionName();
-				DrawTimeNodesName();
-			}
-			return base.OnExposeEvent(evnt);
-		}
-
-		protected override bool OnMotionNotifyEvent(EventMotion evnt)
-		{
-			int pos = (int)(evnt.X*pixelRatio);
-
-			//If not moving don't do anything
-			if(!movingLimit) {
-			}
-			//Moving Start time
-			else if(candidateStart) {
-				if(candidateTN.HasDrawings && pos > 0 && pos > candidateTN.KeyFrame-10)
-					candidateTN.StartFrame = candidateTN.KeyFrame-10;
-				//Check not to go under start time nor 0
-				else if(pos  > 0 && pos < candidateTN.StopFrame-10)
-					candidateTN.StartFrame = (uint)pos;
-				if(TimeNodeChanged != null)
-					TimeNodeChanged(candidateTN,candidateTN.Start);
-			}
-			//Moving Stop time
-			else if(!candidateStart) {
-				if(candidateTN.HasDrawings &&  pos < candidateTN.KeyFrame+10)
-					candidateTN.StopFrame = candidateTN.KeyFrame+10;
-				//Check not to go under start time nor 0
-				else if(pos < frames && pos > candidateTN.StartFrame+10)
-					candidateTN.StopFrame = (uint) pos;
-				if(TimeNodeChanged != null)
-					TimeNodeChanged(candidateTN,candidateTN.Stop);
-			}
-
-			Gdk.Region region = GdkWindow.ClipRegion;
-			GdkWindow.InvalidateRegion(region,true);
-			GdkWindow.ProcessUpdates(true);
-
-			return base.OnMotionNotifyEvent(evnt);
-		}
-
-		protected override bool OnButtonPressEvent(EventButton evnt)
-		{
-			if(evnt.Button == 1)
-				ProcessButton1(evnt);
-			// On Right button pressed
-			else if(evnt.Button == 3)
-				ProcessButton3(evnt.X);
-			lastTime = evnt.Time;
-			return base.OnButtonPressEvent(evnt);
-		}
-
-		protected override bool OnButtonReleaseEvent(EventButton evnt)
-		{
-			if(movingLimit) {
-				movingLimit = false;
-				candidateTN.Selected = false;
-				GdkWindow.Cursor = new Gdk.Cursor(CursorType.Arrow);
-				ReDraw();
-			}
-			return base.OnButtonReleaseEvent(evnt);
+		
+		override protected void AddNewTimeNode() {
+			if (NewMarkAtFrameEvent != null)
+				NewMarkAtFrameEvent(category, cursorFrame);
 		}
+		
 	}
 }
diff --git a/LongoMatch.GUI/LongoMatch.GUI.mdp b/LongoMatch.GUI/LongoMatch.GUI.mdp
index f204889..c07a70f 100644
--- a/LongoMatch.GUI/LongoMatch.GUI.mdp
+++ b/LongoMatch.GUI/LongoMatch.GUI.mdp
@@ -143,6 +143,9 @@
     <File subtype="Code" buildaction="Compile" name="gtk-gui/LongoMatch.Gui.Component.GameUnitWidget.cs" />
     <File subtype="Code" buildaction="Compile" name="Gui/Component/GameUnitsTagger.cs" />
     <File subtype="Code" buildaction="Compile" name="gtk-gui/LongoMatch.Gui.Component.GameUnitsTagger.cs" />
+    <File subtype="Directory" buildaction="Compile" name="Gui/Base" />
+    <File subtype="Code" buildaction="Compile" name="Gui/Base/TimeScaleBase.cs" />
+    <File subtype="Directory" buildaction="Compile" name="Gui/Component" />
   </Contents>
   <MonoDevelop.Autotools.MakefileInfo RelativeMakefileName="../CesarPlayer/Makefile.am" RelativeConfigureInPath="../">
     <BuildFilesVar Name="FILES" />
diff --git a/LongoMatch.GUI/Makefile.am b/LongoMatch.GUI/Makefile.am
index 79c9260..1a2b7be 100644
--- a/LongoMatch.GUI/Makefile.am
+++ b/LongoMatch.GUI/Makefile.am
@@ -52,6 +52,7 @@ SOURCES = \
 	gtk-gui/LongoMatch.Gui.MainWindow.cs \
 	gtk-gui/LongoMatch.Gui.Popup.CalendarPopup.cs \
 	gtk-gui/LongoMatch.Gui.Popup.TransparentDrawingArea.cs \
+	Gui/Base/TimeScaleBase.cs \
 	Gui/Component/ButtonsWidget.cs \
 	Gui/Component/CategoriesScale.cs \
 	Gui/Component/CategoriesTemplateEditor.cs \
diff --git a/LongoMatch.GUI/gtk-gui/objects.xml b/LongoMatch.GUI/gtk-gui/objects.xml
index b6f32fd..ac16f53 100644
--- a/LongoMatch.GUI/gtk-gui/objects.xml
+++ b/LongoMatch.GUI/gtk-gui/objects.xml
@@ -25,22 +25,6 @@
       </itemgroup>
     </signals>
   </object>
-  <object type="LongoMatch.Gui.Component.TimeScale" palette-category="LongoMatch" allow-children="false" base-type="Gtk.DrawingArea">
-    <itemgroups>
-      <itemgroup label="TimeScale Properties">
-        <property name="PixelRatio" />
-        <property name="CurrentFrame" />
-      </itemgroup>
-    </itemgroups>
-    <signals>
-      <itemgroup label="TimeScale Signals">
-        <signal name="NewMarkAtFrameEvent" />
-        <signal name="TimeNodeChanged" />
-        <signal name="TimeNodeSelected" />
-        <signal name="TimeNodeDeleted" />
-      </itemgroup>
-    </signals>
-  </object>
   <object type="LongoMatch.Gui.Component.TimeReferenceWidget" palette-category="LongoMatch" allow-children="false" base-type="Gtk.DrawingArea">
     <itemgroups>
       <itemgroup label="TimeReferenceWidget Properties">
@@ -130,61 +114,6 @@
     <itemgroups />
     <signals />
   </object>
-  <object type="LongoMatch.Gui.Component.TagsTreeView" palette-category="LongoMatch" allow-children="false" base-type="Gtk.TreeView">
-    <itemgroups>
-      <itemgroup label="ListTreeViewBase Properties">
-        <property name="Colors" />
-      </itemgroup>
-    </itemgroups>
-    <signals>
-      <itemgroup label="ListTreeViewBase Signals">
-        <signal name="TimeNodeChanged" />
-        <signal name="TimeNodeSelected" />
-        <signal name="TimeNodeDeleted" />
-        <signal name="PlayListNodeAdded" />
-        <signal name="SnapshotSeriesEvent" />
-        <signal name="TagPlay" />
-        <signal name="NewRenderingJob" />
-      </itemgroup>
-    </signals>
-  </object>
-  <object type="LongoMatch.Gui.Component.PlaysTreeView" palette-category="LongoMatch" allow-children="false" base-type="Gtk.TreeView">
-    <itemgroups>
-      <itemgroup label="ListTreeViewBase Properties">
-        <property name="Colors" />
-      </itemgroup>
-    </itemgroups>
-    <signals>
-      <itemgroup label="PlaysTreeView Signals">
-        <signal name="EditProperties" />
-        <signal name="TimeNodeChanged" />
-        <signal name="TimeNodeSelected" />
-        <signal name="TimeNodeDeleted" />
-        <signal name="PlayListNodeAdded" />
-        <signal name="SnapshotSeriesEvent" />
-        <signal name="TagPlay" />
-        <signal name="NewRenderingJob" />
-      </itemgroup>
-    </signals>
-  </object>
-  <object type="LongoMatch.Gui.Component.PlayersTreeView" palette-category="LongoMatch" allow-children="false" base-type="Gtk.TreeView">
-    <itemgroups>
-      <itemgroup label="ListTreeViewBase Properties">
-        <property name="Colors" />
-      </itemgroup>
-    </itemgroups>
-    <signals>
-      <itemgroup label="ListTreeViewBase Signals">
-        <signal name="TimeNodeChanged" />
-        <signal name="TimeNodeSelected" />
-        <signal name="TimeNodeDeleted" />
-        <signal name="PlayListNodeAdded" />
-        <signal name="SnapshotSeriesEvent" />
-        <signal name="TagPlay" />
-        <signal name="NewRenderingJob" />
-      </itemgroup>
-    </signals>
-  </object>
   <object type="LongoMatch.Gui.Component.NotesWidget" palette-category="LongoMatch" allow-children="false" base-type="Gtk.Bin">
     <itemgroups />
     <signals>
@@ -223,21 +152,6 @@
       </itemgroup>
     </signals>
   </object>
-  <object type="LongoMatch.Gui.Component.TeamTemplateEditorWidget" palette-category="General" allow-children="false" base-type="Gtk.Bin">
-    <itemgroups>
-    </itemgroups>
-    <signals />
-  </object>
-  <object type="LongoMatch.Gui.Component.CategoriesTemplateEditorWidget" palette-category="General" allow-children="false" base-type="Gtk.Bin">
-    <itemgroups>
-    </itemgroups>
-    <signals />
-  </object>
-  <object type="LongoMatch.Gui.Component.TemplatesEditorWidget" palette-category="General" allow-children="false" base-type="Gtk.Bin">
-    <itemgroups>
-    </itemgroups>
-    <signals />
-  </object>
   <object type="LongoMatch.Gui.Component.CategoryProperties" palette-category="LongoMatch" allow-children="false" base-type="Gtk.Bin">
     <itemgroups />
     <signals>
@@ -273,30 +187,6 @@
       </itemgroup>
     </signals>
   </object>
-  <object type="LongoMatch.Gui.Component.TimeLineWidget" palette-category="LongoMatch" allow-children="false" base-type="Gtk.Bin">
-    <itemgroups>
-      <itemgroup label="TimeLineWidget Properties">
-        <property name="CurrentFrame" />
-      </itemgroup>
-    </itemgroups>
-    <signals>
-      <itemgroup label="TimeLineWidget Signals">
-        <signal name="TimeNodeChanged" />
-        <signal name="TimeNodeSelected" />
-        <signal name="TimeNodeDeleted" />
-        <signal name="NewMarkEvent" />
-      </itemgroup>
-    </signals>
-  </object>
-  <object type="LongoMatch.Gui.Component.TemplatesEditorBase" palette-category="LongoMatch" allow-children="false" base-type="Gtk.Bin">
-    <itemgroups>
-      <itemgroup label="TemplatesEditorBase Properties">
-        <property name="CanExport" />
-        <property name="Edited" />
-      </itemgroup>
-    </itemgroups>
-    <signals />
-  </object>
   <object type="LongoMatch.Gui.Component.GameUnitsTagger" palette-category="General" allow-children="false" base-type="Gtk.Bin">
     <itemgroups />
     <signals>
@@ -334,4 +224,71 @@
       </itemgroup>
     </signals>
   </object>
+  <object type="LongoMatch.Gui.Component.TimeScale" palette-category="LongoMatch" allow-children="false" base-type="Gtk.DrawingArea">
+    <itemgroups>
+      <itemgroup label="TimeScale Properties">
+        <property name="PixelRatio" />
+        <property name="CurrentFrame" />
+      </itemgroup>
+    </itemgroups>
+    <signals>
+      <itemgroup label="TimeScale Signals">
+        <signal name="NewMarkAtFrameEvent" />
+        <signal name="TimeNodeChanged" />
+        <signal name="TimeNodeSelected" />
+        <signal name="TimeNodeDeleted" />
+      </itemgroup>
+    </signals>
+  </object>
+  <object type="LongoMatch.Gui.Component.TimeLineWidget" palette-category="LongoMatch" allow-children="false" base-type="Gtk.Bin">
+    <itemgroups>
+      <itemgroup label="TimeLineWidget Properties">
+        <property name="CurrentFrame" />
+      </itemgroup>
+    </itemgroups>
+    <signals>
+      <itemgroup label="TimeLineWidget Signals">
+        <signal name="TimeNodeChanged" />
+        <signal name="TimeNodeSelected" />
+        <signal name="TimeNodeDeleted" />
+        <signal name="NewMarkEvent" />
+      </itemgroup>
+    </signals>
+  </object>
+  <object type="LongoMatch.Gui.Base.TimeScaleBase" palette-category="LongoMatch" allow-children="false" base-type="Gtk.DrawingArea">
+    <itemgroups>
+      <itemgroup label="TimeScaleBase Properties">
+        <property name="PixelRatio" />
+        <property name="CurrentFrame" />
+      </itemgroup>
+    </itemgroups>
+    <signals />
+  </object>
+  <object type="LongoMatch.Gui.Base.TemplatesEditorWidget" palette-category="General" allow-children="false" base-type="Gtk.Bin">
+    <itemgroups>
+      <itemgroup label="TemplatesEditorBase Properties">
+        <property name="CanExport" />
+        <property name="Edited" />
+      </itemgroup>
+    </itemgroups>
+    <signals />
+  </object>
+  <object type="LongoMatch.Gui.Base.TemplatesEditorBase" palette-category="LongoMatch" allow-children="false" base-type="Gtk.Bin">
+    <itemgroups>
+      <itemgroup label="TemplatesEditorBase Properties">
+        <property name="CanExport" />
+        <property name="Edited" />
+      </itemgroup>
+    </itemgroups>
+    <signals />
+  </object>
+  <object type="LongoMatch.Gui.Component.CategoriesTemplateEditorWidget" palette-category="General" allow-children="false" base-type="Gtk.Bin">
+    <itemgroups>
+      <itemgroup label="TemplatesEditorBase Properties">
+        <property name="CanExport" />
+        <property name="Edited" />
+      </itemgroup>
+    </itemgroups>
+    <signals />
+  </object>
 </objects>
\ No newline at end of file
diff --git a/LongoMatch.Services/Services/GameUnitsManager.cs b/LongoMatch.Services/Services/GameUnitsManager.cs
index 26ebdef..0a03d09 100644
--- a/LongoMatch.Services/Services/GameUnitsManager.cs
+++ b/LongoMatch.Services/Services/GameUnitsManager.cs
@@ -68,7 +68,7 @@ namespace LongoMatch.Services
 		}
 		
 		private void StopGameUnit(GameUnit gameUnit) {
-			TimeNode timeInfo;
+			TimelineNode timeInfo;
 			GameUnit projectGameUnit;
 			Time start, stop;
 			
@@ -77,7 +77,7 @@ namespace LongoMatch.Services
 			
 			start = gameUnitsStarted[gameUnit];
 			stop = new Time{MSeconds=(int)player.CurrentTime};
-			timeInfo = new TimeNode {Start=start, Stop=stop};
+			timeInfo = new GameUnit {Start=start, Stop=stop};
 			
 			gameUnit.Add(timeInfo);
 			gameUnitsStarted.Remove(gameUnit);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]