[tasque] Remove Inactivate task state and outsource timer treeview col



commit abfa2f8197f212936b68e338b294c5a1217ff6a0
Author: Antonius Riha <antoniusriha gmail com>
Date:   Sun Jan 6 22:46:50 2013 +0100

    Remove Inactivate task state and outsource timer treeview col

 src/Addins/Backends/Dummy/DummyTask.cs   |    7 -
 src/Addins/Backends/Rtm/RtmTask.cs       |   10 -
 src/Addins/Backends/Sqlite/SqliteTask.cs |    7 -
 src/Gtk.Tasque/AppIndicatorTray.cs       |    3 +-
 src/Gtk.Tasque/Gtk.Tasque.csproj         |    1 +
 src/Gtk.Tasque/GtkApplicationBase.cs     |    3 +-
 src/Gtk.Tasque/GtkLinuxApplication.cs    |    3 +-
 src/Gtk.Tasque/GtkTray.cs                |    5 +-
 src/Gtk.Tasque/ITaskField.cs             |   36 ++++
 src/Gtk.Tasque/RemoteControl.cs          |    5 +-
 src/Gtk.Tasque/StatusIconTray.cs         |    3 +-
 src/Gtk.Tasque/TaskTreeView.cs           |  317 ++++--------------------------
 src/Gtk.Tasque/TaskWindow.cs             |    6 +-
 src/Gtk.Tasque/TimerColumn.cs            |  317 ++++++++++++++++++++++++++++++
 src/Gtk.Tasque/TimerTaskField.cs         |   44 ++++
 src/libtasque/AbstractTask.cs            |    1 -
 src/libtasque/ITask.cs                   |    7 +-
 src/libtasque/TaskGroupModel.cs          |    5 -
 src/libtasque/TaskState.cs               |    7 -
 src/tasque/Program.cs                    |    1 +
 20 files changed, 451 insertions(+), 337 deletions(-)
---
diff --git a/src/Addins/Backends/Dummy/DummyTask.cs b/src/Addins/Backends/Dummy/DummyTask.cs
index 5d51199..83d979d 100644
--- a/src/Addins/Backends/Dummy/DummyTask.cs
+++ b/src/Addins/Backends/Dummy/DummyTask.cs
@@ -133,13 +133,6 @@ Logger.Debug ("DummyTask.Activate ()");
 			CompletionDate = DateTime.MinValue;
 		}
 		
-		public override void Inactivate ()
-		{
-Logger.Debug ("DummyTask.Inactivate ()");
-			state = TaskState.Inactive;
-			CompletionDate = DateTime.Now;
-		}
-		
 		public override void Complete ()
 		{
 			Logger.Debug ("DummyTask.Complete ()");
diff --git a/src/Addins/Backends/Rtm/RtmTask.cs b/src/Addins/Backends/Rtm/RtmTask.cs
index d6bb74f..c36b707 100644
--- a/src/Addins/Backends/Rtm/RtmTask.cs
+++ b/src/Addins/Backends/Rtm/RtmTask.cs
@@ -252,16 +252,6 @@ namespace Tasque.Backends.Rtm
 		}
 		
 		/// <summary>
-		/// Sets the task to be inactive
-		/// </summary>
-		public override void Inactivate ()
-		{
-			Logger.Debug("Inactivating Task: " + Name);		
-			state = TaskState.Inactive;
-			CompletionDate = DateTime.Now;
-		}
-		
-		/// <summary>
 		/// Completes the task
 		/// </summary>
 		public override void Complete ()
diff --git a/src/Addins/Backends/Sqlite/SqliteTask.cs b/src/Addins/Backends/Sqlite/SqliteTask.cs
index d262fd7..c12c8a5 100644
--- a/src/Addins/Backends/Sqlite/SqliteTask.cs
+++ b/src/Addins/Backends/Sqlite/SqliteTask.cs
@@ -191,13 +191,6 @@ namespace Tasque.Backends.Sqlite
 			CompletionDate = DateTime.MinValue;
 		}
 		
