[tasque] Make libtasque Gtk# agnostic.
- From: Antonius Riha <antoniusri src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [tasque] Make libtasque Gtk# agnostic.
- Date: Sat, 19 Jan 2013 20:26:24 +0000 (UTC)
commit 8d085917a13c3dd68e9d876d9785bb2b3606616d
Author: Antonius Riha <antoniusriha gmail com>
Date: Sun Dec 2 23:41:46 2012 +0100
Make libtasque Gtk# agnostic.
src/Addins/Backends/Dummy/DummyBackend.cs | 201 ++---
src/Addins/Backends/Dummy/DummyBackend.csproj | 5 +-
src/Addins/Backends/Dummy/DummyTask.cs | 27 +-
.../Backends/Rtm/Gtk/RtmPreferencesWidget.cs | 33 +-
src/Addins/Backends/Rtm/RtmBackend.cs | 825 ++++++++------------
src/Addins/Backends/Rtm/RtmBackend.csproj | 5 +-
src/Addins/Backends/Rtm/RtmCategory.cs | 11 +-
src/Addins/Backends/Rtm/RtmNote.cs | 2 +-
src/Addins/Backends/Rtm/RtmTask.cs | 36 +-
src/Addins/Backends/Sqlite/SqliteBackend.cs | 193 ++---
src/Addins/Backends/Sqlite/SqliteBackend.csproj | 5 +-
src/Addins/Backends/Sqlite/SqliteTask.cs | 29 +-
src/Gtk.Tasque/Application.cs | 527 +++++++++++++
src/Gtk.Tasque/CompletedTaskGroup.cs | 84 ++-
src/Gtk.Tasque/Gtk.Tasque.csproj | 4 +-
src/Gtk.Tasque/GtkApplicationBase.cs | 16 +-
src/Gtk.Tasque/GtkTray.cs | 7 +-
src/Gtk.Tasque/PreferencesDialog.cs | 31 +-
src/Gtk.Tasque/RemoteControl.cs | 90 +--
src/Gtk.Tasque/TaskGroup.cs | 69 +-
src/Gtk.Tasque/TaskWindow.cs | 69 +-
src/Gtk.Tasque/TreeModelListAdapter.cs | 105 +++
src/{Gtk.Tasque => libtasque}/AbstractTask.cs | 17 +
src/{Gtk.Tasque => libtasque}/AllCategory.cs | 1 +
src/libtasque/CategoryComparer.cs | 45 ++
src/libtasque/CompletedTaskGroupModel.cs | 10 +-
src/libtasque/IBackend.cs | 5 +-
src/libtasque/ITask.cs | 3 +-
src/libtasque/TaskComparer.cs | 40 +
src/libtasque/TaskGroupModel.cs | 305 +++++++-
src/libtasque/TaskGroupModelFactory.cs | 15 +-
src/libtasque/libtasque.csproj | 11 +-
32 files changed, 1751 insertions(+), 1075 deletions(-)
---
diff --git a/src/Addins/Backends/Dummy/DummyBackend.cs b/src/Addins/Backends/Dummy/DummyBackend.cs
index 8c83477..222aa1f 100644
--- a/src/Addins/Backends/Dummy/DummyBackend.cs
+++ b/src/Addins/Backends/Dummy/DummyBackend.cs
@@ -3,31 +3,29 @@
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
using Mono.Unix;
using Tasque.Backends;
using Gtk.Tasque.Backends.Dummy;
+using System.ComponentModel;
namespace Tasque.Backends.Dummy
{
public class DummyBackend : IBackend
{
- /// <summary>
- /// Keep track of the Gtk.TreeIters for the tasks so that they can
- /// be referenced later.
- ///
- /// Key = Task ID
- /// Value = Gtk.TreeIter in taskStore
- /// </summary>
- private Dictionary<int, Gtk.TreeIter> taskIters;
private int newTaskId;
- private Gtk.TreeStore taskStore;
- private Gtk.TreeModelSort sortedTasksModel;
private bool initialized;
private bool configured = true;
- private Gtk.ListStore categoryListStore;
- private Gtk.TreeModelSort sortedCategoriesModel;
-
+ ObservableCollection<ITask> taskStore;
+ ObservableCollection<ICategory> categoryListStore;
+ ReadOnlyObservableCollection<ITask> readOnlyTaskStore;
+ ReadOnlyObservableCollection<ICategory> readOnlyCategoryStore;
+
+ TaskComparer taskComparer;
+ CategoryComparer categoryComparer;
+
public event BackendInitializedHandler BackendInitialized;
public event BackendSyncStartedHandler BackendSyncStarted;
public event BackendSyncFinishedHandler BackendSyncFinished;
@@ -40,18 +38,13 @@ namespace Tasque.Backends.Dummy
{
initialized = false;
newTaskId = 0;
- taskIters = new Dictionary<int, Gtk.TreeIter> ();
- taskStore = new Gtk.TreeStore (typeof (ITask));
-
- sortedTasksModel = new Gtk.TreeModelSort (taskStore);
- sortedTasksModel.SetSortFunc (0, new Gtk.TreeIterCompareFunc (CompareTasksSortFunc));
- sortedTasksModel.SetSortColumnId (0, Gtk.SortType.Ascending);
-
- categoryListStore = new Gtk.ListStore (typeof (ICategory));
-
- sortedCategoriesModel = new Gtk.TreeModelSort (categoryListStore);
- sortedCategoriesModel.SetSortFunc (0, new Gtk.TreeIterCompareFunc (CompareCategorySortFunc));
- sortedCategoriesModel.SetSortColumnId (0, Gtk.SortType.Ascending);
+ taskStore = new ObservableCollection<ITask> ();
+ categoryListStore = new ObservableCollection<ICategory> ();
+ readOnlyTaskStore = new ReadOnlyObservableCollection<ITask> (taskStore);
+ readOnlyCategoryStore
+ = new ReadOnlyObservableCollection<ICategory> (categoryListStore);
+ taskComparer = new TaskComparer ();
+ categoryComparer = new CategoryComparer ();
}
#region Public Properties
@@ -63,17 +56,17 @@ namespace Tasque.Backends.Dummy
/// <value>
/// All the tasks including ITaskDivider items.
/// </value>
- public Gtk.TreeModel Tasks
+ public ICollection<ITask> Tasks
{
- get { return sortedTasksModel; }
+ get { return readOnlyTaskStore; }
}
/// <value>
/// This returns all the task lists (categories) that exist.
/// </value>
- public Gtk.TreeModel Categories
+ public ICollection<ICategory> Categories
{
- get { return sortedCategoriesModel; }
+ get { return readOnlyCategoryStore; }
}
/// <value>
@@ -93,7 +86,7 @@ namespace Tasque.Backends.Dummy
}
#endregion // Public Properties
- #region Public Methods
+ #region Public Methodsopen source contributors badges
public ITask CreateTask (string taskName, ICategory category)
{
// not sure what to do here with the category
@@ -105,11 +98,11 @@ namespace Tasque.Backends.Dummy
else
task.Category = category;
- Gtk.TreeIter iter = taskStore.AppendNode ();
- taskStore.SetValue (iter, 0, task);
- taskIters [newTaskId] = iter;
+ AddTask (task);
newTaskId++;
+ task.PropertyChanged += HandlePropertyChanged;
+
return task;
}
@@ -118,35 +111,28 @@ namespace Tasque.Backends.Dummy
public void Refresh()
{}
-
+
public void Initialize (Preferences preferences)
{
if (preferences == null)
throw new ArgumentNullException ("preferences");
- Gtk.TreeIter iter;
-
//
// Add in the "All" Category
//
- AllCategory allCategory = new Tasque.AllCategory (preferences);
- iter = categoryListStore.Append ();
- categoryListStore.SetValue (iter, 0, allCategory);
+ AddCategory (new AllCategory (preferences));
//
// Add in some fake categories
//
homeCategory = new DummyCategory ("Home");
- iter = categoryListStore.Append ();
- categoryListStore.SetValue (iter, 0, homeCategory);
+ AddCategory (homeCategory);
workCategory = new DummyCategory ("Work");
- iter = categoryListStore.Append ();
- categoryListStore.SetValue (iter, 0, workCategory);
+ AddCategory (workCategory);
projectsCategory = new DummyCategory ("Projects");
- iter = categoryListStore.Append ();
- categoryListStore.SetValue (iter, 0, projectsCategory);
+ AddCategory (projectsCategory);
//
// Add in some fake tasks
@@ -156,9 +142,8 @@ namespace Tasque.Backends.Dummy
task.Category = projectsCategory;
task.DueDate = DateTime.Now.AddDays (1);
task.Priority = TaskPriority.Medium;
- iter = taskStore.AppendNode ();
- taskStore.SetValue (iter, 0, task);
- taskIters [newTaskId] = iter;
+ AddTask (task);
+ task.PropertyChanged += HandlePropertyChanged;
newTaskId++;
task = new DummyTask (this, newTaskId, "Call Roger");
@@ -166,79 +151,70 @@ namespace Tasque.Backends.Dummy
task.DueDate = DateTime.Now.AddDays (-1);
task.Complete ();
task.CompletionDate = task.DueDate;
- iter = taskStore.AppendNode ();
- taskStore.SetValue (iter, 0, task);
- taskIters [newTaskId] = iter;
+ AddTask (task);
+ task.PropertyChanged += HandlePropertyChanged;
newTaskId++;
task = new DummyTask (this, newTaskId, "Replace burnt out lightbulb");
task.Category = homeCategory;
task.DueDate = DateTime.Now;
task.Priority = TaskPriority.Low;
- iter = taskStore.AppendNode ();
- taskStore.SetValue (iter, 0, task);
- taskIters [newTaskId] = iter;
+ AddTask (task);
+ task.PropertyChanged += HandlePropertyChanged;
newTaskId++;
task = new DummyTask (this, newTaskId, "File taxes");
task.Category = homeCategory;
task.DueDate = new DateTime (2008, 4, 1);
- iter = taskStore.AppendNode ();
- taskStore.SetValue (iter, 0, task);
- taskIters [newTaskId] = iter;
+ AddTask (task);
+ task.PropertyChanged += HandlePropertyChanged;
newTaskId++;
task = new DummyTask (this, newTaskId, "Purchase lumber");
task.Category = projectsCategory;
task.DueDate = DateTime.Now.AddDays (1);
task.Priority = TaskPriority.High;
- iter = taskStore.AppendNode ();
- taskStore.SetValue (iter, 0, task);
- taskIters [newTaskId] = iter;
+ AddTask (task);
+ task.PropertyChanged += HandlePropertyChanged;
newTaskId++;
task = new DummyTask (this, newTaskId, "Estimate drywall requirements");
task.Category = projectsCategory;
task.DueDate = DateTime.Now.AddDays (1);
task.Priority = TaskPriority.Low;
- iter = taskStore.AppendNode ();
- taskStore.SetValue (iter, 0, task);
- taskIters [newTaskId] = iter;
+ AddTask (task);
+ task.PropertyChanged += HandlePropertyChanged;
newTaskId++;
task = new DummyTask (this, newTaskId, "Borrow framing nailer from Ben");
task.Category = projectsCategory;
task.DueDate = DateTime.Now.AddDays (1);
task.Priority = TaskPriority.High;
- iter = taskStore.AppendNode ();
- taskStore.SetValue (iter, 0, task);
- taskIters [newTaskId] = iter;
+ AddTask (task);
+ task.PropertyChanged += HandlePropertyChanged;
newTaskId++;
task = new DummyTask (this, newTaskId, "Call for an insulation estimate");
task.Category = projectsCategory;
task.DueDate = DateTime.Now.AddDays (1);
task.Priority = TaskPriority.Medium;
- iter = taskStore.AppendNode ();
- taskStore.SetValue (iter, 0, task);
- taskIters [newTaskId] = iter;
+ AddTask (task);
+ task.PropertyChanged += HandlePropertyChanged;
newTaskId++;
task = new DummyTask (this, newTaskId, "Pay storage rental fee");
task.Category = homeCategory;
task.DueDate = DateTime.Now.AddDays (1);
task.Priority = TaskPriority.None;
- iter = taskStore.AppendNode ();
- taskStore.SetValue (iter, 0, task);
- taskIters [newTaskId] = iter;
+ AddTask (task);
+ task.PropertyChanged += HandlePropertyChanged;
newTaskId++;
task = new DummyTask (this, newTaskId, "Place carpet order");
task.Category = projectsCategory;
task.Priority = TaskPriority.None;
- iter = taskStore.AppendNode ();
- taskStore.SetValue (iter, 0, task);
- taskIters [newTaskId] = iter;
+ AddTask (task);
+ task.PropertyChanged += HandlePropertyChanged;
newTaskId++;
task = new DummyTask (this, newTaskId, "Test task overdue");
@@ -246,9 +222,8 @@ namespace Tasque.Backends.Dummy
task.DueDate = DateTime.Now.AddDays (-89);
task.Priority = TaskPriority.None;
task.Complete ();
- iter = taskStore.AppendNode ();
- taskStore.SetValue (iter, 0, task);
- taskIters [newTaskId] = iter;
+ AddTask (task);
+ task.PropertyChanged += HandlePropertyChanged;
newTaskId++;
initialized = true;
@@ -271,59 +246,39 @@ namespace Tasque.Backends.Dummy
#endregion // Public Methods
#region Private Methods
- static int CompareTasksSortFunc (Gtk.TreeModel model,
- Gtk.TreeIter a,
- Gtk.TreeIter b)
+ internal void DeleteTask (DummyTask task)
{
- ITask taskA = model.GetValue (a, 0) as ITask;
- ITask taskB = model.GetValue (b, 0) as ITask;
-
- if (taskA == null || taskB == null)
- return 0;
-
- return (taskA.CompareTo (taskB));
+ if (taskStore.Remove (task))
+ task.PropertyChanged -= HandlePropertyChanged;
}
- static int CompareCategorySortFunc (Gtk.TreeModel model,
- Gtk.TreeIter a,
- Gtk.TreeIter b)
+ void AddCategory (ICategory category)
{
- ICategory categoryA = model.GetValue (a, 0) as ICategory;
- ICategory categoryB = model.GetValue (b, 0) as ICategory;
-
- if (categoryA == null || categoryB == null)
- return 0;
-
- if (categoryA is Tasque.AllCategory)
- return -1;
- else if (categoryB is Tasque.AllCategory)
- return 1;
-
- return (categoryA.Name.CompareTo (categoryB.Name));
+ var index = categoryListStore.Count;
+ var valIdx = categoryListStore.Select ((val, idx) => new { val, idx })
+ .FirstOrDefault (x => categoryComparer.Compare (x.val, category) > 0);
+ if (valIdx != null)
+ index = valIdx.idx;
+ categoryListStore.Insert (index, category);
}
- public void UpdateTask (DummyTask task)
+ void AddTask (DummyTask task)
{
- // Set the task in the store so the model will update the UI.
- Gtk.TreeIter iter;
+ var index = taskStore.Count;
+ var valIdx = taskStore.Select ((val, idx) => new { val, idx })
+ .FirstOrDefault (t => taskComparer.Compare (t.val, task) > 0);
+ if (valIdx != null)
+ index = valIdx.idx;
- if (!taskIters.ContainsKey (task.DummyId))
- return;
-
- iter = taskIters [task.DummyId];
-
- if (task.State == TaskState.Deleted) {
- taskIters.Remove (task.DummyId);
- if (!taskStore.Remove (ref iter)) {
- Logger.Debug ("Successfully deleted from taskStore: {0}",
- task.Name);
- } else {
- Logger.Debug ("Problem removing from taskStore: {0}",
- task.Name);
- }
- } else {
- taskStore.SetValue (iter, 0, task);
- }
+ taskStore.Insert (index, task);
+ }
+
+ void HandlePropertyChanged (object sender, PropertyChangedEventArgs e)
+ {
+ // when a property changes (any property atm), "reorder" tasks
+ var task = (DummyTask)sender;
+ if (taskStore.Remove (task))
+ AddTask (task);
}
#endregion // Private Methods
diff --git a/src/Addins/Backends/Dummy/DummyBackend.csproj b/src/Addins/Backends/Dummy/DummyBackend.csproj
index 5000d1e..ee106b1 100644
--- a/src/Addins/Backends/Dummy/DummyBackend.csproj
+++ b/src/Addins/Backends/Dummy/DummyBackend.csproj
@@ -43,6 +43,7 @@
<Reference Include="atk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f">
<Private>False</Private>
</Reference>
+ <Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<Compile Include="DummyTask.cs" />
@@ -56,10 +57,6 @@
<Compile Include="Gtk\DummyPreferences.cs" />
</ItemGroup>
<ItemGroup>
- <ProjectReference Include="..\..\..\Gtk.Tasque\Gtk.Tasque.csproj">
- <Project>{B19B9840-669D-4984-9772-E1F55193A67F}</Project>
- <Name>Gtk.Tasque</Name>
- </ProjectReference>
<ProjectReference Include="..\..\..\libtasque\libtasque.csproj">
<Project>{784C9AA8-2B28-400B-8CC4-DCDC48CA37F0}</Project>
<Name>libtasque</Name>
diff --git a/src/Addins/Backends/Dummy/DummyTask.cs b/src/Addins/Backends/Dummy/DummyTask.cs
index e8e86e3..5d51199 100644
--- a/src/Addins/Backends/Dummy/DummyTask.cs
+++ b/src/Addins/Backends/Dummy/DummyTask.cs
@@ -48,12 +48,12 @@ namespace Tasque.Backends.Dummy
get { return name; }
set {
Logger.Debug ("Setting new task name");
+ OnPropertyChanging ("Name");
if (value == null)
name = string.Empty;
name = value.Trim ();
-
- backend.UpdateTask (this);
+ OnPropertyChanged ("Name");
}
}
@@ -62,9 +62,9 @@ Logger.Debug ("Setting new task name");
get { return dueDate; }
set {
Logger.Debug ("Setting new task due date");
+ OnPropertyChanging ("DueDate");
dueDate = value;
-
- backend.UpdateTask (this);
+ OnPropertyChanged ("DueDate");
}
}
@@ -73,9 +73,9 @@ Logger.Debug ("Setting new task due date");
get { return completionDate; }
set {
Logger.Debug ("Setting new task completion date");
+ OnPropertyChanging ("CompletionDate");
completionDate = value;
-
- backend.UpdateTask (this);
+ OnPropertyChanged ("CompletionDate");
}
}
@@ -89,9 +89,9 @@ Logger.Debug ("Setting new task completion date");
get { return priority; }
set {
Logger.Debug ("Setting new task priority");
+ OnPropertyChanging ("Priority");
priority = value;
-
- backend.UpdateTask (this);
+ OnPropertyChanged ("Priority");
}
}
@@ -129,32 +129,29 @@ Logger.Debug ("Setting new task priority");
public override void Activate ()
{
Logger.Debug ("DummyTask.Activate ()");
- completionDate = DateTime.MinValue;
state = TaskState.Active;
- backend.UpdateTask (this);
+ CompletionDate = DateTime.MinValue;
}
public override void Inactivate ()
{
Logger.Debug ("DummyTask.Inactivate ()");
- completionDate = DateTime.Now;
state = TaskState.Inactive;
- backend.UpdateTask (this);
+ CompletionDate = DateTime.Now;
}
public override void Complete ()
{
Logger.Debug ("DummyTask.Complete ()");
- CompletionDate = DateTime.Now;
state = TaskState.Completed;
- backend.UpdateTask (this);
+ CompletionDate = DateTime.Now;
}
public override void Delete ()
{
Logger.Debug ("DummyTask.Delete ()");
state = TaskState.Deleted;
- backend.UpdateTask (this);
+ backend.DeleteTask (this);
}
public override INote CreateNote(string text)
diff --git a/src/Addins/Backends/Rtm/Gtk/RtmPreferencesWidget.cs b/src/Addins/Backends/Rtm/Gtk/RtmPreferencesWidget.cs
index da9912c..528dc72 100644
--- a/src/Addins/Backends/Rtm/Gtk/RtmPreferencesWidget.cs
+++ b/src/Addins/Backends/Rtm/Gtk/RtmPreferencesWidget.cs
@@ -1,12 +1,14 @@
// RtmPreferencesWidget.cs created with MonoDevelop
// User: boyd at 11:29 PMÂ2/18/2008
-
using System;
using System.Diagnostics;
-using Gtk;
using Mono.Unix;
+using Gdk;
+using Gtk;
+using Tasque;
+using Tasque.Backends;
-namespace Tasque.Backends.RtmBackend
+namespace Tasque.Backends.Rtm
{
public class RtmPreferencesWidget : Gtk.EventBox, IBackendPreferences
{
@@ -24,7 +26,7 @@ namespace Tasque.Backends.RtmBackend
static RtmPreferencesWidget ()
{
- normalPixbuf = Utilities.GetIcon ("tasque-rtm-logo", 128);
+ normalPixbuf = GetIcon ("tasque-rtm-logo", 128);
}
public RtmPreferencesWidget (RtmBackend backend, Preferences preferences) : base ()
@@ -156,5 +158,28 @@ namespace Tasque.Backends.RtmBackend
"\n" + userName.Trim ();
}
}
+
+ static Pixbuf GetIcon (string iconName, int size)
+ {
+ try {
+ return IconTheme.Default.LoadIcon (iconName, size, 0);
+ } catch (GLib.GException) {}
+
+ try {
+ var ret = new Pixbuf (null, iconName + ".png");
+ return ret.ScaleSimple (size, size, InterpType.Bilinear);
+ } catch (ArgumentException) {}
+
+ // TODO: This is a temporary fix to allow installing all icons as assembly
+ // resources. The proper thing to do is to ship the actual icons,
+ // and append to the default gtk+ icon theme path. See Tomboy.
+ try {
+ var ret = new Pixbuf (null, string.Format ("{0}-{1}.png", iconName, size));
+ return ret.ScaleSimple (size, size, InterpType.Bilinear);
+ } catch (ArgumentException) {}
+
+ Logger.Debug ("Unable to load icon '{0}'.", iconName);
+ return null;
+ }
}
}
diff --git a/src/Addins/Backends/Rtm/RtmBackend.cs b/src/Addins/Backends/Rtm/RtmBackend.cs
index 380a7c8..79bd449 100644
--- a/src/Addins/Backends/Rtm/RtmBackend.cs
+++ b/src/Addins/Backends/Rtm/RtmBackend.cs
@@ -1,197 +1,123 @@
-// RtmBackend.cs created with MonoDevelop
-// User: boyd at 7:10 AMÂ2/11/2008
//
-// To change standard headers go to Edit->Preferences->Coding->Standard Headers
+// RtmBackend.cs
//
-
+// Author:
+// Antonius Riha <antoniusriha gmail com>
+//
+// Copyright (c) 2012 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 Mono.Unix;
-using Tasque.Backends;
-using RtmNet;
-using System.Threading;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
-namespace Tasque.Backends.RtmBackend
+namespace Tasque.Backends.Rtm
{
public class RtmBackend : IBackend
{
- Preferences preferences;
-
- private const string apiKey = "b29f7517b6584035d07df3170b80c430";
- private const string sharedSecret = "93eb5f83628b2066";
- private Gtk.TreeStore taskStore;
- private Gtk.TreeModelSort sortedTasksModel;
-
- private Gtk.ListStore categoryListStore;
- private Gtk.TreeModelSort sortedCategoriesModel;
-
- private Thread refreshThread;
- private bool runningRefreshThread;
- private AutoResetEvent runRefreshEvent;
-
- private Rtm rtm;
- private string frob;
- private Auth rtmAuth;
- private string timeline;
-
- private Dictionary<string, Gtk.TreeIter> taskIters;
- private object taskLock;
-
- private Dictionary<string, RtmCategory> categories;
- private object catLock;
- private bool initialized;
- private bool configured;
-
- public event BackendInitializedHandler BackendInitialized;
- public event BackendSyncStartedHandler BackendSyncStarted;
- public event BackendSyncFinishedHandler BackendSyncFinished;
-
public RtmBackend ()
{
- initialized = false;
- configured = false;
-
- taskIters = new Dictionary<string, Gtk.TreeIter> ();
- taskLock = new Object();
-
- categories = new Dictionary<string, RtmCategory> ();
- catLock = new Object();
-
- // *************************************
- // Data Model Set up
- // *************************************
- taskStore = new Gtk.TreeStore (typeof (ITask));
-
- sortedTasksModel = new Gtk.TreeModelSort (taskStore);
- sortedTasksModel.SetSortFunc (0, new Gtk.TreeIterCompareFunc (CompareTasksSortFunc));
- sortedTasksModel.SetSortColumnId (0, Gtk.SortType.Ascending);
+ taskComparer = new TaskComparer ();
+ categoryComparer = new CategoryComparer ();
- categoryListStore = new Gtk.ListStore (typeof (ICategory));
+ tasks = new ObservableCollection<ITask> ();
+ categories = new ObservableCollection<ICategory> ();
- sortedCategoriesModel = new Gtk.TreeModelSort (categoryListStore);
- sortedCategoriesModel.SetSortFunc (0, new Gtk.TreeIterCompareFunc (CompareCategorySortFunc));
- sortedCategoriesModel.SetSortColumnId (0, Gtk.SortType.Ascending);
+ Tasks = new ReadOnlyObservableCollection<ITask> (tasks);
+ Categories = new ReadOnlyObservableCollection<ICategory> (categories);
}
- #region Public Properties
- public string Name
- {
- get { return "Remember the Milk"; }
- }
+ public string Name { get { return "Remember the Milk"; } }
- /// <value>
- /// All the tasks including ITaskDivider items.
- /// </value>
- public Gtk.TreeModel Tasks
- {
- get { return sortedTasksModel; }
- }
-
- /// <value>
- /// This returns all the task lists (categories) that exist.
- /// </value>
- public Gtk.TreeModel Categories
- {
- get { return sortedCategoriesModel; }
- }
-
- public string RtmUserName
- {
- get {
- if( (rtmAuth != null) && (rtmAuth.User != null) ) {
- return rtmAuth.User.Username;
- } else
- return null;
- }
- }
+ public ICollection<ITask> Tasks { get; private set; }
- /// <value>
- /// Indication that the rtm backend is configured
- /// </value>
- public bool Configured
- {
- get { return configured; }
- }
+ public ICollection<ICategory> Categories { get; private set; }
- /// <value>
- /// Inidication that the backend is initialized
- /// </value>
- public bool Initialized
- {
- get { return initialized; }
- }
+ public bool Configured { get; private set; }
- public IBackendPreferences Preferences { get { return new RtmPreferencesWidget (this, preferences); } }
+ public bool Initialized { get; private set; }
-#endregion // Public Properties
+ public IBackendPreferences Preferences {
+ get { return new RtmPreferencesWidget (this, preferences); }
+ }
-#region Public Methods
public ITask CreateTask (string taskName, ICategory category)
{
- string categoryID;
RtmTask rtmTask = null;
-
- if(category is Tasque.AllCategory)
- categoryID = null;
- else
- categoryID = (category as RtmCategory).ID;
- if(rtm != null) {
+ string categoryID = null;
+ if (!(category is AllCategory))
+ categoryID = (category as RtmCategory).ID;
+
+ if (rtm != null) {
try {
- List list;
-
- if(categoryID == null)
- list = rtm.TasksAdd(timeline, taskName);
+ RtmNet.List list;
+ if (categoryID == null)
+ list = rtm.TasksAdd (timeline, taskName);
else
- list = rtm.TasksAdd(timeline, taskName, categoryID);
-
- rtmTask = UpdateTaskFromResult(list);
- } catch(Exception e) {
- Logger.Debug("Unable to set create task: " + taskName);
- Logger.Debug(e.ToString());
+ list = rtm.TasksAdd (timeline, taskName, categoryID);
+ rtmTask = UpdateTaskFromResult (list);
+ } catch (Exception e) {
+ Logger.Debug ("Unable to set create task: " + taskName);
+ Logger.Debug (e.ToString ());
}
- }
- else
- throw new Exception("Unable to communicate with Remember The Milk");
-
+ } else
+ throw new Exception ("Unable to communicate with Remember The Milk");
+
return rtmTask;
}
-
- public void DeleteTask(ITask task)
+
+ public void DeleteTask (ITask task)
{
- RtmTask rtmTask = task as RtmTask;
- if(rtm != null) {
+ var rtmTask = task as RtmTask;
+ if (rtm != null) {
try {
- rtm.TasksDelete(timeline, rtmTask.ListID, rtmTask.SeriesTaskID, rtmTask.TaskTaskID);
-
- lock(taskLock)
- {
- Gtk.Application.Invoke ( delegate {
- if(taskIters.ContainsKey(rtmTask.ID)) {
- Gtk.TreeIter iter = taskIters[rtmTask.ID];
- taskStore.Remove(ref iter);
- taskIters.Remove(rtmTask.ID);
- }
- });
- }
- } catch(Exception e) {
- Logger.Debug("Unable to delete task: " + task.Name);
- Logger.Debug(e.ToString());
+ rtm.TasksDelete (timeline, rtmTask.ListID,
+ rtmTask.SeriesTaskID, rtmTask.TaskTaskID);
+ rtmTask.Delete ();
+ } catch (Exception e) {
+ Logger.Debug ("Unable to delete task: " + task.Name);
+ Logger.Debug (e.ToString ());
}
- }
- else
- throw new Exception("Unable to communicate with Remember The Milk");
+ } else
+ throw new Exception ("Unable to communicate with Remember The Milk");
}
-
- public void Refresh()
+
+ public void Refresh ()
{
+ if (rtmAuth == null)
+ return;
+
Logger.Debug("Refreshing data...");
- if (!runningRefreshThread)
- StartThread();
+ if (BackendSyncStarted != null)
+ BackendSyncStarted ();
+
+ var lists = rtm.ListsGetList ();
+ UpdateCategories (lists);
+ UpdateTasks (lists);
+
+ if (BackendSyncFinished != null)
+ BackendSyncFinished ();
- runRefreshEvent.Set();
-
Logger.Debug("Done refreshing data!");
}
@@ -200,99 +126,80 @@ namespace Tasque.Backends.RtmBackend
if (preferences == null)
throw new ArgumentNullException ("preferences");
this.preferences = preferences;
-
- // make sure we have the all Category in our list
- Gtk.Application.Invoke ( delegate {
- AllCategory allCategory = new Tasque.AllCategory (preferences);
- Gtk.TreeIter iter = categoryListStore.Append ();
- categoryListStore.SetValue (iter, 0, allCategory);
- });
- runRefreshEvent = new AutoResetEvent(false);
+ // make sure we have the all Category in our list
+ AllCategory allCategory = new AllCategory (preferences);
+ AddCategory (allCategory);
- runningRefreshThread = false;
- refreshThread = new Thread(RefreshThreadLoop);
-
// *************************************
// AUTHENTICATION to Remember The Milk
// *************************************
string authToken = preferences.Get (Tasque.Preferences.AuthTokenKey);
- if (authToken != null ) {
- Logger.Debug("Found AuthToken, checking credentials...");
+ if (authToken != null) {
+ Logger.Debug ("Found AuthToken, checking credentials...");
try {
- rtm = new Rtm(apiKey, sharedSecret, authToken);
- rtmAuth = rtm.AuthCheckToken(authToken);
- timeline = rtm.TimelineCreate();
- Logger.Debug("RTM Auth Token is valid!");
- Logger.Debug("Setting configured status to true");
- configured = true;
+ rtm = new RtmNet.Rtm (apiKey, sharedSecret, authToken);
+ rtmAuth = rtm.AuthCheckToken (authToken);
+ timeline = rtm.TimelineCreate ();
+ Logger.Debug ("RTM Auth Token is valid!");
+ Logger.Debug ("Setting configured status to true");
+ Configured = true;
} catch (RtmNet.RtmApiException e) {
-
preferences.Set (Tasque.Preferences.AuthTokenKey, null);
preferences.Set (Tasque.Preferences.UserIdKey, null);
preferences.Set (Tasque.Preferences.UserNameKey, null);
rtm = null;
rtmAuth = null;
- Logger.Error("Exception authenticating, reverting" + e.Message);
- } catch (ResponseXmlException e) {
+ Logger.Error ("Exception authenticating, reverting" + e.Message);
+ } catch (RtmNet.ResponseXmlException e) {
rtm = null;
rtmAuth = null;
Logger.Error ("Cannot parse RTM response. Maybe the service is down."
- + e.Message);
+ + e.Message);
} catch (RtmNet.RtmWebException e) {
rtm = null;
rtmAuth = null;
- Logger.Error("Not connected to RTM, maybe proxy: #{0}", e.Message);
- }
- catch (System.Net.WebException e) {
+ Logger.Error ("Not connected to RTM, maybe proxy: #{0}", e.Message);
+ } catch (System.Net.WebException e) {
rtm = null;
rtmAuth = null;
- Logger.Error("Problem connecting to internet: #{0}", e.Message);
+ Logger.Error ("Problem connecting to internet: #{0}", e.Message);
}
}
+
+ if (rtm == null)
+ rtm = new RtmNet.Rtm (apiKey, sharedSecret);
- if(rtm == null)
- rtm = new Rtm(apiKey, sharedSecret);
-
- StartThread();
- }
+ Refresh ();
- public void StartThread()
- {
- if (!configured) {
- Logger.Debug("Backend not configured, not starting thread");
- return;
- }
- runningRefreshThread = true;
- Logger.Debug("ThreadState: " + refreshThread.ThreadState);
- if (refreshThread.ThreadState == ThreadState.Running) {
- Logger.Debug ("RtmBackend refreshThread already running");
- } else {
- if (!refreshThread.IsAlive) {
- refreshThread = new Thread(RefreshThreadLoop);
- }
- refreshThread.Start();
- }
- runRefreshEvent.Set();
+ Initialized = true;
+ if (BackendInitialized != null)
+ BackendInitialized ();
}
- public void Cleanup()
+ public void Cleanup ()
{
- runningRefreshThread = false;
- runRefreshEvent.Set();
- refreshThread.Abort ();
+ tasks.Clear ();
+ categories.Clear ();
+
+ rtm = null;
+ Initialized = false;
}
- public string GetAuthUrl()
+ public event BackendInitializedHandler BackendInitialized;
+ public event BackendSyncStartedHandler BackendSyncStarted;
+ public event BackendSyncFinishedHandler BackendSyncFinished;
+
+ #region Internals
+ internal void DeleteTask (RtmTask task)
{
- frob = rtm.AuthGetFrob();
- string url = rtm.AuthCalcUrl(frob, AuthLevel.Delete);
- return url;
+ if (tasks.Remove (task))
+ task.PropertyChanged -= HandlePropertyChanged;
}
- public void FinishedAuth()
+ internal void FinishedAuth ()
{
- rtmAuth = rtm.AuthGetToken(frob);
+ rtmAuth = rtm.AuthGetToken (frob);
if (rtmAuth != null) {
preferences.Set (Tasque.Preferences.AuthTokenKey, rtmAuth.Token);
if (rtmAuth.User != null) {
@@ -301,412 +208,300 @@ namespace Tasque.Backends.RtmBackend
}
}
- string authToken = preferences.Get (Tasque.Preferences.AuthTokenKey);
- if (authToken != null ) {
- Logger.Debug("Found AuthToken, checking credentials...");
+ var authToken = preferences.Get (Tasque.Preferences.AuthTokenKey);
+ if (authToken != null) {
+ Logger.Debug ("Found AuthToken, checking credentials...");
try {
- rtm = new Rtm(apiKey, sharedSecret, authToken);
- rtmAuth = rtm.AuthCheckToken(authToken);
- timeline = rtm.TimelineCreate();
- Logger.Debug("RTM Auth Token is valid!");
- Logger.Debug("Setting configured status to true");
- configured = true;
- Refresh();
+ rtm = new RtmNet.Rtm (apiKey, sharedSecret, authToken);
+ rtmAuth = rtm.AuthCheckToken (authToken);
+ timeline = rtm.TimelineCreate ();
+ Logger.Debug ("RTM Auth Token is valid!");
+ Logger.Debug ("Setting configured status to true");
+ Configured = true;
+ Refresh ();
} catch (Exception e) {
rtm = null;
rtmAuth = null;
- Logger.Error("Exception authenticating, reverting" + e.Message);
+ Logger.Error ("Exception authenticating, reverting" + e.Message);
}
}
}
- public void UpdateTaskName(RtmTask task)
+ internal string GetAuthUrl ()
{
- if(rtm != null) {
- try {
- List list = rtm.TasksSetName(timeline, task.ListID, task.SeriesTaskID, task.TaskTaskID, task.Name);
- UpdateTaskFromResult(list);
- } catch(Exception e) {
- Logger.Debug("Unable to set name on task: " + task.Name);
- Logger.Debug(e.ToString());
- }
+ frob = rtm.AuthGetFrob ();
+ string url = rtm.AuthCalcUrl (frob, RtmNet.AuthLevel.Delete);
+ return url;
+ }
+
+ internal RtmCategory GetCategory (string id)
+ {
+ foreach (var item in categories) {
+ var category = item as RtmCategory;
+ if (category != null && category.ID == id)
+ return category;
}
+ return null;
}
-
- public void UpdateTaskDueDate(RtmTask task)
+
+ internal void UpdateTaskName (RtmTask task)
{
- if(rtm != null) {
+ if (rtm != null) {
try {
- List list;
- if(task.DueDate == DateTime.MinValue)
- list = rtm.TasksSetDueDate(timeline, task.ListID, task.SeriesTaskID, task.TaskTaskID);
- else
- list = rtm.TasksSetDueDate(timeline, task.ListID, task.SeriesTaskID, task.TaskTaskID, task.DueDateString);
- UpdateTaskFromResult(list);
- } catch(Exception e) {
- Logger.Debug("Unable to set due date on task: " + task.Name);
- Logger.Debug(e.ToString());
+ RtmNet.List list = rtm.TasksSetName (timeline, task.ListID, task.SeriesTaskID, task.TaskTaskID, task.Name);
+ UpdateTaskFromResult (list);
+ } catch (Exception e) {
+ Logger.Debug ("Unable to set name on task: " + task.Name);
+ Logger.Debug (e.ToString ());
}
}
}
- public void UpdateTaskCompleteDate(RtmTask task)
+ internal void UpdateTaskDueDate (RtmTask task)
{
- UpdateTask(task);
+ if (rtm != null) {
+ try {
+ if (task.DueDate == DateTime.MinValue)
+ rtm.TasksSetDueDate (timeline, task.ListID, task.SeriesTaskID, task.TaskTaskID);
+ else
+ rtm.TasksSetDueDate (timeline, task.ListID, task.SeriesTaskID, task.TaskTaskID, task.DueDateString);
+ } catch (Exception e) {
+ Logger.Debug ("Unable to set due date on task: " + task.Name);
+ Logger.Debug (e.ToString ());
+ }
+ }
}
- public void UpdateTaskPriority(RtmTask task)
+ internal void UpdateTaskPriority (RtmTask task)
{
- if(rtm != null) {
+ if (rtm != null) {
try {
- List list = rtm.TasksSetPriority(timeline, task.ListID, task.SeriesTaskID, task.TaskTaskID, task.PriorityString);
- UpdateTaskFromResult(list);
- } catch(Exception e) {
- Logger.Debug("Unable to set priority on task: " + task.Name);
- Logger.Debug(e.ToString());
+ RtmNet.List list = rtm.TasksSetPriority (timeline, task.ListID, task.SeriesTaskID, task.TaskTaskID, task.PriorityString);
+ UpdateTaskFromResult (list);
+ } catch (Exception e) {
+ Logger.Debug ("Unable to set priority on task: " + task.Name);
+ Logger.Debug (e.ToString ());
}
}
}
- public void UpdateTaskActive(RtmTask task)
+ internal void UpdateTaskActive (RtmTask task)
{
- if(task.State == TaskState.Completed)
- {
- if(rtm != null) {
+ if (task.State == TaskState.Completed) {
+ if (rtm != null) {
try {
- List list = rtm.TasksUncomplete(timeline, task.ListID, task.SeriesTaskID, task.TaskTaskID);
- UpdateTaskFromResult(list);
- } catch(Exception e) {
- Logger.Debug("Unable to set Task as completed: " + task.Name);
- Logger.Debug(e.ToString());
+ rtm.TasksUncomplete (timeline, task.ListID, task.SeriesTaskID, task.TaskTaskID);
+ } catch (Exception e) {
+ Logger.Debug ("Unable to set Task as completed: " + task.Name);
+ Logger.Debug (e.ToString ());
}
}
}
- else
- UpdateTask(task);
}
- public void UpdateTaskInactive(RtmTask task)
+ internal void UpdateTaskCompleted (RtmTask task)
{
- UpdateTask(task);
- }
-
- public void UpdateTaskCompleted(RtmTask task)
- {
- if(rtm != null) {
+ if (rtm != null) {
try {
- List list = rtm.TasksComplete(timeline, task.ListID, task.SeriesTaskID, task.TaskTaskID);
- UpdateTaskFromResult(list);
- } catch(Exception e) {
- Logger.Debug("Unable to set Task as completed: " + task.Name);
- Logger.Debug(e.ToString());
+ rtm.TasksComplete (timeline, task.ListID, task.SeriesTaskID, task.TaskTaskID);
+ } catch (Exception e) {
+ Logger.Debug ("Unable to set Task as completed: " + task.Name);
+ Logger.Debug (e.ToString ());
}
}
- }
-
- public void UpdateTaskDeleted(RtmTask task)
- {
- UpdateTask(task);
}
-
- public void MoveTaskCategory(RtmTask task, string id)
+ internal void MoveTaskCategory (RtmTask task, string id)
{
- if(rtm != null) {
+ if (rtm != null) {
try {
- List list = rtm.TasksMoveTo(timeline, task.ListID, id, task.SeriesTaskID, task.TaskTaskID);
- UpdateTaskFromResult(list);
- } catch(Exception e) {
- Logger.Debug("Unable to set Task as completed: " + task.Name);
- Logger.Debug(e.ToString());
+ rtm.TasksMoveTo (timeline, task.ListID, id, task.SeriesTaskID, task.TaskTaskID);
+ } catch (Exception e) {
+ Logger.Debug ("Unable to set Task as completed: " + task.Name);
+ Logger.Debug (e.ToString ());
}
}
}
-
-
- public void UpdateTask(RtmTask task)
- {
- lock(taskLock)
- {
- Gtk.TreeIter iter;
-
- Gtk.Application.Invoke ( delegate {
- if(taskIters.ContainsKey(task.ID)) {
- iter = taskIters[task.ID];
- taskStore.SetValue (iter, 0, task);
- }
- });
- }
- }
-
- public RtmTask UpdateTaskFromResult(List list)
- {
- TaskSeries ts = list.TaskSeriesCollection[0];
- if(ts != null) {
- RtmTask rtmTask = null;
- foreach(Task task in ts.TaskCollection)
- {
- rtmTask = new RtmTask(ts, task, this, list.ID);
- lock(taskLock)
- {
- Gtk.Application.Invoke ( delegate {
- if(taskIters.ContainsKey(rtmTask.ID)) {
- Gtk.TreeIter iter = taskIters[rtmTask.ID];
- taskStore.SetValue (iter, 0, rtmTask);
- } else {
- Gtk.TreeIter iter = taskStore.AppendNode();
- taskIters.Add(rtmTask.ID, iter);
- taskStore.SetValue (iter, 0, rtmTask);
- }
- });
- }
- }
- /* Always return the last task received */
- return rtmTask;
- }
- return null;
- }
-
- public RtmCategory GetCategory(string id)
- {
- if(categories.ContainsKey(id))
- return categories[id];
- else
- return null;
- }
-
- public RtmNote CreateNote (RtmTask rtmTask, string text)
+
+ internal RtmNote CreateNote (RtmTask rtmTask, string text)
{
RtmNet.Note note = null;
RtmNote rtmNote = null;
- if(rtm != null) {
+ if (rtm != null) {
try {
- note = rtm.NotesAdd(timeline, rtmTask.ListID, rtmTask.SeriesTaskID, rtmTask.TaskTaskID, String.Empty, text);
- rtmNote = new RtmNote(note);
- } catch(Exception e) {
- Logger.Debug("RtmBackend.CreateNote: Unable to create a new note");
- Logger.Debug(e.ToString());
+ note = rtm.NotesAdd (timeline, rtmTask.ListID, rtmTask.SeriesTaskID, rtmTask.TaskTaskID, String.Empty, text);
+ rtmNote = new RtmNote (note);
+ } catch (Exception e) {
+ Logger.Debug ("RtmBackend.CreateNote: Unable to create a new note");
+ Logger.Debug (e.ToString ());
}
- }
- else
- throw new Exception("Unable to communicate with Remember The Milk");
-
+ } else
+ throw new Exception ("Unable to communicate with Remember The Milk");
+
return rtmNote;
}
-
- public void DeleteNote (RtmTask rtmTask, RtmNote note)
+ internal void DeleteNote (RtmTask rtmTask, RtmNote note)
{
- if(rtm != null) {
+ if (rtm != null) {
try {
- rtm.NotesDelete(timeline, note.ID);
- } catch(Exception e) {
- Logger.Debug("RtmBackend.DeleteNote: Unable to delete note");
- Logger.Debug(e.ToString());
+ rtm.NotesDelete (timeline, note.ID);
+ } catch (Exception e) {
+ Logger.Debug ("RtmBackend.DeleteNote: Unable to delete note");
+ Logger.Debug (e.ToString ());
}
- }
- else
- throw new Exception("Unable to communicate with Remember The Milk");
+ } else
+ throw new Exception ("Unable to communicate with Remember The Milk");
}
-
- public void SaveNote (RtmTask rtmTask, RtmNote note)
+
+ internal void SaveNote (RtmTask rtmTask, RtmNote note)
{
- if(rtm != null) {
+ if (rtm != null) {
try {
- rtm.NotesEdit(timeline, note.ID, String.Empty, note.Text);
- } catch(Exception e) {
- Logger.Debug("RtmBackend.SaveNote: Unable to save note");
- Logger.Debug(e.ToString());
+ rtm.NotesEdit (timeline, note.ID, String.Empty, note.Text);
+ } catch (Exception e) {
+ Logger.Debug ("RtmBackend.SaveNote: Unable to save note");
+ Logger.Debug (e.ToString ());
}
- }
- else
- throw new Exception("Unable to communicate with Remember The Milk");
- }
-
-#endregion // Public Methods
-
-#region Private Methods
- static int CompareTasksSortFunc (Gtk.TreeModel model,
- Gtk.TreeIter a,
- Gtk.TreeIter b)
- {
- ITask taskA = model.GetValue (a, 0) as ITask;
- ITask taskB = model.GetValue (b, 0) as ITask;
-
- if (taskA == null || taskB == null)
- return 0;
-
- return (taskA.CompareTo (taskB));
- }
-
- static int CompareCategorySortFunc (Gtk.TreeModel model,
- Gtk.TreeIter a,
- Gtk.TreeIter b)
- {
- ICategory categoryA = model.GetValue (a, 0) as ICategory;
- ICategory categoryB = model.GetValue (b, 0) as ICategory;
-
- if (categoryA == null || categoryB == null)
- return 0;
-
- if (categoryA is Tasque.AllCategory)
- return -1;
- else if (categoryB is Tasque.AllCategory)
- return 1;
-
- return (categoryA.Name.CompareTo (categoryB.Name));
+ } else
+ throw new Exception ("Unable to communicate with Remember The Milk");
}
+ #endregion
+ #region My privates
/// <summary>
/// Update the model to match what is in RTM
/// FIXME: This is a lame implementation and needs to be optimized
/// </summary>
- private void UpdateCategories(Lists lists)
+ void UpdateCategories (RtmNet.Lists lists)
{
- Logger.Debug("RtmBackend.UpdateCategories was called");
-
+ Logger.Debug ("RtmBackend.UpdateCategories was called");
try {
- foreach(List list in lists.listCollection)
- {
+ foreach (var list in lists.listCollection) {
if (list.Smart == 1) {
Logger.Warn ("Smart list \"{0}\" omitted", list.Name);
continue;
}
- RtmCategory rtmCategory = new RtmCategory(list);
-
- lock(catLock)
- {
- Gtk.TreeIter iter;
-
- Gtk.Application.Invoke ( delegate {
-
- if(categories.ContainsKey(rtmCategory.ID)) {
- iter = categories[rtmCategory.ID].Iter;
- categoryListStore.SetValue (iter, 0, rtmCategory);
- } else {
- iter = categoryListStore.Append();
- categoryListStore.SetValue (iter, 0, rtmCategory);
- rtmCategory.Iter = iter;
- categories.Add(rtmCategory.ID, rtmCategory);
- }
- });
- }
+ var rtmCategory = new RtmCategory (list);
+ if (categories.Any (c => c.Name == rtmCategory.Name))
+ continue;
+
+ AddCategory (rtmCategory);
}
} catch (Exception e) {
- Logger.Debug("Exception in fetch " + e.Message);
+ Logger.Debug ("Exception in fetch " + e.Message);
}
- Logger.Debug("RtmBackend.UpdateCategories is done");
+ Logger.Debug ("RtmBackend.UpdateCategories is done");
}
-
+
/// <summary>
/// Update the model to match what is in RTM
/// FIXME: This is a lame implementation and needs to be optimized
/// </summary>
- private void UpdateTasks(Lists lists)
+ void UpdateTasks (RtmNet.Lists lists)
{
- Logger.Debug("RtmBackend.UpdateTasks was called");
-
+ Logger.Debug ("RtmBackend.UpdateTasks was called");
try {
- foreach(List list in lists.listCollection)
- {
+ foreach (var list in lists.listCollection) {
// smart lists are based on criteria and therefore
// can contain tasks that actually belong to another list.
// Hence skip smart lists in task list population.
if (list.Smart == 1)
continue;
- Tasks tasks = null;
+ RtmNet.Tasks rtmTasks = null;
try {
- tasks = rtm.TasksGetList(list.ID);
+ rtmTasks = rtm.TasksGetList (list.ID);
} catch (Exception tglex) {
- Logger.Debug("Exception calling TasksGetList(list.ListID) " + tglex.Message);
+ Logger.Debug ("Exception calling TasksGetList (list.ListID) "
+ + tglex.Message);
}
-
- if(tasks != null) {
- foreach(List tList in tasks.ListCollection)
- {
+
+ if (rtmTasks != null) {
+ foreach (var tList in rtmTasks.ListCollection) {
if (tList.TaskSeriesCollection == null)
continue;
- foreach(TaskSeries ts in tList.TaskSeriesCollection)
- {
- foreach(Task task in ts.TaskCollection)
- {
- RtmTask rtmTask = new RtmTask(ts, task, this, tList.ID);
-
- lock(taskLock)
- {
- Gtk.TreeIter iter;
-
- Gtk.Application.Invoke ( delegate {
-
- if(taskIters.ContainsKey(rtmTask.ID)) {
- iter = taskIters[rtmTask.ID];
- } else {
- iter = taskStore.AppendNode ();
- taskIters.Add(rtmTask.ID, iter);
- }
-
- taskStore.SetValue (iter, 0, rtmTask);
- });
- }
- }
- }
+
+ foreach (var ts in tList.TaskSeriesCollection)
+ UpdateTaskCore (ts, tList.ID);
}
}
}
} catch (Exception e) {
- Logger.Debug("Exception in fetch " + e.Message);
- Logger.Debug(e.ToString());
+ Logger.Debug ("Exception in fetch " + e.Message);
+ Logger.Debug (e.ToString ());
+ }
+ Logger.Debug ("RtmBackend.UpdateTasks is done");
+ }
+
+ RtmTask UpdateTaskFromResult (RtmNet.List list)
+ {
+ var ts = list.TaskSeriesCollection [0];
+ if (ts != null)
+ return UpdateTaskCore (ts, list.ID);
+ return null;
+ }
+
+ RtmTask UpdateTaskCore (RtmNet.TaskSeries taskSeries, string listId)
+ {
+ RtmTask rtmTask = null;
+ foreach (var task in taskSeries.TaskCollection) {
+ rtmTask = new RtmTask (taskSeries, task, this, listId);
+ if (tasks.Any (t => t.Id == rtmTask.Id))
+ continue;
+
+ rtmTask.PropertyChanged += HandlePropertyChanged;
+ AddTask (rtmTask);
}
- Logger.Debug("RtmBackend.UpdateTasks is done");
+ /* Always return the last task received */
+ return rtmTask;
}
+ void AddCategory (ICategory category)
+ {
+ var index = categories.Count;
+ var valIdx = categories.Select ((val, idx) => new { val, idx })
+ .FirstOrDefault (x => categoryComparer.Compare (x.val, category) > 0);
+ if (valIdx != null)
+ index = valIdx.idx;
+ categories.Insert (index, category);
+ }
+ void AddTask (RtmTask task)
+ {
+ var index = tasks.Count;
+ var valIdx = tasks.Select ((val, idx) => new { val, idx })
+ .FirstOrDefault (t => taskComparer.Compare (t.val, task) > 0);
+ if (valIdx != null)
+ index = valIdx.idx;
+
+ tasks.Insert (index, task);
+ }
- private void RefreshThreadLoop()
+ void HandlePropertyChanged (object sender, PropertyChangedEventArgs e)
{
- while(runningRefreshThread) {
- runRefreshEvent.WaitOne();
-
- if(!runningRefreshThread)
- return;
-
- // Fire the event on the main thread
- Gtk.Application.Invoke ( delegate {
- if(BackendSyncStarted != null)
- BackendSyncStarted();
- });
+ // when a property changes (any property atm), "reorder" tasks
+ var task = (RtmTask)sender;
+ if (tasks.Remove (task))
+ AddTask (task);
+ }
- runRefreshEvent.Reset();
+ ObservableCollection<ITask> tasks;
+ ObservableCollection<ICategory> categories;
+ TaskComparer taskComparer;
+ CategoryComparer categoryComparer;
- if(rtmAuth != null) {
- Lists lists = rtm.ListsGetList();
- UpdateCategories(lists);
- UpdateTasks(lists);
- }
- if(!initialized) {
- initialized = true;
-
- // Fire the event on the main thread
- Gtk.Application.Invoke ( delegate {
- if(BackendInitialized != null)
- BackendInitialized();
- });
- }
+ Preferences preferences;
- // Fire the event on the main thread
- Gtk.Application.Invoke ( delegate {
- if(BackendSyncFinished != null)
- BackendSyncFinished();
- });
- }
- }
-
-#endregion // Private Methods
+ const string apiKey = "b29f7517b6584035d07df3170b80c430";
+ const string sharedSecret = "93eb5f83628b2066";
-#region Event Handlers
-#endregion // Event Handlers
+ RtmNet.Rtm rtm;
+ string frob;
+ RtmNet.Auth rtmAuth;
+ string timeline;
+ #endregion
}
}
diff --git a/src/Addins/Backends/Rtm/RtmBackend.csproj b/src/Addins/Backends/Rtm/RtmBackend.csproj
index 3ba0aa3..8373fad 100644
--- a/src/Addins/Backends/Rtm/RtmBackend.csproj
+++ b/src/Addins/Backends/Rtm/RtmBackend.csproj
@@ -58,6 +58,7 @@
<Reference Include="atk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f">
<Private>False</Private>
</Reference>
+ <Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\libtasque\libtasque.csproj">
@@ -68,10 +69,6 @@
<Project>{0AA1B96E-03DE-4D26-B4FD-507E988FD9B7}</Project>
<Name>RtmNet</Name>
</ProjectReference>
- <ProjectReference Include="..\..\..\Gtk.Tasque\Gtk.Tasque.csproj">
- <Project>{B19B9840-669D-4984-9772-E1F55193A67F}</Project>
- <Name>Gtk.Tasque</Name>
- </ProjectReference>
</ItemGroup>
<ItemGroup>
<Compile Include="RtmTask.cs" />
diff --git a/src/Addins/Backends/Rtm/RtmCategory.cs b/src/Addins/Backends/Rtm/RtmCategory.cs
index 87595c3..b8cac95 100644
--- a/src/Addins/Backends/Rtm/RtmCategory.cs
+++ b/src/Addins/Backends/Rtm/RtmCategory.cs
@@ -3,17 +3,14 @@
//
// To change standard headers go to Edit->Preferences->Coding->Standard Headers
//
-
-using System;
using Tasque;
using RtmNet;
-namespace Tasque.Backends.RtmBackend
+namespace Tasque.Backends.Rtm
{
public class RtmCategory : ICategory
{
private List list;
- private Gtk.TreeIter iter;
public RtmCategory(List list)
{
@@ -54,12 +51,6 @@ namespace Tasque.Backends.RtmBackend
{
get { return list.Smart; }
}
-
- public Gtk.TreeIter Iter
- {
- get { return iter; }
- set { iter = value; }
- }
public bool ContainsTask(ITask task)
{
diff --git a/src/Addins/Backends/Rtm/RtmNote.cs b/src/Addins/Backends/Rtm/RtmNote.cs
index 72b0689..2e605b5 100644
--- a/src/Addins/Backends/Rtm/RtmNote.cs
+++ b/src/Addins/Backends/Rtm/RtmNote.cs
@@ -8,7 +8,7 @@ using System;
using Tasque;
using RtmNet;
-namespace Tasque.Backends.RtmBackend
+namespace Tasque.Backends.Rtm
{
public class RtmNote : INote
{
diff --git a/src/Addins/Backends/Rtm/RtmTask.cs b/src/Addins/Backends/Rtm/RtmTask.cs
index d742660..d6bb74f 100644
--- a/src/Addins/Backends/Rtm/RtmTask.cs
+++ b/src/Addins/Backends/Rtm/RtmTask.cs
@@ -5,7 +5,7 @@ using System;
using RtmNet;
using System.Collections.Generic;
-namespace Tasque.Backends.RtmBackend
+namespace Tasque.Backends.Rtm
{
public class RtmTask : AbstractTask
{
@@ -61,8 +61,10 @@ namespace Tasque.Backends.RtmBackend
get { return taskSeries.Name; }
set {
if (value != null) {
+ OnPropertyChanging ("Name");
taskSeries.Name = value.Trim ();
rtmBackend.UpdateTaskName(this);
+ OnPropertyChanged ("CompletionDate");
}
}
}
@@ -73,9 +75,11 @@ namespace Tasque.Backends.RtmBackend
public override DateTime DueDate
{
get { return task.Due; }
- set {
+ set {
+ OnPropertyChanging ("DueDate");
task.Due = value;
- rtmBackend.UpdateTaskDueDate(this);
+ rtmBackend.UpdateTaskDueDate(this);
+ OnPropertyChanged ("CompletionDate");
}
}
@@ -100,9 +104,10 @@ namespace Tasque.Backends.RtmBackend
public override DateTime CompletionDate
{
get { return task.Completed; }
- set {
+ set {
+ OnPropertyChanging ("CompletionDate");
task.Completed = value;
- rtmBackend.UpdateTaskCompleteDate(this);
+ OnPropertyChanged ("CompletionDate");
}
}
@@ -133,6 +138,7 @@ namespace Tasque.Backends.RtmBackend
}
}
set {
+ OnPropertyChanging ("Priority");
switch (value) {
default:
case TaskPriority.None:
@@ -148,7 +154,8 @@ namespace Tasque.Backends.RtmBackend
task.Priority = "3";
break;
}
- rtmBackend.UpdateTaskPriority(this);
+ rtmBackend.UpdateTaskPriority(this);
+ OnPropertyChanged ("CompletionDate");
}
}
@@ -189,8 +196,10 @@ namespace Tasque.Backends.RtmBackend
{
get { return category; }
set {
+ OnPropertyChanging ("Category");
RtmCategory rtmCategory = value as RtmCategory;
- rtmBackend.MoveTaskCategory(this, rtmCategory.ID);
+ rtmBackend.MoveTaskCategory(this, rtmCategory.ID);
+ OnPropertyChanged ("CompletionDate");
}
}
@@ -239,8 +248,7 @@ namespace Tasque.Backends.RtmBackend
{
Logger.Debug("Activating Task: " + Name);
state = TaskState.Active;
- task.Completed = DateTime.MinValue;
- rtmBackend.UpdateTaskActive(this);
+ CompletionDate = DateTime.MinValue;
}
/// <summary>
@@ -250,8 +258,7 @@ namespace Tasque.Backends.RtmBackend
{
Logger.Debug("Inactivating Task: " + Name);
state = TaskState.Inactive;
- task.Completed = DateTime.Now;
- rtmBackend.UpdateTaskInactive(this);
+ CompletionDate = DateTime.Now;
}
/// <summary>
@@ -261,9 +268,8 @@ namespace Tasque.Backends.RtmBackend
{
Logger.Debug("Completing Task: " + Name);
state = TaskState.Completed;
- if(task.Completed == DateTime.MinValue)
- task.Completed = DateTime.Now;
- rtmBackend.UpdateTaskCompleted(this);
+ if(CompletionDate == DateTime.MinValue)
+ CompletionDate = DateTime.Now;
}
/// <summary>
@@ -272,7 +278,7 @@ namespace Tasque.Backends.RtmBackend
public override void Delete ()
{
state = TaskState.Deleted;
- rtmBackend.UpdateTaskDeleted(this);
+ rtmBackend.DeleteTask (this);
}
/// <summary>
diff --git a/src/Addins/Backends/Sqlite/SqliteBackend.cs b/src/Addins/Backends/Sqlite/SqliteBackend.cs
index 6c6d50d..49201e5 100644
--- a/src/Addins/Backends/Sqlite/SqliteBackend.cs
+++ b/src/Addins/Backends/Sqlite/SqliteBackend.cs
@@ -1,27 +1,30 @@
// SqliteBackend.cs created with MonoDevelop
// User: boyd at 7:10 AMÂ2/11/2008
-
using System;
using System.Collections.Generic;
-using Mono.Unix;
-using Tasque.Backends;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
using Mono.Data.Sqlite;
+using Tasque.Backends;
using Gtk.Tasque.Backends.Sqlite;
namespace Tasque.Backends.Sqlite
{
public class SqliteBackend : IBackend
{
- private Dictionary<int, Gtk.TreeIter> taskIters;
- private Gtk.TreeStore taskStore;
- private Gtk.TreeModelSort sortedTasksModel;
private bool initialized;
private bool configured = true;
+
+ ObservableCollection<ITask> taskStore;
+ ObservableCollection<ICategory> categoryListStore;
+ ReadOnlyObservableCollection<ITask> readOnlyTaskStore;
+ ReadOnlyObservableCollection<ICategory> readOnlyCategoryStore;
- private Database db;
+ TaskComparer taskComparer;
+ CategoryComparer categoryComparer;
- private Gtk.ListStore categoryListStore;
- private Gtk.TreeModelSort sortedCategoriesModel;
+ private Database db;
public event BackendInitializedHandler BackendInitialized;
public event BackendSyncStartedHandler BackendSyncStarted;
@@ -34,18 +37,13 @@ namespace Tasque.Backends.Sqlite
public SqliteBackend ()
{
initialized = false;
- taskIters = new Dictionary<int, Gtk.TreeIter> ();
- taskStore = new Gtk.TreeStore (typeof (ITask));
-
- sortedTasksModel = new Gtk.TreeModelSort (taskStore);
- sortedTasksModel.SetSortFunc (0, new Gtk.TreeIterCompareFunc (CompareTasksSortFunc));
- sortedTasksModel.SetSortColumnId (0, Gtk.SortType.Ascending);
-
- categoryListStore = new Gtk.ListStore (typeof (ICategory));
-
- sortedCategoriesModel = new Gtk.TreeModelSort (categoryListStore);
- sortedCategoriesModel.SetSortFunc (0, new Gtk.TreeIterCompareFunc (CompareCategorySortFunc));
- sortedCategoriesModel.SetSortColumnId (0, Gtk.SortType.Ascending);
+ taskStore = new ObservableCollection<ITask> ();
+ categoryListStore = new ObservableCollection<ICategory> ();
+ readOnlyTaskStore = new ReadOnlyObservableCollection<ITask> (taskStore);
+ readOnlyCategoryStore
+ = new ReadOnlyObservableCollection<ICategory> (categoryListStore);
+ taskComparer = new TaskComparer ();
+ categoryComparer = new CategoryComparer ();
}
#region Public Properties
@@ -57,17 +55,17 @@ namespace Tasque.Backends.Sqlite
/// <value>
/// All the tasks including ITaskDivider items.
/// </value>
- public Gtk.TreeModel Tasks
+ public ICollection<ITask> Tasks
{
- get { return sortedTasksModel; }
+ get { return readOnlyTaskStore; }
}
/// <value>
/// This returns all the task lists (categories) that exist.
/// </value>
- public Gtk.TreeModel Categories
+ public ICollection<ICategory> Categories
{
- get { return sortedCategoriesModel; }
+ get { return readOnlyCategoryStore; }
}
/// <value>
@@ -113,9 +111,8 @@ namespace Tasque.Backends.Sqlite
else
task.Category = category;
- Gtk.TreeIter iter = taskStore.AppendNode ();
- taskStore.SetValue (iter, 0, task);
- taskIters [task.SqliteId] = iter;
+ AddTask (task);
+ task.PropertyChanged += HandlePropertyChanged;
return task;
}
@@ -145,14 +142,11 @@ namespace Tasque.Backends.Sqlite
// Add in the "All" Category
//
AllCategory allCategory = new Tasque.AllCategory (preferences);
- Gtk.TreeIter iter = categoryListStore.Append ();
- categoryListStore.SetValue (iter, 0, allCategory);
-
-
+ AddCategory (allCategory);
+
RefreshCategories();
RefreshTasks();
-
initialized = true;
if(BackendInitialized != null) {
BackendInitialized();
@@ -163,7 +157,6 @@ namespace Tasque.Backends.Sqlite
{
this.categoryListStore.Clear();
this.taskStore.Clear();
- this.taskIters.Clear();
if (db != null)
db.Close();
@@ -182,68 +175,8 @@ namespace Tasque.Backends.Sqlite
}
#endregion // Public Methods
-
- #region Private Methods
- static int CompareTasksSortFunc (Gtk.TreeModel model,
- Gtk.TreeIter a,
- Gtk.TreeIter b)
- {
- ITask taskA = model.GetValue (a, 0) as ITask;
- ITask taskB = model.GetValue (b, 0) as ITask;
-
- if (taskA == null || taskB == null)
- return 0;
-
- return (taskA.CompareTo (taskB));
- }
-
- static int CompareCategorySortFunc (Gtk.TreeModel model,
- Gtk.TreeIter a,
- Gtk.TreeIter b)
- {
- ICategory categoryA = model.GetValue (a, 0) as ICategory;
- ICategory categoryB = model.GetValue (b, 0) as ICategory;
-
- if (categoryA == null || categoryB == null)
- return 0;
-
- if (categoryA is Tasque.AllCategory)
- return -1;
- else if (categoryB is Tasque.AllCategory)
- return 1;
-
- return (categoryA.Name.CompareTo (categoryB.Name));
- }
-
- public void UpdateTask (SqliteTask task)
- {
- // Set the task in the store so the model will update the UI.
- Gtk.TreeIter iter;
-
- if (!taskIters.ContainsKey (task.SqliteId))
- return;
-
- iter = taskIters [task.SqliteId];
-
- if (task.State == TaskState.Deleted) {
- taskIters.Remove (task.SqliteId);
- if (!taskStore.Remove (ref iter)) {
- Logger.Debug ("Successfully deleted from taskStore: {0}",
- task.Name);
- } else {
- Logger.Debug ("Problem removing from taskStore: {0}",
- task.Name);
- }
- } else {
- taskStore.SetValue (iter, 0, task);
- }
- }
-
-
-
public void RefreshCategories()
{
- Gtk.TreeIter iter;
SqliteCategory newCategory;
bool hasValues = false;
@@ -258,8 +191,7 @@ namespace Tasque.Backends.Sqlite
newCategory = new SqliteCategory (this, id);
if( (defaultCategory == null) || (newCategory.Name.CompareTo("Work") == 0) )
defaultCategory = newCategory;
- iter = categoryListStore.Append ();
- categoryListStore.SetValue (iter, 0, newCategory);
+ AddCategory (newCategory);
}
dataReader.Close();
@@ -268,30 +200,24 @@ namespace Tasque.Backends.Sqlite
if(!hasValues)
{
defaultCategory = newCategory = new SqliteCategory (this, "Work");
- iter = categoryListStore.Append ();
- categoryListStore.SetValue (iter, 0, newCategory);
+ AddCategory (defaultCategory);
newCategory = new SqliteCategory (this, "Personal");
- iter = categoryListStore.Append ();
- categoryListStore.SetValue (iter, 0, newCategory);
+ AddCategory (newCategory);
newCategory = new SqliteCategory (this, "Family");
- iter = categoryListStore.Append ();
- categoryListStore.SetValue (iter, 0, newCategory);
+ AddCategory (newCategory);
newCategory = new SqliteCategory (this, "Project");
- iter = categoryListStore.Append ();
- categoryListStore.SetValue (iter, 0, newCategory);
+ AddCategory (newCategory);
}
}
-
public void RefreshTasks()
{
- Gtk.TreeIter iter;
SqliteTask newTask;
bool hasValues = false;
-
+
string command = "SELECT id,Category,Name,DueDate,CompletionDate,Priority, State FROM Tasks";
SqliteCommand cmd = db.Connection.CreateCommand();
cmd.CommandText = command;
@@ -304,32 +230,65 @@ namespace Tasque.Backends.Sqlite
long completionDate = dataReader.GetInt64(4);
int priority = dataReader.GetInt32(5);
int state = dataReader.GetInt32(6);
-
+
hasValues = true;
-
+
newTask = new SqliteTask(this, id, category,
name, dueDate, completionDate,
priority, state);
- iter = taskStore.AppendNode();
- taskStore.SetValue (iter, 0, newTask);
- taskIters [newTask.SqliteId] = iter;
+ AddTask (newTask);
+ newTask.PropertyChanged += HandlePropertyChanged;
}
-
+
dataReader.Close();
cmd.Dispose();
-
+
if(!hasValues)
{
newTask = new SqliteTask (this, "Create some tasks");
newTask.Category = defaultCategory;
newTask.DueDate = DateTime.Now;
newTask.Priority = TaskPriority.Medium;
- iter = taskStore.AppendNode ();
- taskStore.SetValue (iter, 0, newTask);
- taskIters [newTask.SqliteId] = iter;
+ AddTask (newTask);
+ newTask.PropertyChanged += HandlePropertyChanged;
}
}
+ #region Private Methods
+ internal void DeleteTask (SqliteTask task)
+ {
+ if (taskStore.Remove (task))
+ task.PropertyChanged -= HandlePropertyChanged;
+ }
+
+ void AddCategory (ICategory category)
+ {
+ var index = categoryListStore.Count;
+ var valIdx = categoryListStore.Select ((val, idx) => new { val, idx })
+ .FirstOrDefault (x => categoryComparer.Compare (x.val, category) > 0);
+ if (valIdx != null)
+ index = valIdx.idx;
+ categoryListStore.Insert (index, category);
+ }
+
+ void AddTask (SqliteTask task)
+ {
+ var index = taskStore.Count;
+ var valIdx = taskStore.Select ((val, idx) => new { val, idx })
+ .FirstOrDefault (t => taskComparer.Compare (t.val, task) > 0);
+ if (valIdx != null)
+ index = valIdx.idx;
+
+ taskStore.Insert (index, task);
+ }
+
+ void HandlePropertyChanged (object sender, PropertyChangedEventArgs e)
+ {
+ // when a property changes (any property atm), "reorder" tasks
+ var task = (SqliteTask)sender;
+ if (taskStore.Remove (task))
+ AddTask (task);
+ }
#endregion // Private Methods
#region Event Handlers
diff --git a/src/Addins/Backends/Sqlite/SqliteBackend.csproj b/src/Addins/Backends/Sqlite/SqliteBackend.csproj
index ebee3af..c762a43 100644
--- a/src/Addins/Backends/Sqlite/SqliteBackend.csproj
+++ b/src/Addins/Backends/Sqlite/SqliteBackend.csproj
@@ -77,16 +77,13 @@
<Private>False</Private>
<Package>gtk-sharp-2.0</Package>
</Reference>
+ <Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\libtasque\libtasque.csproj">
<Project>{784C9AA8-2B28-400B-8CC4-DCDC48CA37F0}</Project>
<Name>libtasque</Name>
</ProjectReference>
- <ProjectReference Include="..\..\..\Gtk.Tasque\Gtk.Tasque.csproj">
- <Project>{B19B9840-669D-4984-9772-E1F55193A67F}</Project>
- <Name>Gtk.Tasque</Name>
- </ProjectReference>
</ItemGroup>
<ItemGroup>
<Compile Include="SqliteTask.cs" />
diff --git a/src/Addins/Backends/Sqlite/SqliteTask.cs b/src/Addins/Backends/Sqlite/SqliteTask.cs
index 9ede1b1..d262fd7 100644
--- a/src/Addins/Backends/Sqlite/SqliteTask.cs
+++ b/src/Addins/Backends/Sqlite/SqliteTask.cs
@@ -65,11 +65,12 @@ namespace Tasque.Backends.Sqlite
{
get { return this.name; }
set {
+ OnPropertyChanging ("Name");
string name = backend.SanitizeText (value);
this.name = name;
string command = String.Format("UPDATE Tasks set Name='{0}' where ID='{1}'", name, id);
backend.Database.ExecuteScalar(command);
- backend.UpdateTask(this);
+ OnPropertyChanged ("Name");
}
}
@@ -77,10 +78,11 @@ namespace Tasque.Backends.Sqlite
{
get { return Database.ToDateTime(this.dueDate); }
set {
+ OnPropertyChanging ("DueDate");
this.dueDate = Database.FromDateTime(value);
string command = String.Format("UPDATE Tasks set DueDate='{0}' where ID='{1}'", this.dueDate, id);
backend.Database.ExecuteScalar(command);
- backend.UpdateTask(this);
+ OnPropertyChanged ("DueDate");
}
}
@@ -89,12 +91,13 @@ namespace Tasque.Backends.Sqlite
{
get { return Database.ToDateTime(this.completionDate); }
set {
+ OnPropertyChanging ("CompletionDate");
this.completionDate = Database.FromDateTime(value);
string command = String.Format("UPDATE Tasks set CompletionDate='{0}' where ID='{1}'", this.completionDate, id);
backend.Database.ExecuteScalar(command);
- backend.UpdateTask(this);
+ OnPropertyChanged ("CompletionDate");
}
- }
+ }
public override bool IsComplete
@@ -111,10 +114,11 @@ namespace Tasque.Backends.Sqlite
{
get { return (TaskPriority) this.priority; }
set {
+ OnPropertyChanging ("Priority");
this.priority = (int) value;
string command = String.Format("UPDATE Tasks set Priority='{0}' where ID='{1}'", this.priority, id);
backend.Database.ExecuteScalar(command);
- backend.UpdateTask(this);
+ OnPropertyChanged ("Priority");
}
}
@@ -143,7 +147,6 @@ namespace Tasque.Backends.Sqlite
this.state = (int) value;
string command = String.Format("UPDATE Tasks set State='{0}' where ID='{1}'", this.state, id);
backend.Database.ExecuteScalar(command);
- backend.UpdateTask(this);
}
}
@@ -151,10 +154,11 @@ namespace Tasque.Backends.Sqlite
{
get { return new SqliteCategory(backend, this.category); }
set {
+ OnPropertyChanging ("Category");
this.category = (int)(value as SqliteCategory).ID;
string command = String.Format("UPDATE Tasks set Category='{0}' where ID='{1}'", category, id);
backend.Database.ExecuteScalar(command);
- backend.UpdateTask(this);
+ OnPropertyChanged ("Category");
}
}
@@ -183,32 +187,29 @@ namespace Tasque.Backends.Sqlite
public override void Activate ()
{
// Logger.Debug ("SqliteTask.Activate ()");
- CompletionDate = DateTime.MinValue;
LocalState = TaskState.Active;
- backend.UpdateTask (this);
+ CompletionDate = DateTime.MinValue;
}
public override void Inactivate ()
{
// Logger.Debug ("SqliteTask.Inactivate ()");
- CompletionDate = DateTime.Now;
LocalState = TaskState.Inactive;
- backend.UpdateTask (this);
+ CompletionDate = DateTime.Now;
}
public override void Complete ()
{
//Logger.Debug ("SqliteTask.Complete ()");
- CompletionDate = DateTime.Now;
LocalState = TaskState.Completed;
- backend.UpdateTask (this);
+ CompletionDate = DateTime.Now;
}
public override void Delete ()
{
//Logger.Debug ("SqliteTask.Delete ()");
LocalState = TaskState.Deleted;
- backend.UpdateTask (this);
+ backend.DeleteTask (this);
}
public override INote CreateNote(string text)
diff --git a/src/Gtk.Tasque/Application.cs b/src/Gtk.Tasque/Application.cs
new file mode 100644
index 0000000..d145622
--- /dev/null
+++ b/src/Gtk.Tasque/Application.cs
@@ -0,0 +1,527 @@
+/***************************************************************************
+ * Application.cs
+ *
+ * Copyright (C) 2008 Novell, Inc.
+ * Written by Calvin Gaisford <calvinrg gmail com>
+ ****************************************************************************/
+
+/* THIS FILE IS LICENSED UNDER THE MIT LICENSE AS OUTLINED IMMEDIATELY BELOW:
+ *
+ * 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.Generic;
+using System.Net;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using System.Threading;
+using System.Xml;
+using System.Net.Sockets;
+
+using Gtk;
+using Gdk;
+using Mono.Unix;
+using Mono.Unix.Native;
+#if ENABLE_NOTIFY_SHARP
+using Notifications;
+#endif
+using Tasque.Backends;
+
+namespace Tasque
+{
+ public class Application
+ {
+ private static Tasque.Application application = null;
+ private static System.Object locker = new System.Object();
+ private bool initialized;
+ private INativeApplication nativeApp;
+#if !WIN && !OSX
+ private RemoteControl remoteControl;
+#endif
+ private Gdk.Pixbuf normalPixBuf;
+ private Gtk.Image trayImage;
+ private GtkTray trayIcon;
+ private Preferences preferences;
+ private EventBox eb;
+ private IBackend backend;
+ private PreferencesDialog preferencesDialog;
+ private bool quietStart = false;
+
+ private DateTime currentDay = DateTime.Today;
+
+ /// <value>
+ /// Keep track of the available backends. The key is the Type name of
+ /// the backend.
+ /// </value>
+ private Dictionary<string, IBackend> availableBackends;
+
+ private IBackend customBackend;
+
+ public static IBackend Backend
+ {
+ get { return Application.Instance.backend; }
+ set { Application.Instance.SetBackend (value); }
+ }
+
+ public static List<IBackend> AvailableBackends
+ {
+ get {
+ return new List<IBackend> (Application.Instance.availableBackends.Values);
+ }
+// get { return Application.Instance.availableBackends; }
+ }
+
+ public static Application Instance
+ {
+ get {
+ lock(locker) {
+ return application;
+ }
+ }
+ }
+
+ public INativeApplication NativeApplication
+ {
+ get
+ {
+ return nativeApp;
+ }
+ }
+
+ public event EventHandler BackendChanged;
+
+ void OnBackendChanged ()
+ {
+ if (BackendChanged != null)
+ BackendChanged (this, EventArgs.Empty);
+ }
+
+ public TaskGroupModel OverdueTasks { get; private set; }
+
+ public TaskGroupModel TodayTasks { get; private set; }
+
+ public TaskGroupModel TomorrowTasks { get; private set; }
+
+ public static Preferences Preferences
+ {
+ get { return Application.Instance.preferences; }
+ }
+
+ public Application (INativeApplication nativeApp)
+ {
+ if (nativeApp == null)
+ throw new ArgumentNullException ("nativeApp");
+ this.nativeApp = nativeApp;
+ application = this;
+ }
+
+ public void Init (string[] args)
+ {
+ lock (locker) {
+ if (initialized)
+ return;
+ initialized = true;
+ }
+
+ nativeApp.Initialize (
+ Defines.LocaleDir,
+ "Tasque",
+ "Tasque",
+ args);
+
+ preferences = new Preferences (nativeApp.ConfDir);
+
+#if !WIN && !OSX
+ // Register Tasque RemoteControl
+ try {
+ remoteControl = RemoteControlProxy.Register ();
+ if (remoteControl != null) {
+ Logger.Debug ("Tasque remote control active.");
+ } else {
+ // If Tasque is already running, open the tasks window
+ // so the user gets some sort of feedback when they
+ // attempt to run Tasque again.
+ RemoteControl remote = null;
+ try {
+ remote = RemoteControlProxy.GetInstance ();
+ remote.ShowTasks ();
+ } catch {}
+
+ Logger.Debug ("Tasque is already running. Exiting...");
+ System.Environment.Exit (0);
+ }
+ } catch (Exception e) {
+ Logger.Debug ("Tasque remote control disabled (DBus exception): {0}",
+ e.Message);
+ }
+#endif
+
+ string potentialBackendClassName = null;
+
+ for (int i = 0; i < args.Length; i++) {
+ switch (args [i]) {
+
+ case "--quiet":
+ quietStart = true;
+ Logger.Debug ("Starting quietly");
+ break;
+
+ case "--backend":
+ if ( (i + 1 < args.Length) &&
+ !string.IsNullOrEmpty (args [i + 1]) &&
+ args [i + 1] [0] != '-') {
+ potentialBackendClassName = args [++i];
+ } // TODO: Else, print usage
+ break;
+
+ default:
+ // Support old argument behavior
+ if (!string.IsNullOrEmpty (args [i]))
+ potentialBackendClassName = args [i];
+ break;
+ }
+ }
+
+ // See if a specific backend is specified
+ if (potentialBackendClassName != null) {
+ Logger.Debug ("Backend specified: " +
+ potentialBackendClassName);
+
+ customBackend = null;
+ Assembly asm = Assembly.GetCallingAssembly ();
+ try {
+ customBackend = (IBackend)
+ asm.CreateInstance (potentialBackendClassName);
+ } catch (Exception e) {
+ Logger.Warn ("Backend specified on args not found: {0}\n\t{1}",
+ potentialBackendClassName, e.Message);
+ }
+ }
+
+ // Discover all available backends
+ LoadAvailableBackends ();
+
+ GLib.Idle.Add(InitializeIdle);
+ GLib.Timeout.Add (60000, CheckForDaySwitch);
+ }
+
+ /// <summary>
+ /// Load all the available backends that Tasque can find. First look in
+ /// Tasque.exe and then for other DLLs in the same directory Tasque.ex
+ /// resides.
+ /// </summary>
+ private void LoadAvailableBackends ()
+ {
+ availableBackends = new Dictionary<string,IBackend> ();
+
+ List<IBackend> backends = new List<IBackend> ();
+
+ Assembly tasqueAssembly = Assembly.GetCallingAssembly ();
+
+ // Look for other backends in Tasque.exe
+ backends.AddRange (GetBackendsFromAssembly (tasqueAssembly));
+
+ // Look through the assemblies located in the same directory as
+ // Tasque.exe.
+ Logger.Debug ("Tasque.exe location: {0}", tasqueAssembly.Location);
+
+ DirectoryInfo loadPathInfo =
+ Directory.GetParent (tasqueAssembly.Location);
+ Logger.Info ("Searching for Backend DLLs in: {0}", loadPathInfo.FullName);
+
+ foreach (FileInfo fileInfo in loadPathInfo.GetFiles ("*.dll")) {
+ Logger.Info ("\tReading {0}", fileInfo.FullName);
+ Assembly asm = null;
+ try {
+ asm = Assembly.LoadFile (fileInfo.FullName);
+ } catch (Exception e) {
+ Logger.Debug ("Exception loading {0}: {1}",
+ fileInfo.FullName,
+ e.Message);
+ continue;
+ }
+
+ backends.AddRange (GetBackendsFromAssembly (asm));
+ }
+
+ foreach (IBackend backend in backends) {
+ string typeId = backend.GetType ().ToString ();
+ if (availableBackends.ContainsKey (typeId))
+ continue;
+
+ Logger.Debug ("Storing '{0}' = '{1}'", typeId, backend.Name);
+ availableBackends [typeId] = backend;
+ }
+ }
+
+ private List<IBackend> GetBackendsFromAssembly (Assembly asm)
+ {
+ List<IBackend> backends = new List<IBackend> ();
+
+ Type[] types = null;
+
+ try {
+ types = asm.GetTypes ();
+ } catch (Exception e) {
+ Logger.Warn ("Exception reading types from assembly '{0}': {1}",
+ asm.ToString (), e.Message);
+ return backends;
+ }
+ foreach (Type type in types) {
+ if (!type.IsClass) {
+ continue; // Skip non-class types
+ }
+ if (type.GetInterface ("Tasque.Backends.IBackend") == null) {
+ continue;
+ }
+ Logger.Debug ("Found Available Backend: {0}", type.ToString ());
+
+ IBackend availableBackend = null;
+ try {
+ availableBackend = (IBackend)
+ asm.CreateInstance (type.ToString ());
+ } catch (Exception e) {
+ Logger.Warn ("Could not instantiate {0}: {1}",
+ type.ToString (),
+ e.Message);
+ continue;
+ }
+
+ if (availableBackend != null) {
+ backends.Add (availableBackend);
+ }
+ }
+
+ return backends;
+ }
+
+ private void SetBackend (IBackend value)
+ {
+ bool changingBackend = false;
+ if (this.backend != null) {
+ UnhookFromTooltipTaskGroupModels ();
+ changingBackend = true;
+ // Cleanup the old backend
+ try {
+ Logger.Debug ("Cleaning up backend: {0}",
+ this.backend.Name);
+ this.backend.Cleanup ();
+ } catch (Exception e) {
+ Logger.Warn ("Exception cleaning up '{0}': {1}",
+ this.backend.Name,
+ e);
+ }
+ }
+
+ // Initialize the new backend
+ var oldBackend = backend;
+ this.backend = value;
+ if (this.backend == null) {
+ if (trayIcon != null)
+ trayIcon.RefreshTrayIconTooltip ();
+ return;
+ }
+
+ Logger.Info ("Using backend: {0} ({1})",
+ this.backend.Name,
+ this.backend.GetType ().ToString ());
+ this.backend.Initialize (preferences);
+
+ if (!changingBackend) {
+ TaskWindow.Reinitialize (!this.quietStart);
+ } else {
+ TaskWindow.Reinitialize (true);
+ }
+
+ RebuildTooltipTaskGroupModels ();
+ if (trayIcon != null)
+ trayIcon.RefreshTrayIconTooltip ();
+
+ Logger.Debug("Configuration status: {0}",
+ this.backend.Configured.ToString());
+
+ if (backend != oldBackend)
+ OnBackendChanged ();
+ }
+
+ private bool InitializeIdle()
+ {
+ if (customBackend != null) {
+ Application.Backend = customBackend;
+ } else {
+ // Check to see if the user has a preference of which backend
+ // to use. If so, use it, otherwise, pop open the preferences
+ // dialog so they can choose one.
+ string backendTypeString = Preferences.Get (Preferences.CurrentBackend);
+ Logger.Debug ("CurrentBackend specified in Preferences: {0}", backendTypeString);
+ if (backendTypeString != null
+ && availableBackends.ContainsKey (backendTypeString)) {
+ Application.Backend = availableBackends [backendTypeString];
+ }
+ }
+
+ trayIcon = GtkTray.CreateTray ();
+
+ if (backend == null) {
+ // Pop open the preferences dialog so the user can choose a
+ // backend service to use.
+ Application.ShowPreferences ();
+ } else if (!quietStart) {
+ TaskWindow.ShowWindow ();
+ }
+ if (backend == null || !backend.Configured){
+ GLib.Timeout.Add(1000, new GLib.TimeoutHandler(RetryBackend));
+ }
+
+ nativeApp.InitializeIdle ();
+
+ return false;
+ }
+ private bool RetryBackend(){
+ try {
+ if (backend != null && !backend.Configured) {
+ backend.Cleanup();
+ backend.Initialize (preferences);
+ }
+ } catch (Exception e) {
+ Logger.Error("{0}", e.Message);
+ }
+ if (backend == null || !backend.Configured) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private bool CheckForDaySwitch ()
+ {
+ if (DateTime.Today != currentDay) {
+ Logger.Debug ("Day has changed, reloading tasks");
+ currentDay = DateTime.Today;
+ // Reinitialize window according to new date
+ if (TaskWindow.IsOpen)
+ TaskWindow.Reinitialize (true);
+
+ UnhookFromTooltipTaskGroupModels ();
+ RebuildTooltipTaskGroupModels ();
+ if (trayIcon != null)
+ trayIcon.RefreshTrayIconTooltip ();
+ }
+
+ return true;
+ }
+
+ private void UnhookFromTooltipTaskGroupModels ()
+ {
+ foreach (TaskGroupModel model in new TaskGroupModel[] { OverdueTasks, TodayTasks, TomorrowTasks })
+ {
+ if (model == null) {
+ continue;
+ }
+
+ model.CollectionChanged -= OnTooltipModelChanged;
+ }
+ }
+
+ private void OnTooltipModelChanged (object o, EventArgs args)
+ {
+ if (trayIcon != null)
+ trayIcon.RefreshTrayIconTooltip ();
+ }
+
+ private void RebuildTooltipTaskGroupModels ()
+ {
+ if (backend == null || backend.Tasks == null) {
+ OverdueTasks = null;
+ TodayTasks = null;
+ TomorrowTasks = null;
+
+ return;
+ }
+
+ OverdueTasks = TaskGroupModelFactory.CreateOverdueModel (backend.Tasks);
+ TodayTasks = TaskGroupModelFactory.CreateTodayModel (backend.Tasks);
+ TomorrowTasks = TaskGroupModelFactory.CreateTomorrowModel (backend.Tasks);
+
+ foreach (TaskGroupModel model in new TaskGroupModel[] { OverdueTasks, TodayTasks, TomorrowTasks })
+ {
+ if (model == null) {
+ continue;
+ }
+
+ model.CollectionChanged += OnTooltipModelChanged;
+ }
+ }
+
+ private void OnPreferencesDialogHidden (object sender, EventArgs args)
+ {
+ preferencesDialog.Destroy ();
+ preferencesDialog = null;
+ }
+
+ public static void ShowPreferences ()
+ {
+ Logger.Info ("ShowPreferences called");
+ var app = application;
+ if (app.preferencesDialog == null) {
+ app.preferencesDialog = new PreferencesDialog ();
+ app.preferencesDialog.Hidden += app.OnPreferencesDialogHidden;
+ }
+
+ app.preferencesDialog.Present ();
+ }
+
+#if ENABLE_NOTIFY_SHARP
+ public static void ShowAppNotification(Notification notification)
+ {
+ // TODO: Use this API for newer versions of notify-sharp
+ //notification.AttachToStatusIcon(
+ // Tasque.Application.Instance.trayIcon);
+ notification.Show();
+ }
+#endif
+
+ public void StartMainLoop ()
+ {
+ nativeApp.StartMainLoop ();
+ }
+
+ public void Quit ()
+ {
+ Logger.Info ("Quit called - terminating application");
+ if (backend != null) {
+ UnhookFromTooltipTaskGroupModels ();
+ backend.Cleanup ();
+ }
+ TaskWindow.SavePosition ();
+
+ nativeApp.QuitMainLoop ();
+ }
+
+ public void Exit (int exitCode)
+ {
+ if (nativeApp != null)
+ nativeApp.Exit (exitCode);
+ else
+ Environment.Exit (exitCode);
+ }
+ }
+}
diff --git a/src/Gtk.Tasque/CompletedTaskGroup.cs b/src/Gtk.Tasque/CompletedTaskGroup.cs
index 4bcd71d..7213e28 100644
--- a/src/Gtk.Tasque/CompletedTaskGroup.cs
+++ b/src/Gtk.Tasque/CompletedTaskGroup.cs
@@ -5,8 +5,13 @@
//
using System;
-using Gtk;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Linq;
using Mono.Unix;
+using Gtk;
+using Gtk.Tasque;
namespace Tasque
{
@@ -26,9 +31,8 @@ namespace Tasque
ShowCompletedRange currentRange;
public CompletedTaskGroup (string groupName, DateTime rangeStart,
- DateTime rangeEnd, Gtk.TreeModel tasks, INativeApplication application)
- : base (groupName, rangeStart, rangeEnd,
- new CompletedTasksSortModel(tasks), application)
+ DateTime rangeEnd, ICollection<ITask> tasks, INativeApplication application)
+ : base (groupName, rangeStart, rangeEnd, new CompletedTasksSortModel(tasks), application)
{
// Don't hide this group when it's empty because then the range
// slider won't appear and the user won't be able to customize the
@@ -75,11 +79,12 @@ namespace Tasque
this.ExtraWidget = rangeSlider;
}
- protected override TaskGroupModel CreateModel (DateTime rangeStart,
- DateTime rangeEnd,
- TreeModel tasks)
+ protected override TreeModel CreateModel (DateTime rangeStart, DateTime rangeEnd,
+ ICollection<ITask> tasks)
{
- return new CompletedTaskGroupModel (rangeStart, rangeEnd, tasks);
+ Model = new CompletedTaskGroupModel (rangeStart, rangeEnd,
+ tasks, Application.Preferences);
+ return new TreeModelListAdapter<ITask> (Model);
}
protected void OnSelectedCategorySettingChanged (
@@ -100,18 +105,8 @@ namespace Tasque
string cat = Application.Preferences.Get (
Preferences.SelectedCategoryKey);
if (cat != null) {
- TreeIter iter;
- TreeModel model = Application.Backend.Categories;
-
- if (model.GetIterFirst (out iter)) {
- do {
- ICategory category = model.GetValue (iter, 0) as ICategory;
- if (category.Name.CompareTo (cat) == 0) {
- foundCategory = category;
- break;
- }
- } while (model.IterNext (ref iter));
- }
+ var model = Application.Backend.Categories;
+ foundCategory = model.FirstOrDefault (c => c.Name == cat);
}
return foundCategory;
@@ -211,29 +206,52 @@ namespace Tasque
/// completed tasks in reverse order (i.e., most recently completed tasks
/// at the top of the list).
/// </summary>
- class CompletedTasksSortModel : Gtk.TreeModelSort
+ class CompletedTasksSortModel : ObservableCollection<ITask>
{
- public CompletedTasksSortModel (Gtk.TreeModel childModel)
- : base (childModel)
+ public CompletedTasksSortModel (ICollection<ITask> childModel)
{
- SetSortFunc (0, new Gtk.TreeIterCompareFunc (CompareTasksSortFunc));
- SetSortColumnId (0, Gtk.SortType.Descending);
+ originalTasks = childModel;
+ ((INotifyCollectionChanged)childModel).CollectionChanged += HandleCollectionChanged;
+ foreach (var item in originalTasks)
+ AddTask (item);
}
#region Private Methods
- static int CompareTasksSortFunc (Gtk.TreeModel model,
- Gtk.TreeIter a,
- Gtk.TreeIter b)
+ void AddTask (ITask task)
{
- ITask taskA = model.GetValue (a, 0) as ITask;
- ITask taskB = model.GetValue (b, 0) as ITask;
+ var index = Count;
+ var valIdx = this.Select ((val, idx) => new { val, idx })
+ .FirstOrDefault (t => CompareTasksSortFunc (t.val, task) > 0);
+ if (valIdx != null)
+ index = valIdx.idx;
- if (taskA == null || taskB == null)
+ Insert (index, task);
+ }
+
+ void HandleCollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
+ {
+ //FIXME: Only add and remove actions are expected
+ switch (e.Action) {
+ case NotifyCollectionChangedAction.Add:
+ AddTask ((ITask)e.NewItems [0]);
+ break;
+ case NotifyCollectionChangedAction.Remove:
+ var task = ((ITask)e.OldItems [0]);
+ Remove (task);
+ break;
+ }
+ }
+
+ int CompareTasksSortFunc (ITask x, ITask y)
+ {
+ if (x == null || y == null)
return 0;
- // Reverse the logic with the '!' so it's in re
- return (taskA.CompareToByCompletionDate (taskB));
+ // Reverse the logic with the '-' so it's in reverse
+ return -(x.CompareToByCompletionDate (y));
}
#endregion // Private Methods
+
+ ICollection<ITask> originalTasks;
}
}
diff --git a/src/Gtk.Tasque/Gtk.Tasque.csproj b/src/Gtk.Tasque/Gtk.Tasque.csproj
index 2430b6f..01fdb69 100644
--- a/src/Gtk.Tasque/Gtk.Tasque.csproj
+++ b/src/Gtk.Tasque/Gtk.Tasque.csproj
@@ -72,6 +72,7 @@
<Reference Include="dbus-sharp-glib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=5675b0c3093115b5">
<Private>False</Private>
</Reference>
+ <Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\libtasque\libtasque.csproj">
@@ -84,8 +85,6 @@
<Compile Include="GtkTray.cs" />
<Compile Include="StatusIconTray.cs" />
<Compile Condition=" '$(AppIndicator)' != '' " Include="AppIndicatorTray.cs" />
- <Compile Include="AbstractTask.cs" />
- <Compile Include="AllCategory.cs" />
<Compile Include="CellRendererDate.cs" />
<Compile Include="CompletedTaskGroup.cs" />
<Compile Include="DateButton.cs" />
@@ -105,6 +104,7 @@
<Compile Include="GtkApplicationBase.cs" />
<Compile Include="GtkLinuxApplication.cs" />
<Compile Include="GtkWinApplication.cs" />
+ <Compile Include="TreeModelListAdapter.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 af01121..d0511f8 100644
--- a/src/Gtk.Tasque/GtkApplicationBase.cs
+++ b/src/Gtk.Tasque/GtkApplicationBase.cs
@@ -202,19 +202,17 @@ namespace Tasque
return;
}
- OverdueTasks = TaskGroupModelFactory.CreateOverdueModel (Backend.Tasks);
- TodayTasks = TaskGroupModelFactory.CreateTodayModel (Backend.Tasks);
- TomorrowTasks = TaskGroupModelFactory.CreateTomorrowModel (Backend.Tasks);
+ OverdueTasks = TaskGroupModelFactory.CreateOverdueModel (Backend.Tasks, Preferences);
+ TodayTasks = TaskGroupModelFactory.CreateTodayModel (Backend.Tasks, Preferences);
+ TomorrowTasks = TaskGroupModelFactory.CreateTomorrowModel (Backend.Tasks, Preferences);
foreach (TaskGroupModel model in new TaskGroupModel[] { OverdueTasks, TodayTasks, TomorrowTasks })
{
if (model == null) {
continue;
}
-
- model.RowInserted += OnTooltipModelChanged;
- model.RowChanged += OnTooltipModelChanged;
- model.RowDeleted += OnTooltipModelChanged;
+
+ model.CollectionChanged += OnTooltipModelChanged;
}
}
@@ -226,9 +224,7 @@ namespace Tasque
continue;
}
- model.RowInserted -= OnTooltipModelChanged;
- model.RowChanged -= OnTooltipModelChanged;
- model.RowDeleted -= OnTooltipModelChanged;
+ model.CollectionChanged -= OnTooltipModelChanged;
}
}
diff --git a/src/Gtk.Tasque/GtkTray.cs b/src/Gtk.Tasque/GtkTray.cs
index 3801bea..a563628 100644
--- a/src/Gtk.Tasque/GtkTray.cs
+++ b/src/Gtk.Tasque/GtkTray.cs
@@ -24,6 +24,7 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
using System;
+using System.Linq;
using System.Text;
using Mono.Unix;
using Gtk;
@@ -81,7 +82,7 @@ namespace Tasque
var overdueTasks = application.OverdueTasks;
if (overdueTasks != null) {
- int count = overdueTasks.IterNChildren ();
+ int count = overdueTasks.Count ();
if (count > 0) {
sb.Append (String.Format (Catalog.GetPluralString ("{0} task is Overdue\n", "{0} tasks are Overdue\n", count), count));
@@ -90,7 +91,7 @@ namespace Tasque
var todayTasks = application.TodayTasks;
if (todayTasks != null) {
- int count = todayTasks.IterNChildren ();
+ int count = todayTasks.Count ();
if (count > 0) {
sb.Append (String.Format (Catalog.GetPluralString ("{0} task for Today\n", "{0} tasks for Today\n", count), count));
@@ -99,7 +100,7 @@ namespace Tasque
var tomorrowTasks = application.TomorrowTasks;
if (tomorrowTasks != null) {
- int count = tomorrowTasks.IterNChildren ();
+ int count = tomorrowTasks.Count ();
if (count > 0) {
sb.Append (String.Format (Catalog.GetPluralString ("{0} task for Tomorrow\n", "{0} tasks for Tomorrow\n", count), count));
diff --git a/src/Gtk.Tasque/PreferencesDialog.cs b/src/Gtk.Tasque/PreferencesDialog.cs
index 36ac198..341245c 100644
--- a/src/Gtk.Tasque/PreferencesDialog.cs
+++ b/src/Gtk.Tasque/PreferencesDialog.cs
@@ -51,7 +51,7 @@ namespace Tasque
Dictionary<int, IBackend> backendComboMap; // track backends
int selectedBackend;
Gtk.CheckButton showCompletedTasksCheckButton;
- Gtk.TreeModelFilter filteredCategories;
+ Gtk.ListStore filteredCategories;
List<string> categoriesToHide;
Gtk.TreeView categoriesTree;
@@ -598,8 +598,11 @@ namespace Tasque
}
IBackend backend = backendComboMap [selectedBackend];
- filteredCategories = new TreeModelFilter (backend.Categories, null);
- filteredCategories.VisibleFunc = FilterFunc;
+ filteredCategories = new ListStore (typeof (ICategory));
+ foreach (var item in backend.Categories) {
+ if (!(item == null || item is AllCategory))
+ filteredCategories.AppendValues (item);
+ }
categoriesTree.Model = filteredCategories;
}
@@ -607,27 +610,5 @@ namespace Tasque
{
RebuildCategoryTree ();
}
-
- /// <summary>
- /// Filter out the AllCategory
- /// </summary>
- /// <param name="model">
- /// A <see cref="Gtk.TreeModel"/>
- /// </param>
- /// <param name="iter">
- /// A <see cref="Gtk.TreeIter"/>
- /// </param>
- /// <returns>
- /// A <see cref="System.Boolean"/>
- /// </returns>
- protected bool FilterFunc (Gtk.TreeModel model,
- Gtk.TreeIter iter)
- {
- ICategory category = model.GetValue (iter, 0) as ICategory;
- if (category == null || category is AllCategory)
- return false;
-
- return true;
- }
}
}
diff --git a/src/Gtk.Tasque/RemoteControl.cs b/src/Gtk.Tasque/RemoteControl.cs
index 5e92b2c..2c310d2 100644
--- a/src/Gtk.Tasque/RemoteControl.cs
+++ b/src/Gtk.Tasque/RemoteControl.cs
@@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using Mono.Unix; // for Catalog.GetString ()
@@ -115,8 +116,7 @@ namespace Tasque
public string CreateTask (string categoryName, string taskName,
bool enterEditMode, bool parseDate)
{
- Gtk.TreeIter iter;
- Gtk.TreeModel model = application.Backend.Categories;
+ var model = application.Backend.Categories;
//
// Validate the input parameters. Don't allow null or empty strings
@@ -130,18 +130,11 @@ namespace Tasque
//
// Look for the specified category
//
- if (!model.GetIterFirst (out iter)) {
+ if (model.Count == 0) {
return string.Empty;
}
- ICategory category = null;
- do {
- ICategory tempCategory = model.GetValue (iter, 0) as ICategory;
- if (tempCategory.Name.ToLower ().CompareTo (categoryName.ToLower ()) == 0) {
- // Found a match
- category = tempCategory;
- }
- } while (model.IterNext (ref iter));
+ ICategory category = model.FirstOrDefault (c => c.Name.ToLower () == categoryName.ToLower ());
if (category == null) {
return string.Empty;
@@ -181,7 +174,7 @@ namespace Tasque
Catalog.GetString ("New task created."), // summary
Catalog.GetString (taskName), // body
Utilities.GetIcon ("tasque", 48));
- Application.ShowAppNotification (notify);
+ application.ShowAppNotification (notify);
#endif
@@ -198,19 +191,16 @@ namespace Tasque
{
List<string> categories = new List<string> ();
string[] emptyArray = categories.ToArray ();
+
+ var model = application.Backend.Categories;
- Gtk.TreeIter iter;
- Gtk.TreeModel model = application.Backend.Categories;
-
- if (!model.GetIterFirst (out iter))
+ if (model.Count == 0)
return emptyArray;
- do {
- ICategory category = model.GetValue (iter, 0) as ICategory;
- if (category is AllCategory)
- continue; // Prevent the AllCategory from being returned
- categories.Add (category.Name);
- } while (model.IterNext (ref iter));
+ foreach (var item in model) {
+ if (!(item is AllCategory))
+ categories.Add (item.Name);
+ }
return categories.ToArray ();
}
@@ -229,22 +219,14 @@ namespace Tasque
/// </returns>
public string[] GetTaskIds ()
{
- Gtk.TreeIter iter;
- Gtk.TreeModel model;
-
- ITask task;
- List<string> ids;
-
- ids = new List<string> ();
- model = application.Backend.Tasks;
-
- if (!model.GetIterFirst (out iter))
+ var ids = new List<string> ();
+ var model = application.Backend.Tasks;
+
+ if (model.Count == 0)
return new string[0];
-
- do {
- task = model.GetValue (iter, 0) as ITask;
- ids.Add (task.Id);
- } while (model.IterNext (ref iter));
+
+ foreach (var item in model)
+ ids.Add (item.Id);
return ids.ToArray ();
}
@@ -324,20 +306,18 @@ namespace Tasque
{
return false;
}
- Gtk.TreeIter iter;
- Gtk.TreeModel model = application.Backend.Categories;
+
+ var model = application.Backend.Categories;
- if (!model.GetIterFirst (out iter))
+ if (model.Count == 0)
return false;
- do {
- ICategory category = model.GetValue (iter, 0) as ICategory;
- if (string.Compare(category.Name,categoryName)==0)
- {
- task.Category = category;
+ foreach (var item in model) {
+ if (item.Name == categoryName) {
+ task.Category = item;
return true;
}
- } while (model.IterNext (ref iter));
+ }
return false;
}
@@ -513,22 +493,8 @@ namespace Tasque
/// </returns>
private ITask GetTaskById (string id)
{
- Gtk.TreeIter iter;
- Gtk.TreeModel model;
-
- ITask task = null;
- model = application.Backend.Tasks;
-
- if (model.GetIterFirst (out iter)) {
- do {
- task = model.GetValue (iter, 0) as ITask;
- if (task.Id.Equals (id)) {
- return task;
- }
- } while (model.IterNext (ref iter));
- }
-
- return task;
+ var model = application.Backend.Tasks;
+ return model.FirstOrDefault (t => t.Id == id);
}
INativeApplication application;
diff --git a/src/Gtk.Tasque/TaskGroup.cs b/src/Gtk.Tasque/TaskGroup.cs
index 122ea72..711f137 100644
--- a/src/Gtk.Tasque/TaskGroup.cs
+++ b/src/Gtk.Tasque/TaskGroup.cs
@@ -2,8 +2,11 @@
// User: boyd at 7:50 PMÂ2/11/2008
using System;
+using System.Collections.Generic;
+using System.Linq;
using Gdk;
using Gtk;
+using Gtk.Tasque;
namespace Tasque
{
@@ -16,7 +19,7 @@ namespace Tasque
{
Gtk.Label header;
TaskTreeView treeView;
- TaskGroupModel filteredTasks;
+ TreeModel treeModel;
Gtk.HBox extraWidgetHBox;
Gtk.Widget extraWidget;
@@ -24,7 +27,7 @@ namespace Tasque
#region Constructor
public TaskGroup (string groupName, DateTime rangeStart,
- DateTime rangeEnd, Gtk.TreeModel tasks, INativeApplication application)
+ DateTime rangeEnd, ICollection<ITask> tasks, INativeApplication application)
{
if (application == null)
throw new ArgumentNullException ("application");
@@ -38,11 +41,8 @@ namespace Tasque
// groups in the main TaskWindow. Reference Tomboy's NoteOfTheDay
// add-in for code that reacts on day changes.
- filteredTasks = CreateModel (rangeStart, rangeEnd, tasks);
-
- filteredTasks.ShowCompletedTasks =
- Application.Preferences.GetBool (
- Preferences.ShowCompletedTasksKey);
+ treeModel = CreateModel (rangeStart, rangeEnd, tasks);
+
Application.Preferences.SettingChanged += OnSettingChanged;
// TODO: Add something to watch events so that the group will
@@ -88,7 +88,7 @@ namespace Tasque
//
// Group TreeView
//
- treeView = new TaskTreeView (filteredTasks, application.Preferences);
+ treeView = new TaskTreeView (treeModel, application.Preferences);
treeView.Show ();
PackStart (treeView, true, true, 0);
@@ -162,12 +162,12 @@ namespace Tasque
/// </value>
public DateTime TimeRangeStart
{
- get { return filteredTasks.TimeRangeStart; }
+ get { return Model.TimeRangeStart; }
set {
- if (value == filteredTasks.TimeRangeStart)
+ if (value == Model.TimeRangeStart)
return;
- filteredTasks.SetRange (value, filteredTasks.TimeRangeEnd);
+ Model.SetRange (value, Model.TimeRangeEnd);
Refilter ();
}
}
@@ -177,12 +177,12 @@ namespace Tasque
/// </value>
public DateTime TimeRangeEnd
{
- get { return filteredTasks.TimeRangeEnd; }
+ get { return Model.TimeRangeEnd; }
set {
- if (value == filteredTasks.TimeRangeEnd)
+ if (value == Model.TimeRangeEnd)
return;
- filteredTasks.SetRange (filteredTasks.TimeRangeStart, value);
+ Model.SetRange (Model.TimeRangeStart, value);
Refilter ();
}
}
@@ -196,7 +196,6 @@ namespace Tasque
#region Public Methods
public void Refilter (ICategory selectedCategory)
{
- filteredTasks.Refilter ();
treeView.Refilter (selectedCategory);
}
@@ -325,12 +324,14 @@ namespace Tasque
#region Private Methods
protected INativeApplication Application { get; private set; }
+ protected TaskGroupModel Model { get; set; }
+
protected override void OnRealized ()
{
base.OnRealized ();
if (treeView.GetNumberOfTasks () == 0
- && (!filteredTasks.ShowCompletedTasks || hideWhenEmpty))
+ && (!Model.ShowCompletedTasks || hideWhenEmpty))
Hide ();
else
Show ();
@@ -342,11 +343,13 @@ namespace Tasque
header.Markup = GetHeaderMarkup (DisplayName);
}
- protected virtual TaskGroupModel CreateModel (DateTime rangeStart,
- DateTime rangeEnd,
- TreeModel tasks)
+ protected virtual TreeModel CreateModel (DateTime rangeStart,
+ DateTime rangeEnd,
+ ICollection<ITask> tasks)
{
- return new TaskGroupModel (rangeStart, rangeEnd, tasks);
+ Model = new TaskGroupModel (rangeStart, rangeEnd,
+ tasks, Application.Preferences);
+ return new TreeModelListAdapter<ITask> (Model);
}
/// <summary>
@@ -374,25 +377,13 @@ namespace Tasque
string selectedCategoryName =
Application.Preferences.Get (Preferences.SelectedCategoryKey);
+ ICategory category = null;
if (selectedCategoryName != null) {
- Gtk.TreeIter iter;
- Gtk.TreeModel model = Application.Backend.Categories;
-
- // Iterate through (yeah, I know this is gross!) and find the
- // matching category
- if (model.GetIterFirst (out iter)) {
- do {
- ICategory cat = model.GetValue (iter, 0) as ICategory;
- if (cat == null)
- continue; // Needed for some reason to prevent crashes from some backends
- if (cat.Name.CompareTo (selectedCategoryName) == 0) {
- return cat;
- }
- } while (model.IterNext (ref iter));
- }
+ var model = Application.Backend.Categories;
+ category = model.FirstOrDefault (c => c != null && c.Name == selectedCategoryName);
}
- return null;
+ return category;
}
/// <summary>
@@ -426,7 +417,7 @@ namespace Tasque
//Logger.Debug ("TaskGroup (\"{0}\").OnNumberOfTasksChanged ()", DisplayName);
// Check to see whether this group should be hidden or shown.
if (treeView.GetNumberOfTasks () == 0
- && (!filteredTasks.ShowCompletedTasks || hideWhenEmpty))
+ && (!Model.ShowCompletedTasks || hideWhenEmpty))
Hide ();
else
Show ();
@@ -455,10 +446,10 @@ namespace Tasque
bool newValue =
preferences.GetBool (Preferences.ShowCompletedTasksKey);
- if (filteredTasks.ShowCompletedTasks == newValue)
+ if (Model.ShowCompletedTasks == newValue)
return; // don't do anything if nothing has changed
- filteredTasks.ShowCompletedTasks = newValue;
+ Model.ShowCompletedTasks = newValue;
ICategory cat = GetSelectedCategory ();
if (cat != null)
diff --git a/src/Gtk.Tasque/TaskWindow.cs b/src/Gtk.Tasque/TaskWindow.cs
index 8411656..09f984c 100644
--- a/src/Gtk.Tasque/TaskWindow.cs
+++ b/src/Gtk.Tasque/TaskWindow.cs
@@ -30,6 +30,8 @@
using System;
using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
using Gdk;
using Gtk;
using Mono.Unix;
@@ -363,7 +365,12 @@ namespace Tasque
// Set up the combo box (after the above to set the current filter)
- categoryComboBox.Model = application.Backend.Categories;
+ var categoryComboStore = new ListStore (typeof(ICategory));
+ foreach (var item in application.Backend.Categories) {
+ categoryComboStore.AppendValues (item);
+ }
+
+ categoryComboBox.Model = categoryComboStore;
// Read preferences for the last-selected category and select it
string selectedCategoryName =
@@ -373,7 +380,6 @@ namespace Tasque
SelectCategory (selectedCategoryName);
}
-
#region Public Methods
/// <summary>
@@ -662,25 +668,10 @@ namespace Tasque
{
// This is disgustingly inefficient, but, oh well
int count = 0;
-
- Gtk.TreeIter iter;
- Gtk.TreeModel model = application.Backend.Tasks;
-
- if (!model.GetIterFirst (out iter))
- return 0;
-
- do {
- ITask task = model.GetValue (iter, 0) as ITask;
- if (task == null)
- continue;
- if (task.State != TaskState.Active
- && task.State != TaskState.Inactive)
- continue;
-
- if (category.ContainsTask (task))
- count++;
- } while (model.IterNext (ref iter));
-
+ var model = application.Backend.Tasks;
+ count = model.Count (t => t != null &&
+ (t.State == TaskState.Active || t.State == TaskState.Inactive) &&
+ category.ContainsTask (t));
return count;
}
@@ -723,24 +714,17 @@ namespace Tasque
}
}
- private void RebuildAddTaskMenu (Gtk.TreeModel categoriesModel)
+ private void RebuildAddTaskMenu (ICollection<ICategory> categoriesModel)
{
Gtk.Menu menu = new Menu ();
- Gtk.TreeIter iter;
- if (categoriesModel.GetIterFirst (out iter)) {
- do {
- ICategory category =
- categoriesModel.GetValue (iter, 0) as ICategory;
-
- if (category is AllCategory)
- continue; // Skip this one
-
- CategoryMenuItem item = new CategoryMenuItem (category);
- item.Activated += OnNewTaskByCategory;
- item.ShowAll ();
- menu.Add (item);
- } while (categoriesModel.IterNext (ref iter));
+ foreach (var cat in categoriesModel) {
+ if (cat is AllCategory)
+ continue;
+ var item = new CategoryMenuItem (cat);
+ item.Activated += OnNewTaskByCategory;
+ item.ShowAll ();
+ menu.Add (item);
}
addTaskButton.Menu = menu;
@@ -1115,13 +1099,12 @@ namespace Tasque
* here in order to enable changing categories. The list of available categories
* is pre-filtered as to not contain the current category and the AllCategory.
*/
- TreeModelFilter filteredCategories = new TreeModelFilter(application.Backend.Categories, null);
- filteredCategories.VisibleFunc = delegate(TreeModel t, TreeIter i) {
- ICategory category = t.GetValue (i, 0) as ICategory;
- if (category == null || category is AllCategory || category.Equals(clickedTask.Category))
- return false;
- return true;
- };
+
+ var filteredCategories = new ListStore (typeof (ICategory));
+ foreach (var cat in application.Backend.Categories) {
+ if (cat != null && !(cat is AllCategory) && !cat.Equals (clickedTask.Category))
+ filteredCategories.AppendValues (cat);
+ }
// The categories submenu is only created in case we actually provide at least one category.
if (filteredCategories.GetIterFirst(out iter))
diff --git a/src/Gtk.Tasque/TreeModelListAdapter.cs b/src/Gtk.Tasque/TreeModelListAdapter.cs
new file mode 100644
index 0000000..6a44756
--- /dev/null
+++ b/src/Gtk.Tasque/TreeModelListAdapter.cs
@@ -0,0 +1,105 @@
+//
+// TreeModelListAdapter.cs
+//
+// Author:
+// Antonius Riha <antoniusriha gmail com>
+//
+// Copyright (c) 2012 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.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Linq;
+using System.Runtime.InteropServices;
+using GLib;
+
+namespace Gtk.Tasque
+{
+ public class TreeModelListAdapter<T> : ListStore, IDisposable where T : INotifyPropertyChanged
+ {
+ public TreeModelListAdapter (IEnumerable<T> source) : base (typeof (T))
+ {
+ if (source == null)
+ throw new ArgumentNullException ("source");
+ var observableSource = source as INotifyCollectionChanged;
+ if (observableSource == null)
+ throw new ArgumentException ("source must be InotifyCollectionChanged");
+ this.source = source;
+ observableSource.CollectionChanged += HandleCollectionChanged;
+
+ // fill ListStore and register change events from all items in source,
+ // since we need to update the TreeView on a by-row basis
+ foreach (var item in source) {
+ AppendValues (item);
+ item.PropertyChanged += HandlePropertyChanged;
+ }
+ }
+
+ public void DisposeLocal ()
+ {
+ if (disposed)
+ return;
+
+ foreach (var item in source)
+ item.PropertyChanged -= HandlePropertyChanged;
+
+ disposed = true;
+ }
+
+ void HandlePropertyChanged (object sender, PropertyChangedEventArgs e)
+ {
+ // get index of changed item
+ var item = (T)sender;
+ var index = 0;
+ foreach (var element in source) {
+ if (item.Equals (element))
+ break;
+ index++;
+ }
+ TreeIter iter;
+ GetIter (out iter, new TreePath (new int [] { index }));
+ SetValue (iter, 0, sender);
+ }
+
+ void HandleCollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
+ {
+ TreeIter iter;
+ //FIXME: Only handles add and remove actions
+ switch (e.Action) {
+ case NotifyCollectionChangedAction.Add:
+ var newItem = (T)e.NewItems [0];
+ iter = Insert (e.NewStartingIndex);
+ SetValues (iter, newItem);
+ newItem.PropertyChanged += HandlePropertyChanged;
+ break;
+ case NotifyCollectionChangedAction.Remove:
+ var oldItem = (T)e.OldItems [0];
+ oldItem.PropertyChanged -= HandlePropertyChanged;
+ GetIter (out iter, new TreePath (new int [] { e.OldStartingIndex }));
+ Remove (ref iter);
+ break;
+ }
+ }
+
+ bool disposed;
+ IEnumerable<T> source;
+ }
+}
diff --git a/src/Gtk.Tasque/AbstractTask.cs b/src/libtasque/AbstractTask.cs
similarity index 88%
rename from src/Gtk.Tasque/AbstractTask.cs
rename to src/libtasque/AbstractTask.cs
index 3aa5a2d..74d2b09 100644
--- a/src/Gtk.Tasque/AbstractTask.cs
+++ b/src/libtasque/AbstractTask.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
namespace Tasque
{
@@ -181,5 +182,21 @@ namespace Tasque
return Name.CompareTo (task.Name);
}
#endregion // Private Methods
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public event PropertyChangingEventHandler PropertyChanging;
+
+ protected virtual void OnPropertyChanged (string propertyName)
+ {
+ if (PropertyChanged != null)
+ PropertyChanged (this, new PropertyChangedEventArgs (propertyName));
+ }
+
+ protected virtual void OnPropertyChanging (string propertyName)
+ {
+ if (PropertyChanging != null)
+ PropertyChanging (this, new PropertyChangingEventArgs (propertyName));
+ }
}
}
diff --git a/src/Gtk.Tasque/AllCategory.cs b/src/libtasque/AllCategory.cs
similarity index 95%
rename from src/Gtk.Tasque/AllCategory.cs
rename to src/libtasque/AllCategory.cs
index 1197f77..e080c25 100644
--- a/src/Gtk.Tasque/AllCategory.cs
+++ b/src/libtasque/AllCategory.cs
@@ -24,6 +24,7 @@ namespace Tasque
this.preferences = preferences;
categoriesToHide =
preferences.GetStringList (Preferences.HideInAllCategory);
+ categoriesToHide = preferences.GetStringList (Preferences.HideInAllCategory);
preferences.SettingChanged += OnSettingChanged;
}
diff --git a/src/libtasque/CategoryComparer.cs b/src/libtasque/CategoryComparer.cs
new file mode 100644
index 0000000..b81fcfa
--- /dev/null
+++ b/src/libtasque/CategoryComparer.cs
@@ -0,0 +1,45 @@
+//
+// CategoryComparer.cs
+//
+// Author:
+// Antonius Riha <antoniusriha gmail com>
+//
+// Copyright (c) 2012 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.Collections.Generic;
+
+namespace Tasque
+{
+ public class CategoryComparer : Comparer<ICategory>
+ {
+ public override int Compare (ICategory x, ICategory y)
+ {
+ if (x == null || y == null)
+ return 0;
+
+ if (x is AllCategory)
+ return -1;
+ else if (y is AllCategory)
+ return 1;
+
+ return (x.Name.CompareTo (y.Name));
+ }
+ }
+}
diff --git a/src/libtasque/CompletedTaskGroupModel.cs b/src/libtasque/CompletedTaskGroupModel.cs
index 0db7cbe..446d0e6 100644
--- a/src/libtasque/CompletedTaskGroupModel.cs
+++ b/src/libtasque/CompletedTaskGroupModel.cs
@@ -1,13 +1,14 @@
using System;
-using Gtk;
+using System.Collections.Generic;
namespace Tasque
{
public class CompletedTaskGroupModel : TaskGroupModel
{
- public CompletedTaskGroupModel (DateTime rangeStart, DateTime rangeEnd, TreeModel tasks)
- : base (rangeStart, rangeEnd, tasks)
+ public CompletedTaskGroupModel (DateTime rangeStart, DateTime rangeEnd,
+ ICollection<ITask> tasks, Preferences preferences)
+ : base (rangeStart, rangeEnd, tasks, preferences)
{
}
@@ -24,13 +25,12 @@ namespace Tasque
/// <returns>
/// A <see cref="System.Boolean"/>
/// </returns>
- protected override bool FilterTasks (TreeModel model, TreeIter iter)
+ protected override bool FilterTasks (ITask task)
{
// Don't show any task here if showCompletedTasks is false
if (!showCompletedTasks)
return false;
- ITask task = model.GetValue (iter, 0) as ITask;
if (task == null || task.State != TaskState.Completed)
return false;
diff --git a/src/libtasque/IBackend.cs b/src/libtasque/IBackend.cs
index 81fc8ee..d7c628d 100644
--- a/src/libtasque/IBackend.cs
+++ b/src/libtasque/IBackend.cs
@@ -2,6 +2,7 @@
// User: boyd at 7:02 AMÂ2/11/2008
using System;
+using System.Collections.Generic;
namespace Tasque.Backends
{
@@ -33,7 +34,7 @@ namespace Tasque.Backends
/// <value>
/// All the tasks provided by the backend.
/// </value>
- Gtk.TreeModel Tasks
+ ICollection<ITask> Tasks
{
get;
}
@@ -41,7 +42,7 @@ namespace Tasque.Backends
/// <value>
/// This returns all the ICategory items from the backend.
/// </value>
- Gtk.TreeModel Categories
+ ICollection<ICategory> Categories
{
get;
}
diff --git a/src/libtasque/ITask.cs b/src/libtasque/ITask.cs
index aeef9c1..f5a4446 100644
--- a/src/libtasque/ITask.cs
+++ b/src/libtasque/ITask.cs
@@ -3,10 +3,11 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
namespace Tasque
{
- public interface ITask
+ public interface ITask : INotifyPropertyChanged, INotifyPropertyChanging
{
#region Properties
/// <value>
diff --git a/src/libtasque/TaskComparer.cs b/src/libtasque/TaskComparer.cs
new file mode 100644
index 0000000..1285fe1
--- /dev/null
+++ b/src/libtasque/TaskComparer.cs
@@ -0,0 +1,40 @@
+//
+// TaskComparer.cs
+//
+// Author:
+// Antonius Riha <antoniusriha gmail com>
+//
+// Copyright (c) 2012 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.Collections.Generic;
+
+namespace Tasque
+{
+ public class TaskComparer : Comparer<ITask>
+ {
+ public override int Compare (ITask x, ITask y)
+ {
+ if (x == null || y == null)
+ return 0;
+
+ return (x.CompareTo (y));
+ }
+ }
+}
diff --git a/src/libtasque/TaskGroupModel.cs b/src/libtasque/TaskGroupModel.cs
index 0e8cdb1..7235258 100644
--- a/src/libtasque/TaskGroupModel.cs
+++ b/src/libtasque/TaskGroupModel.cs
@@ -1,18 +1,25 @@
using System;
-
-using Gtk;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Linq;
namespace Tasque
{
- public class TaskGroupModel : TreeModelFilter
+ /// <summary>
+ /// Task group model. Filters tasks.
+ /// </summary>
+ public class TaskGroupModel
+ : IEnumerable<ITask>, INotifyCollectionChanged, INotifyPropertyChanged, IDisposable
{
public bool ShowCompletedTasks
{
get { return showCompletedTasks; }
set {
showCompletedTasks = value;
- base.Refilter ();
+ OnPropertyChanged ("ShowCompletedTasks");
}
}
@@ -27,27 +34,184 @@ namespace Tasque
}
public TaskGroupModel (DateTime rangeStart, DateTime rangeEnd,
- Gtk.TreeModel tasks) : base (tasks, null)
+ ICollection<ITask> tasks, Preferences preferences)
{
+ if (preferences == null)
+ throw new ArgumentNullException ("preferences");
+ if (tasks == null)
+ throw new ArgumentNullException ("tasks");
+
this.timeRangeStart = rangeStart;
this.timeRangeEnd = rangeEnd;
+
+ taskChangeLog = new List<MementoTask> ();
+
+ showCompletedTasks = preferences.GetBool (
+ Preferences.ShowCompletedTasksKey);
+
+ originalTasks = tasks;
+ ((INotifyCollectionChanged)tasks).CollectionChanged += HandleCollectionChanged;
+
+ // register change events for each task
+ foreach (var item in tasks) {
+ item.PropertyChanging += HandlePropertyChanging;
+ item.PropertyChanged += HandlePropertyChanged;
+ }
+ }
- base.VisibleFunc = FilterTasks;
+ void HandlePropertyChanging (object sender, PropertyChangingEventArgs e)
+ {
+ var task = (ITask)sender;
+ var index = 0;
+ foreach (var item in this) {
+ if (item == task)
+ break;
+ index++;
+ }
+ var mementoTask = new MementoTask (task, index);
+ taskChangeLog.Add (mementoTask);
}
+ void HandlePropertyChanged (object sender, PropertyChangedEventArgs e)
+ {
+ var task = (ITask)sender;
+ var mementoTask = taskChangeLog.First (m => m.OriginalTaskRef == task);
+ taskChangeLog.Remove (mementoTask);
+
+ if (FilterTasks (mementoTask)) {
+ if (!FilterTasks (task)) {
+ var eArgs = new NotifyCollectionChangedEventArgs (
+ NotifyCollectionChangedAction.Remove, task, mementoTask.OriginalIndex);
+ OnCollectionChanged (eArgs);
+ }
+ } else {
+ if (FilterTasks (task)) {
+ var index = 0;
+ foreach (var item in this) {
+ if (item == task)
+ break;
+ index++;
+ }
+ var eArgs = new NotifyCollectionChangedEventArgs (
+ NotifyCollectionChangedAction.Add, task, index);
+ OnCollectionChanged (eArgs);
+ }
+ }
+ }
+
+ void HandleCollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
+ {
+ ITask changedItem = null;
+ var index = 0;
+ //FIXME: Only accounts for add and remove actions
+ switch (e.Action) {
+ case NotifyCollectionChangedAction.Add:
+ changedItem = (ITask)e.NewItems [0];
+
+ // register change event on task
+ changedItem.PropertyChanged += HandlePropertyChanged;
+ changedItem.PropertyChanging += HandlePropertyChanging;
+
+ if (!FilterTasks (changedItem))
+ return;
+
+ // calculate index without filtered out items
+ foreach (var item in this) {
+ if (item == changedItem)
+ break;
+ index++;
+ }
+
+ break;
+ case NotifyCollectionChangedAction.Remove:
+ changedItem = (ITask)e.OldItems [0];
+
+ // unregister change event on task
+ changedItem.PropertyChanged -= HandlePropertyChanged;
+ changedItem.PropertyChanging -= HandlePropertyChanging;
+
+ if (!FilterTasks (changedItem))
+ return;
+
+ var i = 0;
+ var enmrtr = originalTasks.GetEnumerator ();
+ bool enmrtrStatus;
+ while (enmrtrStatus = enmrtr.MoveNext ()) {
+ // move enumerator to right position
+ if (i++ < e.OldStartingIndex)
+ continue;
+
+ // right position: i == oldStartingIndex
+ if (FilterTasks (enmrtr.Current))
+ break;
+ }
+
+ if (enmrtrStatus) {
+ foreach (var task in this) {
+ if (task == enmrtr.Current)
+ break;
+ index++;
+ }
+ } else
+ index = this.Count ();
+
+ break;
+ }
+
+ var eArgs = new NotifyCollectionChangedEventArgs (e.Action, changedItem, index);
+ OnCollectionChanged (eArgs);
+ }
+
+ public IEnumerator<ITask> GetEnumerator ()
+ {
+ foreach (var item in originalTasks) {
+ if (FilterTasks (item))
+ yield return item;
+ }
+ }
+
public void SetRange (DateTime rangeStart, DateTime rangeEnd)
{
this.timeRangeStart = rangeStart;
this.timeRangeEnd = rangeEnd;
- base.Refilter ();
+ OnPropertyChanged ("TimeRangeStart");
+ OnPropertyChanged ("TimeRangeEnd");
+ }
+
+ public void Dispose ()
+ {
+ if (disposed)
+ return;
+
+ foreach (var item in originalTasks) {
+ item.PropertyChanged -= HandlePropertyChanged;
+ item.PropertyChanging -= HandlePropertyChanging;
+ }
+
+ disposed = true;
}
+ public event NotifyCollectionChangedEventHandler CollectionChanged;
+
+ protected virtual void OnCollectionChanged (NotifyCollectionChangedEventArgs e)
+ {
+ if (CollectionChanged != null)
+ CollectionChanged (this, e);
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ protected virtual void OnPropertyChanged (string propertyName)
+ {
+ if (PropertyChanged != null)
+ PropertyChanged (this, new PropertyChangedEventArgs (propertyName));
+ }
+
/// <summary>
/// Filter out tasks that don't fit within the group's date range
/// </summary>
- protected virtual bool FilterTasks (Gtk.TreeModel model, Gtk.TreeIter iter)
+ protected virtual bool FilterTasks (ITask task)
{
- ITask task = model.GetValue (iter, 0) as ITask;
if (task == null || task.State == TaskState.Deleted)
return false;
@@ -105,5 +269,128 @@ namespace Tasque
return true;
}
+
+ #region Explicit content
+ IEnumerator IEnumerable.GetEnumerator ()
+ {
+ return GetEnumerator ();
+ }
+ #endregion
+
+ ICollection<ITask> originalTasks;
+ List<MementoTask> taskChangeLog;
+ bool disposed;
+
+ class MementoTask : ITask
+ {
+ public MementoTask (ITask originalTask, int index)
+ {
+ OriginalTaskRef = originalTask;
+ Id = originalTask.Id;
+ Name = originalTask.Name;
+ DueDate = originalTask.DueDate;
+ CompletionDate = originalTask.CompletionDate;
+ IsComplete = originalTask.IsComplete;
+ Priority = originalTask.Priority;
+ HasNotes = originalTask.HasNotes;
+ SupportsMultipleNotes = originalTask.SupportsMultipleNotes;
+ State = originalTask.State;
+ Category = originalTask.Category;
+ TimerID = originalTask.TimerID;
+ }
+
+ public int OriginalIndex { get; private set; }
+
+ public ITask OriginalTaskRef { get; private set; }
+
+ public string Id { get; private set; }
+
+ public string Name { get; set; }
+
+ public DateTime DueDate { get; set; }
+
+ public DateTime CompletionDate { get; set; }
+
+ public bool IsComplete { get; private set; }
+
+ public TaskPriority Priority { get; set; }
+
+ public bool HasNotes { get; private set; }
+
+ public bool SupportsMultipleNotes { get; private set; }
+
+ public TaskState State { get; private set; }
+
+ public ICategory Category { get; set; }
+
+ public uint TimerID { get; set; }
+
+ #region Explicit content
+ List<INote> ITask.Notes {
+ get { throw new NotSupportedException (); }
+ }
+
+ void ITask.Activate ()
+ {
+ throw new NotSupportedException ();
+ }
+
+ void ITask.Inactivate ()
+ {
+ throw new NotSupportedException ();
+ }
+
+ void ITask.Complete ()
+ {
+ throw new NotSupportedException ();
+ }
+
+ void ITask.Delete ()
+ {
+ throw new NotSupportedException ();
+ }
+
+ INote ITask.CreateNote (string text)
+ {
+ throw new NotSupportedException ();
+ }
+
+ void ITask.DeleteNote (INote note)
+ {
+ throw new NotSupportedException ();
+ }
+
+ void ITask.SaveNote (INote note)
+ {
+ throw new NotSupportedException ();
+ }
+
+ int ITask.CompareTo (ITask task)
+ {
+ throw new NotSupportedException ();
+ }
+
+ int ITask.CompareToByCompletionDate (ITask task)
+ {
+ throw new NotSupportedException ();
+ }
+
+ event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged {
+ add {
+ throw new NotSupportedException ();
+ } remove {
+ throw new NotSupportedException ();
+ }
+ }
+
+ event PropertyChangingEventHandler INotifyPropertyChanging.PropertyChanging {
+ add {
+ throw new NotSupportedException ();
+ } remove {
+ throw new NotSupportedException ();
+ }
+ }
+ #endregion
+ }
}
}
diff --git a/src/libtasque/TaskGroupModelFactory.cs b/src/libtasque/TaskGroupModelFactory.cs
index 61e5819..a4e276c 100644
--- a/src/libtasque/TaskGroupModelFactory.cs
+++ b/src/libtasque/TaskGroupModelFactory.cs
@@ -1,13 +1,12 @@
using System;
-
-using Gtk;
+using System.Collections.Generic;
namespace Tasque
{
public static class TaskGroupModelFactory
{
- public static TaskGroupModel CreateTodayModel (TreeModel tasks)
+ public static TaskGroupModel CreateTodayModel (ICollection<ITask> tasks, Preferences preferences)
{
DateTime rangeStart = DateTime.Now;
rangeStart = new DateTime (rangeStart.Year, rangeStart.Month,
@@ -15,20 +14,20 @@ namespace Tasque
DateTime rangeEnd = DateTime.Now;
rangeEnd = new DateTime (rangeEnd.Year, rangeEnd.Month,
rangeEnd.Day, 23, 59, 59);
- return new TaskGroupModel (rangeStart, rangeEnd, tasks);
+ return new TaskGroupModel (rangeStart, rangeEnd, tasks, preferences);
}
- public static TaskGroupModel CreateOverdueModel (TreeModel tasks)
+ public static TaskGroupModel CreateOverdueModel (ICollection<ITask> tasks, Preferences preferences)
{
DateTime rangeStart = DateTime.MinValue;
DateTime rangeEnd = DateTime.Now.AddDays (-1);
rangeEnd = new DateTime (rangeEnd.Year, rangeEnd.Month, rangeEnd.Day,
23, 59, 59);
- return new TaskGroupModel (rangeStart, rangeEnd, tasks);
+ return new TaskGroupModel (rangeStart, rangeEnd, tasks, preferences);
}
- public static TaskGroupModel CreateTomorrowModel (TreeModel tasks)
+ public static TaskGroupModel CreateTomorrowModel (ICollection<ITask> tasks, Preferences preferences)
{
DateTime rangeStart = DateTime.Now.AddDays (1);
rangeStart = new DateTime (rangeStart.Year, rangeStart.Month,
@@ -37,7 +36,7 @@ namespace Tasque
rangeEnd = new DateTime (rangeEnd.Year, rangeEnd.Month,
rangeEnd.Day, 23, 59, 59);
- return new TaskGroupModel (rangeStart, rangeEnd, tasks);
+ return new TaskGroupModel (rangeStart, rangeEnd, tasks, preferences);
}
}
}
diff --git a/src/libtasque/libtasque.csproj b/src/libtasque/libtasque.csproj
index 3957724..b1df76e 100644
--- a/src/libtasque/libtasque.csproj
+++ b/src/libtasque/libtasque.csproj
@@ -30,16 +30,9 @@
<OutputPath>..\..\build\bin\lib\tasque</OutputPath>
</PropertyGroup>
<ItemGroup>
- <Reference Include="gtk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="Mono.Posix" />
- <Reference Include="atk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f">
- <Private>False</Private>
- </Reference>
- <Reference Include="glib-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f">
- <Private>False</Private>
- </Reference>
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
@@ -81,6 +74,10 @@
<Compile Include="TaskGroupModel.cs" />
<Compile Include="TaskGroupModelFactory.cs" />
<Compile Include="CompletedTaskGroupModel.cs" />
+ <Compile Include="AbstractTask.cs" />
+ <Compile Include="CategoryComparer.cs" />
+ <Compile Include="TaskComparer.cs" />
+ <Compile Include="AllCategory.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="DateFormatters\" />
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]