-		public override void Inactivate ()
-		{
-			// Logger.Debug ("SqliteTask.Inactivate ()");
-			LocalState = TaskState.Inactive;
-			CompletionDate = DateTime.Now;
-		}
-		
 		public override void Complete ()
 		{
 			//Logger.Debug ("SqliteTask.Complete ()");
diff --git a/src/Gtk.Tasque/AppIndicatorTray.cs b/src/Gtk.Tasque/AppIndicatorTray.cs
index d724345..e561d2d 100644
--- a/src/Gtk.Tasque/AppIndicatorTray.cs
+++ b/src/Gtk.Tasque/AppIndicatorTray.cs
@@ -23,10 +23,11 @@
 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 // THE SOFTWARE.
+using Tasque;
 using Gtk;
 using AppIndicator;
 
-namespace Tasque
+namespace Gtk.Tasque
 {
 	public class AppIndicatorTray : GtkTray
 	{
diff --git a/src/Gtk.Tasque/Gtk.Tasque.csproj b/src/Gtk.Tasque/Gtk.Tasque.csproj
index 17b6fe6..d2376f7 100644
--- a/src/Gtk.Tasque/Gtk.Tasque.csproj
+++ b/src/Gtk.Tasque/Gtk.Tasque.csproj
@@ -105,6 +105,7 @@
     <Compile Include="GtkLinuxApplication.cs" />
     <Compile Include="GtkWinApplication.cs" />
     <Compile Include="TreeModelListAdapter.cs" />
+    <Compile Include="TimerColumn.cs" />
   </ItemGroup>
   <ItemGroup Condition=" '$(Configuration)' == 'LinuxDebug' Or '$(Configuration)' == 'LinuxRelease' ">
     <Compile Include="RemoteControl.cs" />
diff --git a/src/Gtk.Tasque/GtkApplicationBase.cs b/src/Gtk.Tasque/GtkApplicationBase.cs
index 7c7004f..879031c 100644
--- a/src/Gtk.Tasque/GtkApplicationBase.cs
+++ b/src/Gtk.Tasque/GtkApplicationBase.cs
@@ -28,12 +28,13 @@ using System;
 using System.Diagnostics;
 using System.IO;
 using Mono.Unix;
+using Tasque;
 using Gtk;
 #if ENABLE_NOTIFY_SHARP
 using Notifications;
 #endif
 
-namespace Tasque
+namespace Gtk.Tasque
 {
 	public abstract class GtkApplicationBase : NativeApplication
 	{
diff --git a/src/Gtk.Tasque/GtkLinuxApplication.cs b/src/Gtk.Tasque/GtkLinuxApplication.cs
index 3157f0c..a2e7403 100644
--- a/src/Gtk.Tasque/GtkLinuxApplication.cs
+++ b/src/Gtk.Tasque/GtkLinuxApplication.cs
@@ -25,8 +25,9 @@
 // THE SOFTWARE.
 #if LINUX
 using System;
+using Tasque;
 
-namespace Tasque
+namespace Gtk.Tasque
 {
 	public class GtkLinuxApplication : GtkApplicationBase
 	{
diff --git a/src/Gtk.Tasque/GtkTray.cs b/src/Gtk.Tasque/GtkTray.cs
index 71a66c5..f4f4ba3 100644
--- a/src/Gtk.Tasque/GtkTray.cs
+++ b/src/Gtk.Tasque/GtkTray.cs
@@ -27,10 +27,11 @@ using System;
 using System.Linq;
 using System.Text;
 using Mono.Unix;
-using Gtk;
+using Tasque;
 using Tasque.Backends;
+using Gtk;
 
-namespace Tasque
+namespace Gtk.Tasque
 {
 	public abstract class GtkTray : IDisposable
 	{
diff --git a/src/Gtk.Tasque/ITaskField.cs b/src/Gtk.Tasque/ITaskField.cs
new file mode 100644
index 0000000..b879136
--- /dev/null
+++ b/src/Gtk.Tasque/ITaskField.cs
@@ -0,0 +1,36 @@
+//
+// ITaskColumn.cs
+//
+// Author:
+//       Antonius Riha <antoniusriha gmail com>
+//
+// Copyright (c) 2013 Antonius Riha
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+using Tasque;
+
+namespace Gtk.Tasque
+{
+	public interface ITaskField
+	{
+		string HeaderText { get; set; }
+		void Initialize (ITask task);
+	}
+}
diff --git a/src/Gtk.Tasque/RemoteControl.cs b/src/Gtk.Tasque/RemoteControl.cs
index 8553c65..347279e 100644
--- a/src/Gtk.Tasque/RemoteControl.cs
+++ b/src/Gtk.Tasque/RemoteControl.cs
@@ -9,6 +9,7 @@ using System.Collections.Generic;
 using System.Linq;
 
 using Mono.Unix; // for Catalog.GetString ()
+using Tasque;
 
 #if ENABLE_NOTIFY_SHARP
 using Notifications;
@@ -17,7 +18,7 @@ using Notifications;
 using org.freedesktop.DBus;
 using DBus;
 
-namespace Tasque
+namespace Gtk.Tasque
 {
 	[Interface ("org.gnome.Tasque.RemoteControl")]
 	public class RemoteControl : MarshalByRefObject
@@ -61,7 +62,7 @@ namespace Tasque
 				RemoteInstanceKnocked ();
 		}
 		
-		public Action RemoteInstanceKnocked { get; set; }
+		public System.Action RemoteInstanceKnocked { get; set; }
 				
 		/// <summary>
 		/// Create a new task in Tasque using the given categoryName and name.
diff --git a/src/Gtk.Tasque/StatusIconTray.cs b/src/Gtk.Tasque/StatusIconTray.cs
index d379889..12e329d 100644
--- a/src/Gtk.Tasque/StatusIconTray.cs
+++ b/src/Gtk.Tasque/StatusIconTray.cs
@@ -23,9 +23,10 @@
 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 // THE SOFTWARE.
+using Tasque;
 using Gtk;
 
-namespace Tasque
+namespace Gtk.Tasque
 {
 	public class StatusIconTray : GtkTray
 	{
diff --git a/src/Gtk.Tasque/TaskTreeView.cs b/src/Gtk.Tasque/TaskTreeView.cs
index a0f39d6..b6b87cc 100644
--- a/src/Gtk.Tasque/TaskTreeView.cs
+++ b/src/Gtk.Tasque/TaskTreeView.cs
@@ -3,10 +3,11 @@
 
 using System;
 using System.Collections.Generic;
-using Gtk;
 using Mono.Unix;
+using Tasque;
+using Gtk;
 
-namespace Tasque
+namespace Gtk.Tasque
 {
 	/// <summary>
 	/// This is the main TreeView widget that is used to show tasks in Tasque's
@@ -16,10 +17,10 @@ namespace Tasque
 	{
 		IPreferences preferences;
 
+		TimerColumn timerCol;
+
 		private static Gdk.Pixbuf notePixbuf;
 		
-		private static Gdk.Pixbuf[] inactiveAnimPixbufs;
-		
 		private Gtk.TreeModelFilter modelFilter;
 		private ICategory filterCategory;	
 		private ITask taskBeingEdited = null;
@@ -29,12 +30,6 @@ namespace Tasque
 		static TaskTreeView ()
 		{
 			notePixbuf = Utilities.GetIcon ("tasque-note", 12);
-			
-			inactiveAnimPixbufs = new Gdk.Pixbuf [12];
-			for (int i = 0; i < 12; i++) {
-				string iconName = string.Format ("tasque-completing-{0}", i);
-				inactiveAnimPixbufs [i] = Utilities.GetIcon (iconName, 16);
-			}
 		}
 		
 		public event EventHandler NumberOfTasksChanged;
@@ -242,20 +237,17 @@ namespace Tasque
 			//
 			// Timer Column
 			//
-			column = new Gtk.TreeViewColumn ();
-			// Title for Timer Column
-			column.Title = Catalog.GetString ("Timer");
-			column.Sizing = Gtk.TreeViewColumnSizing.Fixed;
-			column.FixedWidth = 20;
-			column.Resizable = false;
-			
-			renderer = new Gtk.CellRendererPixbuf ();
-			renderer.Xalign = 0.5f;
-			column.PackStart (renderer, false);
-			column.SetCellDataFunc (renderer,
-				new Gtk.TreeCellDataFunc (TaskTimerCellDataFunc));
-			
-			AppendColumn (column);
+			timerCol = new TimerColumn (preferences, model);
+			timerCol.TimerExpired += (sender, e) => {
+				if (!e.Canceled)
+					e.Task.Complete ();
+			};
+
+			timerCol.Tick += (sender, e) => {
+				var status = string.Format (Catalog.GetString ("Completing Task In: {0}"), e.CountdownTick);
+				TaskWindow.ShowStatus (status, 2000);
+			};
+			AppendColumn (timerCol);
 		}
 
 		void CellRenderer_EditingStarted (object o, EditingStartedArgs args)
@@ -271,11 +263,7 @@ namespace Tasque
 				return;
 
 			taskBeingEdited = task;
-
-			if (task.State != TaskState.Inactive)
-				return;
-
-			InactivateTimer.ToggleTimer (taskBeingEdited);
+			timerCol.PauseTimer (taskBeingEdited);
 		}
 		
 		void SetCellRendererCallbacks (CellRendererText renderer, EditedHandler handler)
@@ -285,10 +273,9 @@ namespace Tasque
 			// Canceled: timer can continue.
 			renderer.EditingCanceled += (o, args) => {
 				if (taskBeingEdited != null) {
-					if (taskBeingEdited.State == TaskState.Inactive) {
-						taskBeingEdited.Inactivate ();
-						InactivateTimer.ToggleTimer (taskBeingEdited);
-					}
+					var timerState = timerCol.GetTimerState (taskBeingEdited);
+					if (timerState != null && (TimerColumn.TaskTimerState)timerState == TimerColumn.TaskTimerState.Paused)
+						timerCol.ResumeTimer (taskBeingEdited);
 					taskBeingEdited = null;
 				}
 			};
@@ -298,10 +285,9 @@ namespace Tasque
 					handler (o, args);
 
 				if (taskBeingEdited != null) {
-					if (taskBeingEdited.State == TaskState.Inactive) {
-						taskBeingEdited.Inactivate ();
-						InactivateTimer.ToggleTimer (taskBeingEdited);
-					}
+					var timerState = timerCol.GetTimerState (taskBeingEdited);
+					if (timerState != null && (TimerColumn.TaskTimerState)timerState == TimerColumn.TaskTimerState.Paused)
+						timerCol.ResumeTimer (taskBeingEdited);
 					taskBeingEdited = null;
 				}
 			};
@@ -352,8 +338,8 @@ namespace Tasque
 			if (task == null)
 				crt.Active = false;
 			else {
-				crt.Active =
-					task.State == TaskState.Active ? false : true;
+				var timerState = timerCol.GetTimerState (task);
+				crt.Active = !(task.State == TaskState.Active && timerState == null);
 			}
 		}
 
@@ -460,9 +446,12 @@ namespace Tasque
 				crt.Foreground = overdueTaskColor;
 
 			switch (task.State) {
-			case TaskState.Inactive:
+			case TaskState.Active:
 				// Strikeout the text
-				formatString = "<span strikethrough=\"true\">{0}</span>";
+				var timerState = timerCol.GetTimerState (task);
+				if (timerState != null && (TimerColumn.TaskTimerState)timerState
+				    == TimerColumn.TaskTimerState.Running)
+					formatString = "<span strikethrough=\"true\">{0}</span>";
 				break;
 			case TaskState.Deleted:
 			case TaskState.Completed:
@@ -516,83 +505,6 @@ namespace Tasque
 			crp.Pixbuf = task.HasNotes ? notePixbuf : null;
 		}
 		
-		private void TaskTimerCellDataFunc (Gtk.TreeViewColumn treeColumn,
-				Gtk.CellRenderer renderer, Gtk.TreeModel model,
-				Gtk.TreeIter iter)
-		{
-			Gtk.CellRendererPixbuf crp = renderer as Gtk.CellRendererPixbuf;
-			ITask task = model.GetValue (iter, 0) as ITask;
-			if (task == null)
-				return;
-			
-			if (task.State != TaskState.Inactive) {
-				// The task is not in the inactive state so don't show any icon
-				crp.Pixbuf = null;
-				return;
-			}
-
-			int timerSeconds = preferences.GetInt (PreferencesKeys.InactivateTimeoutKey);
-			// convert to milliseconds for more granularity
-			long timeout = timerSeconds * 1000;
-			
-			//Logger.Debug ("TaskTimerCellDataFunc ()\n\tNow.Ticks: {0}\n\tCompletionDate.Ticks: {1}",
-			//				DateTime.Now.Ticks, task.CompletionDate.Ticks);
-			long elapsedTicks = DateTime.Now.Ticks - task.CompletionDate.Ticks;
-			//Logger.Debug ("\tElapsed Ticks: {0}", elapsedTicks);
-			long elapsedMillis = elapsedTicks / 10000;
-			//Logger.Debug ("\tElapsed Milliseconds: {0}", elapsedMillis);
-			
-			double percentComplete = (double)elapsedMillis / (double)timeout;
-			//Logger.Debug ("\tPercent Complete: {0}", percentComplete);
-			
-			Gdk.Pixbuf pixbuf = GetIconForPercentage (percentComplete * 100);
-			crp.Pixbuf = pixbuf;
-		}
-		
-		protected static Gdk.Pixbuf GetIconForPercentage (double timeoutPercent)
-		{
-			int iconNum = GetIconNumForPercentage (timeoutPercent);
-			if (iconNum == -1 || iconNum > 11)
-				return null;
-			
-			return inactiveAnimPixbufs [iconNum];
-		}
-		
-		protected static int GetIconNumForPercentage (double timeoutPercent)
-		{
-			//Logger.Debug ("GetIconNumForPercentage: {0}", timeoutPercent);
-			int numOfIcons = 12;
-			double percentIncrement = (double)100 / (double)numOfIcons;
-			//Logger.Debug ("\tpercentIncrement: {0}", percentIncrement);
-			
-			if (timeoutPercent < percentIncrement)
-				return 0;
-			if (timeoutPercent < percentIncrement * 2)
-				return 1;
-			if (timeoutPercent < percentIncrement * 3)
-				return 2;
-			if (timeoutPercent < percentIncrement * 4)
-				return 3;
-			if (timeoutPercent < percentIncrement * 5)
-				return 4;
-			if (timeoutPercent < percentIncrement * 6)
-				return 5;
-			if (timeoutPercent < percentIncrement * 7)
-				return 6;
-			if (timeoutPercent < percentIncrement * 8)
-				return 7;
-			if (timeoutPercent < percentIncrement * 9)
-				return 8;
-			if (timeoutPercent < percentIncrement * 10)
-				return 9;
-			if (timeoutPercent < percentIncrement * 11)
-				return 10;
-			if (timeoutPercent < percentIncrement * 12)
-				return 11;
-			
-			return -1;
-		}
-		
 		protected virtual bool FilterFunc (Gtk.TreeModel model,
 										   Gtk.TreeIter iter)
 		{
@@ -629,8 +541,8 @@ namespace Tasque
 			if (task == null)
 				return;
 
-			// remove any timer set up on this task			
-			InactivateTimer.CancelTimer(task);
+			// remove any timer set up on this task
+			timerCol.CancelTimer (task);
 			
 			if (task.State == TaskState.Active) {
 				bool showCompletedTasks =
@@ -642,17 +554,8 @@ namespace Tasque
 				if (showCompletedTasks) {
 					task.Complete ();
 					ShowCompletedTaskStatus ();
-				} else {
-					task.Inactivate ();
-					
-					// Read the inactivate timeout from a preference
-					int timeout =
-						preferences.GetInt (PreferencesKeys.InactivateTimeoutKey);
-					Logger.Debug ("Read timeout from prefs: {0}", timeout);
-					InactivateTimer timer =
-						new InactivateTimer (this, iter, task, (uint) timeout);
-					timer.StartTimer ();
-				}
+				} else
+					timerCol.StartTimer (task);
 			} else {
 				status = Catalog.GetString ("Action Canceled");
 				TaskWindow.ShowStatus (status);
@@ -806,157 +709,5 @@ namespace Tasque
 			NumberOfTasksChanged (this, EventArgs.Empty);
 		}
 		#endregion // EventHandlers
-		
-		#region Private Classes
-		/// <summary>
-		/// Does the work of walking a task through the Inactive -> Complete
-		/// states
-		/// </summary>
-		class InactivateTimer
-		{
-			/// <summary>
-			/// Keep track of all the timers so that the pulseTimeoutId can
-			/// be removed at the proper time.
-			/// </summary>
-			private static Dictionary<uint, InactivateTimer> timers;
-			
-			static InactivateTimer ()
-			{
-				timers = new Dictionary<uint,InactivateTimer> ();
-			}
-			
-			private TaskTreeView tree;
-			private ITask task;
-			private uint delay;
-			private uint secondsLeft;
-			protected uint pulseTimeoutId;
-			private uint secondTimerId;
-			private Gtk.TreeIter iter;
-			private Gtk.TreePath path;
-			
-			public InactivateTimer (TaskTreeView treeView,
-									Gtk.TreeIter taskIter,
-									ITask taskToComplete,
-									uint delayInSeconds)
-			{
-				tree = treeView;
-				iter = taskIter;
-				path = treeView.Model.GetPath (iter);
-				task = taskToComplete;
-				secondsLeft = delayInSeconds;
-				delay = delayInSeconds * 1000; // Convert to milliseconds
-				pulseTimeoutId = 0;
-			}
-			
-			public bool Paused {
-				get; set;
-			}
-
-			public void StartTimer ()
-			{
-				pulseTimeoutId = GLib.Timeout.Add (500, PulseAnimation);
-				StartSecondCountdown ();
-				task.TimerID = GLib.Timeout.Add (delay, CompleteTask);
-				timers [task.TimerID] = this;
-			}
-
-			public static void ToggleTimer (ITask task)
-			{
-				InactivateTimer timer = null;
-				if (timers.TryGetValue (task.TimerID, out timer))
-					timer.Paused = !timer.Paused;
-			}
-
-			public static void CancelTimer(ITask task)
-			{
-				Logger.Debug ("Timeout Canceled for task: " + task.Name);
-				InactivateTimer timer = null;
-				uint timerId = task.TimerID;
-				if(timerId != 0) {
-					if (timers.ContainsKey (timerId)) {
-						timer = timers [timerId];
-						timers.Remove (timerId);
-					}
-					GLib.Source.Remove(timerId);
-					GLib.Source.Remove (timer.pulseTimeoutId);
-					timer.pulseTimeoutId = 0;
-					task.TimerID = 0;
-				}
-				
-				if (timer != null) {
-					GLib.Source.Remove (timer.pulseTimeoutId);
-					timer.pulseTimeoutId = 0;
-					GLib.Source.Remove (timer.secondTimerId);
-					timer.secondTimerId = 0;
-					timer.Paused = false;
-				}
-			}
-			
-			private bool CompleteTask ()
-			{
-				if (!Paused) {
-					GLib.Source.Remove (pulseTimeoutId);
-					if (timers.ContainsKey (task.TimerID))
-						timers.Remove (task.TimerID);
-					
-					if(task.State != TaskState.Inactive)
-						return false;
-						
-					task.Complete ();
-					ShowCompletedTaskStatus ();
-					tree.Refilter ();
-					return false; // Don't automatically call this handler again
-				}
-
-				return true;
-			}
-			
-			private bool PulseAnimation ()
-			{
-				if (tree.Model == null) {
-					// Widget has been closed, no need to call this again
-					return false;
-				} else {
-					if (!Paused) {
-						// Emit this signal to cause the TreeView to update the row
-						// where the task is located.  This will allow the
-						// CellRendererPixbuf to update the icon.
-						tree.Model.EmitRowChanged (path, iter);
-						
-						// Return true so that this method will be called after an
-						// additional timeout duration has elapsed.
-						return true;
-					}
-				}
-
-				return true;
-			}
-
-			private void StartSecondCountdown ()
-			{
-				SecondCountdown();
-				secondTimerId = GLib.Timeout.Add (1000, SecondCountdown);
-			}
-
-			private bool SecondCountdown ()
-			{
-				if (tree.Model == null) {
-					// Widget has been closed, no need to call this again
-					return false;
-				}
-				if (!Paused) {
-					if (secondsLeft > 0 && task.State == TaskState.Inactive) {
-						status = String.Format (Catalog.GetString ("Completing Task In: {0}"), secondsLeft--);
-						TaskWindow.ShowStatus (status);
-						return true;
-					} else {
-						return false;
-					}
-				}
-				return true;
-			}
-	
-		}
-		#endregion // Private Classes
 	}
 }
diff --git a/src/Gtk.Tasque/TaskWindow.cs b/src/Gtk.Tasque/TaskWindow.cs
index 9576fb7..5bbd8c7 100644
--- a/src/Gtk.Tasque/TaskWindow.cs
+++ b/src/Gtk.Tasque/TaskWindow.cs
@@ -35,10 +35,10 @@ using System.Linq;
 using Gdk;
 using Gtk;
 using Mono.Unix;
-
+using Tasque;
 using Tasque.Backends;
 
-namespace Tasque
+namespace Gtk.Tasque
 {
 	public class TaskWindow : Gtk.Window 
 	{
@@ -670,7 +670,7 @@ namespace Tasque
 			int count = 0;
 			var model = application.Backend.Tasks;
 			count = model.Count (t => t != null &&
-			                     (t.State == TaskState.Active || t.State == TaskState.Inactive) &&
+			                     t.State == TaskState.Active &&
 			                     category.ContainsTask (t));
 			return count;
 		}
diff --git a/src/Gtk.Tasque/TimerColumn.cs b/src/Gtk.Tasque/TimerColumn.cs
new file mode 100644
index 0000000..d8359e0
--- /dev/null
+++ b/src/Gtk.Tasque/TimerColumn.cs
@@ -0,0 +1,317 @@
+//
+// TimerColumn.cs
+//
+// Author:
+//       Antonius Riha <antoniusriha gmail com>
+//
+// Copyright (c) 2013 Antonius Riha
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+using System.Collections.Concurrent;
+using System.Timers;
+using Mono.Unix;
+using Tasque;
+using Gdk;
+
+namespace Gtk.Tasque
+{
+	public class TimerColumn : TreeViewColumn
+	{
+		public TimerColumn (IPreferences preferences, TreeModel model)
+		{
+			if (model == null)
+				throw new ArgumentNullException ("model");
+			this.model = model;
+			if (preferences == null)
+				throw new ArgumentNullException ("preferences");
+			this.preferences = preferences;
+
+			timeoutTargets = new ConcurrentDictionary<ITask, TaskTimer> ();
+
+			Title = Catalog.GetString ("Timer");
+			Sizing = TreeViewColumnSizing.Fixed;
+			FixedWidth = 20;
+			Resizable = false;
+			
+			var renderer = new CellRendererPixbuf {	Xalign = 0.5f };
+			PackStart (renderer, false);
+			SetCellDataFunc (renderer, TaskTimerCellDataFunc);
+		}
+
+		public void StartTimer (ITask task)
+		{
+			if (task == null)
+				throw new ArgumentNullException ("task");
+
+			var timeout = preferences.GetInt (PreferencesKeys.InactivateTimeoutKey);
+			TreeIter treeIter;
+			var iterFound = false;
+			model.Foreach ((treeModel, treePath, iter) => {
+				if (treeModel.GetValue (iter, 0) == task) {
+					treeIter = iter;
+					iterFound = true;
+					return true;
+				}
+				return false;
+			});
+
+			var timer = new TaskTimer (timeout, treeIter, model);
+			// if no iter found for task or this task exists in the timer dictinary, return
+			// silently (this is a concurrency sensitive area, hence we shouldn't be to
+			// strict if it is called multiple times)
+			if (!iterFound || !timeoutTargets.TryAdd (task, timer))
+				return;
+
+			timer.TimerStopped += (sender, e) => {
+				TaskTimer tmr;
+				timeoutTargets.TryRemove (e.Task, out tmr);
+				if (TimerExpired != null)
+					TimerExpired (this, e);
+			};
+
+			timer.Tick += (sender, e) => {
+				if (Tick != null)
+					Tick (this, e);
+			};
+
+			timer.Start ();
+		}
+
+		public void PauseTimer (ITask task)
+		{
+			if (task == null)
+				return;
+			TaskTimer timer;
+			if (timeoutTargets.TryGetValue (task, out timer))
+				timer.Pause ();
+		}
+
+		public void ResumeTimer (ITask task)
+		{
+			if (task == null)
+				return;
+			TaskTimer timer;
+			if (timeoutTargets.TryGetValue (task, out timer))
+				timer.Resume ();
+		}
+
+		public TaskTimerState? GetTimerState (ITask task)
+		{
+			if (task == null)
+				throw new ArgumentNullException ("task");
+			TaskTimer timer;
+			if (!timeoutTargets.TryGetValue (task, out timer))
+				return null;
+			return timer.State;
+		}
+
+		public void CancelTimer (ITask task)
+		{
+			if (task == null)
+				return;
+			TaskTimer timer;
+			if (timeoutTargets.TryRemove (task, out timer))
+				timer.Cancel ();
+		}
+
+		public event EventHandler<TickEventArgs> Tick;
+
+		public event EventHandler<TimerExpiredEventArgs> TimerExpired;
+
+		void TaskTimerCellDataFunc (TreeViewColumn treeColumn, CellRenderer cell,
+		                            TreeModel treeModel, TreeIter iter)
+		{
+			var task = treeModel.GetValue (iter, 0) as ITask;
+			TaskTimer timer;
+			var crp = cell as CellRendererPixbuf;
+			if (task == null || !timeoutTargets.TryGetValue (task, out timer)) {
+				crp.Pixbuf = null;
+				return;
+			}
+
+			crp.Pixbuf = timer.CurrentAnimPixbuf;
+		}
+
+		ConcurrentDictionary<ITask, TaskTimer> timeoutTargets;
+		IPreferences preferences;
+		TreeModel model;
+
+		public class TimerExpiredEventArgs : EventArgs
+		{
+			public TimerExpiredEventArgs (ITask task, bool canceled)
+			{
+				if (task == null)
+					throw new ArgumentNullException ("task");
+				Task = task;
+				Canceled = canceled;
+			}
+
+			public bool Canceled { get; private set; }
+
+			public ITask Task { get; private set; }
+		}
+
+		public class TickEventArgs : EventArgs
+		{
+			public TickEventArgs (int tick, ITask task)
+			{
+				if (task == null)
+					throw new ArgumentNullException ("task");
+				Task = task;
+				CountdownTick = tick;
+			}
+
+			public int CountdownTick { get; private set; }
+
+			public ITask Task { get; private set; }
+		}
+
+		public enum TaskTimerState
+		{
+			NotStarted,
+			Running,
+			Paused,
+			Stopped
+		}
+
+		class TaskTimer
+		{
+			static TaskTimer ()
+			{
+				inactiveAnimPixbufs = new Pixbuf [12];
+				for (int i = 0; i < 12; i++) {
+					var iconName = string.Format ("tasque-completing-{0}", i);
+					inactiveAnimPixbufs [i] = Utilities.GetIcon (iconName, 16);
+				}
+			}
+			
+			static Pixbuf [] inactiveAnimPixbufs;
+
+			public TaskTimer (int timeout, TreeIter iter, TreeModel model)
+			{
+				if (model == null)
+					throw new ArgumentNullException ("model");
+				if (timeout < 0)
+					timeout = 5;
+
+				CurrentAnimPixbuf = inactiveAnimPixbufs [0];
+
+				long lngTimeout = timeout * 1000;
+				var interval = lngTimeout / (double)inactiveAnimPixbufs.Length;
+
+				this.model = model;
+				this.iter = iter;
+
+				timer = new Timer (interval);
+				timer.Elapsed += delegate {
+					try {
+						CurrentAnimPixbuf = inactiveAnimPixbufs [++i];
+						NotifyChange ();
+					} catch (IndexOutOfRangeException) {
+						StopTimer (false);
+					}
+				};
+
+				countdown = timeout;
+				sTimer = new Timer (1000);
+				sTimer.Elapsed += delegate {
+					if (countdown == 0) {
+						sTimer.Dispose ();
+						return;
+					}
+
+					var task = model.GetValue (iter, 0) as ITask;
+					if (task == null)
+						return;
+
+					if (Tick != null)
+						Tick (this, new TickEventArgs (--countdown, task));
+				};
+			}
+
+			public Pixbuf CurrentAnimPixbuf { get; private set; }
+
+			public TaskTimerState State { get; private set; }
+
+			public void Start ()
+			{
+				timer.Start ();
+				sTimer.Start ();
+				State = TaskTimerState.Running;
+				NotifyChange ();
+			}
+
+			public void Pause ()
+			{
+				timer.Stop ();
+				sTimer.Stop ();
+				State = TaskTimerState.Paused;
+				NotifyChange ();
+			}
+
+			public void Resume ()
+			{
+				timer.Start ();
+				sTimer.Start ();
+				State = TaskTimerState.Running;
+				NotifyChange ();
+			}
+
+			public void Cancel ()
+			{
+				StopTimer (true);
+			}
+
+			public event EventHandler<TickEventArgs> Tick;
+
+			public event EventHandler<TimerExpiredEventArgs> TimerStopped;
+
+			void NotifyChange ()
+			{
+				var path = model.GetPath (iter);
+				model.EmitRowChanged (path, iter);
+			}
+
+			void StopTimer (bool canceled)
+			{
+				timer.Dispose ();
+				sTimer.Dispose ();
+				State = TaskTimerState.Stopped;
+				CurrentAnimPixbuf = null;
+
+				var task = model.GetValue (iter, 0) as ITask;
+				if (task == null)
+					return;
+
+				NotifyChange ();
+
+				if (TimerStopped != null)
+					TimerStopped (this, new TimerExpiredEventArgs (task, canceled));
+			}
+
+			int i;
+			int countdown;
+			TreeModel model;
+			TreeIter iter;
+			Timer timer;
+			Timer sTimer;
+		}
+	}
+}
diff --git a/src/Gtk.Tasque/TimerTaskField.cs b/src/Gtk.Tasque/TimerTaskField.cs
new file mode 100644
index 0000000..62066d4
--- /dev/null
+++ b/src/Gtk.Tasque/TimerTaskField.cs
@@ -0,0 +1,44 @@
+//
+// TimerTaskField.cs
+//
+// Author:
+//       Antonius Riha <antoniusriha gmail com>
+//
+// Copyright (c) 2013 Antonius Riha
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+using Tasque;
+
+namespace Gtk.Tasque
+{
+	public class TimerTaskField : ITaskField
+	{
+		public TimerTaskField ()
+		{
+		}
+
+		public string HeaderText { get; set; }
+
+		public void Initialize (ITask task)
+		{
+			throw new NotImplementedException ();
+		}
+	}
+}
diff --git a/src/libtasque/AbstractTask.cs b/src/libtasque/AbstractTask.cs
index 74d2b09..fca91d4 100644
--- a/src/libtasque/AbstractTask.cs
+++ b/src/libtasque/AbstractTask.cs
@@ -86,7 +86,6 @@ namespace Tasque
 		#region Methods
 		
 		public abstract void Activate ();
-		public abstract void Inactivate ();
 		public abstract void Complete ();
 		public abstract void Delete ();
 		public abstract INote CreateNote(string text);
diff --git a/src/libtasque/ITask.cs b/src/libtasque/ITask.cs
index f5a4446..fd2e593 100644
--- a/src/libtasque/ITask.cs
+++ b/src/libtasque/ITask.cs
@@ -122,16 +122,11 @@ namespace Tasque
 		#region Methods
 		
 		/// <summary>
-		/// Activate (Reopen) a task that's Inactivated or Completed.
+		/// Activate (Reopen) a task that's Completed.
 		/// </summary>
 		void Activate ();
 		
 		/// <summary>
-		/// Inactivate a task (this is the "limbo" mode).
-		/// </summary>
-		void Inactivate ();
-		
-		/// <summary>
 		/// Mark a task as completed.
 		/// </summary>
 		void Complete ();
diff --git a/src/libtasque/TaskGroupModel.cs b/src/libtasque/TaskGroupModel.cs
index 51f0ed7..770ce7e 100644
--- a/src/libtasque/TaskGroupModel.cs
+++ b/src/libtasque/TaskGroupModel.cs
@@ -335,11 +335,6 @@ namespace Tasque
 				throw new NotSupportedException ();
 			}
 			
-			void ITask.Inactivate ()
-			{
-				throw new NotSupportedException ();
-			}
-			
 			void ITask.Complete ()
 			{
 				throw new NotSupportedException ();
diff --git a/src/libtasque/TaskState.cs b/src/libtasque/TaskState.cs
index 41ffa11..c7cdb11 100644
--- a/src/libtasque/TaskState.cs
+++ b/src/libtasque/TaskState.cs
@@ -13,13 +13,6 @@ namespace Tasque
 		Active,
 		
 		/// <summary>
-		/// A task that's in limbo...the user has clicked that it should be
-		/// completed, but we're delaying so the user can get a visual of what's
-		/// gonna happen.  This feature ROCKS!
-		/// </summary>
-		Inactive,
-		
-		/// <summary>
 		/// A completed task.
 		/// </summary>
 		Completed,
diff --git a/src/tasque/Program.cs b/src/tasque/Program.cs
index 4041152..8b98e39 100644
--- a/src/tasque/Program.cs
+++ b/src/tasque/Program.cs
@@ -27,6 +27,7 @@ using System;
 using System.Runtime.InteropServices;
 using System.Text;
 using Mono.Unix.Native;
+using Gtk.Tasque;
 
 namespace Tasque
 {



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