tasquer r2 - in trunk: . RtmNet data data/images data/sounds po src src/Backends src/Backends/Dummy src/Backends/EDS src/Backends/IceCore src/Backends/Rtm src/Backends/Sqlite



Author: btimothy
Date: Tue Mar 11 23:04:59 2008
New Revision: 2
URL: http://svn.gnome.org/viewvc/tasquer?rev=2&view=rev

Log:
2008-03-11  Boyd Timothy <btimothy gmail com> 

        * Initial check in of Tasquer to GNOME SVN.



Added:
   trunk/AUTHORS
   trunk/COPYING
   trunk/ChangeLog
   trunk/INSTALL
   trunk/MAINTAINERS
   trunk/Makefile.am
   trunk/Makefile.include
   trunk/NEWS
   trunk/README
   trunk/RtmNet/
   trunk/RtmNet/ApiKeyRequiredException.cs
   trunk/RtmNet/AssemblyInfo.cs
   trunk/RtmNet/Auth.cs
   trunk/RtmNet/AuthenticationRequiredException.cs
   trunk/RtmNet/Categories.cs
   trunk/RtmNet/Contacts.cs
   trunk/RtmNet/DateGranularity.cs
   trunk/RtmNet/Enums.cs
   trunk/RtmNet/GroupSearchResults.cs
   trunk/RtmNet/Groups.cs
   trunk/RtmNet/License.txt
   trunk/RtmNet/List.cs
   trunk/RtmNet/Makefile.am
   trunk/RtmNet/Methods.cs
   trunk/RtmNet/Note.cs
   trunk/RtmNet/Response.cs
   trunk/RtmNet/ResponseXmlException.cs
   trunk/RtmNet/Rtm.cs
   trunk/RtmNet/RtmApiException.cs
   trunk/RtmNet/RtmException.cs
   trunk/RtmNet/RtmNet.mdp
   trunk/RtmNet/RtmWebException.cs
   trunk/RtmNet/SignatureRequiredException.cs
   trunk/RtmNet/Tags.cs
   trunk/RtmNet/Task.cs
   trunk/RtmNet/User.cs
   trunk/RtmNet/Utils.cs
   trunk/RtmNet/example_app.config
   trunk/TODO
   trunk/autogen.sh   (contents, props changed)
   trunk/configure.ac
   trunk/create-task.py   (contents, props changed)
   trunk/data/
   trunk/data/Makefile.am
   trunk/data/images/
   trunk/data/images/Makefile.am
   trunk/data/images/clock-16-0.png   (contents, props changed)
   trunk/data/images/clock-16-0.svg
   trunk/data/images/clock-16-1.png   (contents, props changed)
   trunk/data/images/clock-16-10.png   (contents, props changed)
   trunk/data/images/clock-16-11.png   (contents, props changed)
   trunk/data/images/clock-16-2.png   (contents, props changed)
   trunk/data/images/clock-16-3.png   (contents, props changed)
   trunk/data/images/clock-16-4.png   (contents, props changed)
   trunk/data/images/clock-16-5.png   (contents, props changed)
   trunk/data/images/clock-16-6.png   (contents, props changed)
   trunk/data/images/clock-16-7.png   (contents, props changed)
   trunk/data/images/clock-16-8.png   (contents, props changed)
   trunk/data/images/clock-16-9.png   (contents, props changed)
   trunk/data/images/note-16.png   (contents, props changed)
   trunk/data/images/note-22.svg
   trunk/data/images/rtmLogo.png   (contents, props changed)
   trunk/data/images/tasky.svg
   trunk/data/images/tasquer-16.png   (contents, props changed)
   trunk/data/images/tasquer-22.png   (contents, props changed)
   trunk/data/images/tasquer-24.png   (contents, props changed)
   trunk/data/images/tasquer-32.png   (contents, props changed)
   trunk/data/images/tasquer-48.png   (contents, props changed)
   trunk/data/org.gnome.Tasquer.service.in
   trunk/data/sounds/
   trunk/data/sounds/Makefile.am
   trunk/data/sounds/notify.wav   (contents, props changed)
   trunk/data/tasquer.desktop.in
   trunk/po/
   trunk/po/ChangeLog
   trunk/po/LINGUAS
   trunk/po/Makefile.in.in
   trunk/po/POTFILES.in
   trunk/po/fi.po
   trunk/src/
   trunk/src/AbstractTask.cs
   trunk/src/AllCategory.cs
   trunk/src/Application.cs
   trunk/src/Backends/
   trunk/src/Backends/Dummy/
   trunk/src/Backends/Dummy/DummyBackend.cs
   trunk/src/Backends/Dummy/DummyCategory.cs
   trunk/src/Backends/Dummy/DummyNote.cs
   trunk/src/Backends/Dummy/DummyTask.cs
   trunk/src/Backends/EDS/
   trunk/src/Backends/EDS/EDSBackends.cs
   trunk/src/Backends/EDS/EDSCategory.cs
   trunk/src/Backends/EDS/EDSNote.cs
   trunk/src/Backends/EDS/EDSTask.cs
   trunk/src/Backends/IceCore/
   trunk/src/Backends/IceCore/IceBackend.cs
   trunk/src/Backends/IceCore/IceCategory.cs
   trunk/src/Backends/IceCore/IceNote.cs
   trunk/src/Backends/IceCore/IceTask.cs
   trunk/src/Backends/Rtm/
   trunk/src/Backends/Rtm/RtmBackend.cs
   trunk/src/Backends/Rtm/RtmCategory.cs
   trunk/src/Backends/Rtm/RtmNote.cs
   trunk/src/Backends/Rtm/RtmPreferencesWidget.cs
   trunk/src/Backends/Rtm/RtmTask.cs
   trunk/src/Backends/Sqlite/
   trunk/src/Backends/Sqlite/Database.cs
   trunk/src/Backends/Sqlite/SqliteBackend.cs
   trunk/src/Backends/Sqlite/SqliteCategory.cs
   trunk/src/Backends/Sqlite/SqliteNote.cs
   trunk/src/Backends/Sqlite/SqliteTask.cs
   trunk/src/CellRendererDate.cs
   trunk/src/CompletedTaskGroup.cs
   trunk/src/DateButton.cs
   trunk/src/Defines.cs.in
   trunk/src/IBackend.cs
   trunk/src/ICategory.cs
   trunk/src/INote.cs
   trunk/src/ITask.cs
   trunk/src/Logger.cs
   trunk/src/Makefile.am
   trunk/src/NoteDialog.cs
   trunk/src/NoteWidget.cs
   trunk/src/Preferences.cs
   trunk/src/PreferencesDialog.cs
   trunk/src/RemoteControl.cs
   trunk/src/RemoteControlProxy.cs
   trunk/src/RtmDeveloperKey.txt
   trunk/src/TaskCalendar.cs
   trunk/src/TaskGroup.cs
   trunk/src/TaskPriority.cs
   trunk/src/TaskState.cs
   trunk/src/TaskTreeView.cs
   trunk/src/TaskWindow.cs
   trunk/src/TrayLib.cs
   trunk/src/Utilities.cs
   trunk/src/tasquer.in
   trunk/src/tasquer.pc.in
   trunk/tasquer-opensuse-1click.png   (contents, props changed)
   trunk/tasquer.mdp
   trunk/tasquer.mds

Added: trunk/AUTHORS
==============================================================================
--- (empty file)
+++ trunk/AUTHORS	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,3 @@
+Boyd Timothy <btimothy gmail com>
+Calvin Gaisford <calvinrg gmail com>
+Brian Merrell <bgmerrell gmail com>

Added: trunk/COPYING
==============================================================================
--- (empty file)
+++ trunk/COPYING	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,21 @@
+Copyright (c) 2008 Novell, Inc.
+
+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.
+

Added: trunk/INSTALL
==============================================================================
--- (empty file)
+++ trunk/INSTALL	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,236 @@
+Installation Instructions
+*************************
+
+Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005 Free
+Software Foundation, Inc.
+
+This file is free documentation; the Free Software Foundation gives
+unlimited permission to copy, distribute and modify it.
+
+Basic Installation
+==================
+
+These are generic installation instructions.
+
+   The `configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation.  It uses
+those values to create a `Makefile' in each directory of the package.
+It may also create one or more `.h' files containing system-dependent
+definitions.  Finally, it creates a shell script `config.status' that
+you can run in the future to recreate the current configuration, and a
+file `config.log' containing compiler output (useful mainly for
+debugging `configure').
+
+   It can also use an optional file (typically called `config.cache'
+and enabled with `--cache-file=config.cache' or simply `-C') that saves
+the results of its tests to speed up reconfiguring.  (Caching is
+disabled by default to prevent problems with accidental use of stale
+cache files.)
+
+   If you need to do unusual things to compile the package, please try
+to figure out how `configure' could check whether to do them, and mail
+diffs or instructions to the address given in the `README' so they can
+be considered for the next release.  If you are using the cache, and at
+some point `config.cache' contains results you don't want to keep, you
+may remove or edit it.
+
+   The file `configure.ac' (or `configure.in') is used to create
+`configure' by a program called `autoconf'.  You only need
+`configure.ac' if you want to change it or regenerate `configure' using
+a newer version of `autoconf'.
+
+The simplest way to compile this package is:
+
+  1. `cd' to the directory containing the package's source code and type
+     `./configure' to configure the package for your system.  If you're
+     using `csh' on an old version of System V, you might need to type
+     `sh ./configure' instead to prevent `csh' from trying to execute
+     `configure' itself.
+
+     Running `configure' takes awhile.  While running, it prints some
+     messages telling which features it is checking for.
+
+  2. Type `make' to compile the package.
+
+  3. Optionally, type `make check' to run any self-tests that come with
+     the package.
+
+  4. Type `make install' to install the programs and any data files and
+     documentation.
+
+  5. You can remove the program binaries and object files from the
+     source code directory by typing `make clean'.  To also remove the
+     files that `configure' created (so you can compile the package for
+     a different kind of computer), type `make distclean'.  There is
+     also a `make maintainer-clean' target, but that is intended mainly
+     for the package's developers.  If you use it, you may have to get
+     all sorts of other programs in order to regenerate files that came
+     with the distribution.
+
+Compilers and Options
+=====================
+
+Some systems require unusual options for compilation or linking that the
+`configure' script does not know about.  Run `./configure --help' for
+details on some of the pertinent environment variables.
+
+   You can give `configure' initial values for configuration parameters
+by setting variables in the command line or in the environment.  Here
+is an example:
+
+     ./configure CC=c89 CFLAGS=-O2 LIBS=-lposix
+
+   *Note Defining Variables::, for more details.
+
+Compiling For Multiple Architectures
+====================================
+
+You can compile the package for more than one kind of computer at the
+same time, by placing the object files for each architecture in their
+own directory.  To do this, you must use a version of `make' that
+supports the `VPATH' variable, such as GNU `make'.  `cd' to the
+directory where you want the object files and executables to go and run
+the `configure' script.  `configure' automatically checks for the
+source code in the directory that `configure' is in and in `..'.
+
+   If you have to use a `make' that does not support the `VPATH'
+variable, you have to compile the package for one architecture at a
+time in the source code directory.  After you have installed the
+package for one architecture, use `make distclean' before reconfiguring
+for another architecture.
+
+Installation Names
+==================
+
+By default, `make install' installs the package's commands under
+`/usr/local/bin', include files under `/usr/local/include', etc.  You
+can specify an installation prefix other than `/usr/local' by giving
+`configure' the option `--prefix=PREFIX'.
+
+   You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files.  If you
+pass the option `--exec-prefix=PREFIX' to `configure', the package uses
+PREFIX as the prefix for installing programs and libraries.
+Documentation and other data files still use the regular prefix.
+
+   In addition, if you use an unusual directory layout you can give
+options like `--bindir=DIR' to specify different values for particular
+kinds of files.  Run `configure --help' for a list of the directories
+you can set and what kinds of files go in them.
+
+   If the package supports it, you can cause programs to be installed
+with an extra prefix or suffix on their names by giving `configure' the
+option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
+
+Optional Features
+=================
+
+Some packages pay attention to `--enable-FEATURE' options to
+`configure', where FEATURE indicates an optional part of the package.
+They may also pay attention to `--with-PACKAGE' options, where PACKAGE
+is something like `gnu-as' or `x' (for the X Window System).  The
+`README' should mention any `--enable-' and `--with-' options that the
+package recognizes.
+
+   For packages that use the X Window System, `configure' can usually
+find the X include and library files automatically, but if it doesn't,
+you can use the `configure' options `--x-includes=DIR' and
+`--x-libraries=DIR' to specify their locations.
+
+Specifying the System Type
+==========================
+
+There may be some features `configure' cannot figure out automatically,
+but needs to determine by the type of machine the package will run on.
+Usually, assuming the package is built to be run on the _same_
+architectures, `configure' can figure that out, but if it prints a
+message saying it cannot guess the machine type, give it the
+`--build=TYPE' option.  TYPE can either be a short name for the system
+type, such as `sun4', or a canonical name which has the form:
+
+     CPU-COMPANY-SYSTEM
+
+where SYSTEM can have one of these forms:
+
+     OS KERNEL-OS
+
+   See the file `config.sub' for the possible values of each field.  If
+`config.sub' isn't included in this package, then this package doesn't
+need to know the machine type.
+
+   If you are _building_ compiler tools for cross-compiling, you should
+use the option `--target=TYPE' to select the type of system they will
+produce code for.
+
+   If you want to _use_ a cross compiler, that generates code for a
+platform different from the build platform, you should specify the
+"host" platform (i.e., that on which the generated programs will
+eventually be run) with `--host=TYPE'.
+
+Sharing Defaults
+================
+
+If you want to set default values for `configure' scripts to share, you
+can create a site shell script called `config.site' that gives default
+values for variables like `CC', `cache_file', and `prefix'.
+`configure' looks for `PREFIX/share/config.site' if it exists, then
+`PREFIX/etc/config.site' if it exists.  Or, you can set the
+`CONFIG_SITE' environment variable to the location of the site script.
+A warning: not all `configure' scripts look for a site script.
+
+Defining Variables
+==================
+
+Variables not defined in a site shell script can be set in the
+environment passed to `configure'.  However, some packages may run
+configure again during the build, and the customized values of these
+variables may be lost.  In order to avoid this problem, you should set
+them in the `configure' command line, using `VAR=value'.  For example:
+
+     ./configure CC=/usr/local2/bin/gcc
+
+causes the specified `gcc' to be used as the C compiler (unless it is
+overridden in the site shell script).  Here is a another example:
+
+     /bin/bash ./configure CONFIG_SHELL=/bin/bash
+
+Here the `CONFIG_SHELL=/bin/bash' operand causes subsequent
+configuration-related scripts to be executed by `/bin/bash'.
+
+`configure' Invocation
+======================
+
+`configure' recognizes the following options to control how it operates.
+
+`--help'
+`-h'
+     Print a summary of the options to `configure', and exit.
+
+`--version'
+`-V'
+     Print the version of Autoconf used to generate the `configure'
+     script, and exit.
+
+`--cache-file=FILE'
+     Enable the cache: use and save the results of the tests in FILE,
+     traditionally `config.cache'.  FILE defaults to `/dev/null' to
+     disable caching.
+
+`--config-cache'
+`-C'
+     Alias for `--cache-file=config.cache'.
+
+`--quiet'
+`--silent'
+`-q'
+     Do not print messages saying which checks are being made.  To
+     suppress all normal output, redirect it to `/dev/null' (any error
+     messages will still be shown).
+
+`--srcdir=DIR'
+     Look for the package's source code in directory DIR.  Usually
+     `configure' can determine that directory automatically.
+
+`configure' also accepts some other, not widely useful, options.  Run
+`configure --help' for more details.
+

Added: trunk/MAINTAINERS
==============================================================================
--- (empty file)
+++ trunk/MAINTAINERS	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,2 @@
+Boyd Timothy <btimothy gmail com>
+Calvin Gaisford <calvinrg gmail com>

Added: trunk/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/Makefile.am	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,27 @@
+SUBDIRS = RtmNet src data po
+
+pkglib_DATA =  $(DLL_REFERENCES)
+DLL_REFERENCES =   
+EXTRA_DIST = \
+	$(DLL_REFERENCES) \
+	\
+        intltool-extract.in     \
+        intltool-merge.in       \
+        intltool-update.in      \
+	\
+	MAINTAINERS
+
+DISTCLEANFILES =                        \
+        \
+        intltool-extract                \
+        intltool-merge                  \
+        intltool-update                 \
+        po/.intltool-merge-cache
+
+DISTCHECK_CONFIGURE_FLAGS = --disable-schemas-install --disable-scrollkeeper
+
+# Ignore scrollkeeper issues for now.  @#*$& scrollkeeper (from Evince)
+distuninstallcheck_listfiles = find . -type f -print | grep -v scrollkeeper | grep -v /share/gnome/help/ | grep -v \.omf
+
+run: $(PROGRAM)
+	cd src && ./tasquer

Added: trunk/Makefile.include
==============================================================================
--- (empty file)
+++ trunk/Makefile.include	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,33 @@
+## Makefile.include
+
+## Directories
+
+#DIR_ADDINS_ADDINS = $(top_builddir)/Mono.Addins/Mono.Addins
+#DIR_ADDINS_GUI = $(top_builddir)/Mono.Addins/Mono.Addins.Gui
+#DIR_ADDINS_SETUP = $(top_builddir)/Mono.Addins/Mono.Addins.Setup
+
+## Links
+
+LINK_EXE =					\
+	-r:$(top_builddir)/src/Tasky.exe
+
+#if EXTERNAL_MONO_ADDINS
+#LINK_MONO_ADDINS = $(MONO_ADDINS_LIBS)
+#else
+#LINK_MONO_ADDINS =					
+#	-r:$(DIR_ADDINS_ADDINS)/Mono.Addins.dll		
+#	-r:$(DIR_ADDINS_SETUP)/Mono.Addins.Setup.dll	
+#	-r:$(DIR_ADDINS_GUI)/Mono.Addins.Gui.dll
+#endif
+
+## Build
+
+CSC_FLAGS = -debug
+CSC = gmcs $(CSC_FLAGS) -target:exe
+CSC_LIB = $(CSC) -target:library
+
+## Runtime
+
+MONO_DEBUGFLAGS = --debug
+RUNTIME = mono $(MONO_DEBUGFLAGS)
+

Added: trunk/NEWS
==============================================================================
--- (empty file)
+++ trunk/NEWS	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,41 @@
+Tasquer NEWS
+
+Website: http://live.gnome.org/Tasquer
+Bugs/Issues: http://code.google.com/p/tasquer/issues/list
+Mailing list: http://groups.google.com/group/tasquer
+
+Version 0.1.4
+* Added "Show Completed Tasks" preference.
+* Added user preference to filter what appears in "All" category.
+* Added drop-down menu to "Add Task" button for specific categories.
+* Added right-click on task for notes, delete, and edit.
+* Added basic note support when using RTM.
+* Added support for Evolution Data Server (EDS), Johnny Jacob.
+* Support for local-only tasks (SQLite Backend).
+* Fixed building without notify-sharp support.
+* Fixed ICECore Backend to build against Novell.IceDesktop.dll.
+
+Version 0.1.3
+* Added configure options to enable specific backends (--enable-backend-*).
+* Added dynamic backend support (backend can be switched at runtime).
+* Added support for third-party backends (backends can be a DLL).
+* Added ICEcore Backend (work in progress).
+* Added SQLite Backend (work in progress).
+
+Version 0.1.2
+* Bug fixes in build system
+* Fixed up Dates in UTC
+* Fixed up a problem reading from RTM
+
+Version 0.1.1
+* Added status bar message support.
+* Notification (notify-sharp) when new task is created via DBus.
+* Improved Tomboy UI (Tasquer button in toolbar).
+* Fixed "Choose Date..." behavior when task has no date currently set.
+* Fixed Tomboy crash if Tasquer is not running.
+
+Version 0.1.0
+* Initial release.
+* Online-only support for Remember the Milk (RTM).
+* Tasquer Add-in for Tomboy (tomboy-tasquer-addin.patch).
+

Added: trunk/README
==============================================================================
--- (empty file)
+++ trunk/README	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,11 @@
+Welcome to Tasquer, simple task management
+=====================================================================
+
+Website: http://live.gnome.org/Tasquer
+Bugs/Issues: http://code.google.com/p/tasky/issues/list
+Mailing list: http://groups.google.com/group/tasky
+
+======================================================================
+                          KNOWN ISSUES:
+======================================================================
+

Added: trunk/RtmNet/ApiKeyRequiredException.cs
==============================================================================
--- (empty file)
+++ trunk/RtmNet/ApiKeyRequiredException.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,17 @@
+using System;
+
+namespace RtmNet
+{
+	/// <summary>
+	/// Exception thrown is no API key is supplied.
+	/// </summary>
+	public class ApiKeyRequiredException : RtmException
+	{
+		/// <summary>
+		/// Default constructor.
+		/// </summary>
+		public ApiKeyRequiredException() : base("API Key is required for all method calls")
+		{
+		}
+	}
+}

Added: trunk/RtmNet/AssemblyInfo.cs
==============================================================================
--- (empty file)
+++ trunk/RtmNet/AssemblyInfo.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,70 @@
+using System;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Security;
+using System.Security.Permissions;
+using System.Runtime.InteropServices;
+
+//
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+//
+[assembly: AssemblyTitle("Rtm .Net Api Library")]
+[assembly: AssemblyDescription(".Net library for accessing rmilk.com Api functionality")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("tasquer")]
+[assembly: AssemblyCopyright("See website http://live.gnome.org/Tasquer";)]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+//
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Revision and Build Numbers 
+// by using the '*' as shown below:
+
+[assembly: AssemblyVersion("0.1.0.*")]
+
+//
+// In order to sign your assembly you must specify a key to use. Refer to the 
+// Microsoft .NET Framework documentation for more information on assembly signing.
+//
+// Use the attributes below to control which key is used for signing. 
+//
+// Notes: 
+//   (*) If no key is specified, the assembly is not signed.
+//   (*) KeyName refers to a key that has been installed in the Crypto Service
+//       Provider (CSP) on your machine. KeyFile refers to a file which contains
+//       a key.
+//   (*) If the KeyFile and the KeyName values are both specified, the 
+//       following processing occurs:
+//       (1) If the KeyName can be found in the CSP, that key is used.
+//       (2) If the KeyName does not exist and the KeyFile does exist, the key 
+//           in the KeyFile is installed into the CSP and used.
+//   (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility.
+//       When specifying the KeyFile, the location of the KeyFile should be
+//       relative to the project output directory which is
+//       %Project Directory%\obj\<configuration>. For example, if your KeyFile is
+//       located in the project directory, you would specify the AssemblyKeyFile 
+//       attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")]
+//   (*) Delay Signing is an advanced option - see the Microsoft .NET Framework
+//       documentation for more information on this.
+//
+[assembly: AssemblyDelaySign(true)]
+[assembly: AssemblyKeyName("")]
+
+#if !WindowsCE
+//[assembly: AssemblyKeyFile("..\\..\\RtmNet.snk")]
+//[assembly: AllowPartiallyTrustedCallers()]
+//[assembly: SecurityPermission(SecurityAction.RequestMinimum, Execution = true)]
+#endif
+
+[assembly: CLSCompliantAttribute(true)]
+[assembly: ComVisible(false)]

Added: trunk/RtmNet/Auth.cs
==============================================================================
--- (empty file)
+++ trunk/RtmNet/Auth.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,79 @@
+using System;
+
+namespace RtmNet
+{
+	/// <summary>
+	/// Used to specify the authentication levels needed for the Auth methods.
+	/// </summary>
+	public enum AuthLevel
+	{
+		/// <summary>
+		/// No access required - do not use this value!
+		/// </summary>
+		None, 
+		/// <summary>
+		/// Read only access is required by your application.
+		/// </summary>
+		Read, 
+		/// <summary>
+		/// Read and write access is required by your application.
+		/// </summary>
+		Write, 
+		/// <summary>
+		/// Read, write and delete access is required by your application.
+		/// Deleting does not mean deleting photos, just meta data such as tags.
+		/// </summary>
+		Delete
+	}
+
+	/// <summary>
+	/// Successful authentication returns a <see cref="Auth"/> object.
+	/// </summary>
+	public class Auth
+	{
+		private string _token;
+		private AuthLevel _permissions;
+		private FoundUser _user;
+
+		/// <summary>
+		/// The authentication token returned by the <see cref="Rtm.AuthGetToken"/> or <see cref="Rtm.AuthCheckToken"/> methods.
+		/// </summary>
+		public string Token
+		{
+			get { return _token; }
+			set { _token = value; }
+		}
+
+		/// <summary>
+		/// The permissions the current token allows the application to perform.
+		/// </summary>
+		public AuthLevel Permissions
+		{
+			get { return _permissions; }
+			set { _permissions = value; }
+		}
+
+		/// <summary>
+		/// The <see cref="User"/> object associated with the token. Readonly.
+		/// </summary>
+		public FoundUser User
+		{
+			get { return _user; }
+		}
+
+		/// <summary>
+		/// Creates a new instance of the <see cref="Auth"/> class.
+		/// </summary>
+		public Auth()
+		{
+		}
+
+		internal Auth(System.Xml.XmlElement element)
+		{
+			Token = element.SelectSingleNode("token").InnerText;
+			Permissions = (AuthLevel)Enum.Parse(typeof(AuthLevel), element.SelectSingleNode("perms").InnerText, true);
+			System.Xml.XmlNode node = element.SelectSingleNode("user");
+			_user = new FoundUser(node);
+		}
+	}
+}

Added: trunk/RtmNet/AuthenticationRequiredException.cs
==============================================================================
--- (empty file)
+++ trunk/RtmNet/AuthenticationRequiredException.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,14 @@
+using System;
+
+namespace RtmNet
+{
+	/// <summary>
+	/// Exception thrown when method requires authentication but no authentication token is supplied.
+	/// </summary>
+	public class AuthenticationRequiredException : RtmException
+	{
+		internal AuthenticationRequiredException() : base("Method requires authentication but no token supplied.")
+		{
+		}
+	}
+}

Added: trunk/RtmNet/Categories.cs
==============================================================================
--- (empty file)
+++ trunk/RtmNet/Categories.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,80 @@
+ïusing System.Xml.Serialization;
+using System.Xml.Schema;
+
+namespace RtmNet
+{
+	/// <summary>
+	/// Contains details of a category, including groups belonging to the category and sub categories.
+	/// </summary>
+	[System.Serializable]
+	public class Category
+	{
+		/// <summary>
+		/// The name for the category.
+		/// </summary>
+		[XmlAttribute("name", Form=XmlSchemaForm.Unqualified)]
+		public string CategoryName;
+    
+		/// <summary>
+		/// A forward slash delimited list of the parents of the current group.
+		/// </summary>
+		/// <remarks>
+		/// Can be matched against the list of PathIds to jump directly to a parent group.
+		/// </remarks>
+		/// <example>
+		/// Group Id 91, Romance will return "/Life/Romance" as the Path and "/90/91" as its PathIds
+		/// </example>
+		[XmlAttribute("path", Form=XmlSchemaForm.Unqualified)]
+		public string Path;
+    
+		/// <summary>
+		/// A forward slash delimited list of the ids of the parents of the current group.
+		/// </summary>
+		/// <remarks>
+		/// Can be matched against the Path to jump directly to a parent group.
+		/// </remarks>
+		/// <example>
+		/// Group Id 91, Romance will return "/Life/Romance" as the Path and "/90/91" as its PathIds
+		/// </example>
+		[XmlAttribute("pathids", Form=XmlSchemaForm.Unqualified)]
+		public string PathIds;
+
+		/// <summary>
+		/// An array of <see cref="SubCategory"/> items.
+		/// </summary>
+		[XmlElement("subcat", Form=XmlSchemaForm.Unqualified)]
+		public SubCategory[] SubCategories;
+
+		/// <summary>
+		/// An array of <see cref="Group"/> items, listing the groups within this category.
+		/// </summary>
+		[XmlElement("group", Form=XmlSchemaForm.Unqualified)]
+		public Group[] Groups;
+	}
+
+	/// <summary>
+	/// Holds details of a sub category, including its id, name and the number of groups in it.
+	/// </summary>
+	[System.Serializable]
+	public class SubCategory
+	{
+		/// <summary>
+		/// The id of the category.
+		/// </summary>
+		[XmlAttribute("id", Form=XmlSchemaForm.Unqualified)]
+		public long SubCategoryId;
+    
+		/// <summary>
+		/// The name of the category.
+		/// </summary>
+		[XmlAttribute("name", Form=XmlSchemaForm.Unqualified)]
+		public string SubCategoryName;
+    
+		/// <summary>
+		/// The number of groups found within the category.
+		/// </summary>
+		[XmlAttribute("count", Form=XmlSchemaForm.Unqualified)]
+		public long GroupCount;
+	}
+
+}
\ No newline at end of file

Added: trunk/RtmNet/Contacts.cs
==============================================================================
--- (empty file)
+++ trunk/RtmNet/Contacts.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,67 @@
+ïusing System.Xml.Serialization;
+using System.Xml.Schema;
+
+namespace RtmNet
+{
+	/// <summary>
+	/// Contains a list of <see cref="Contact"/> items for a given user.
+	/// </summary>
+	[System.Serializable]
+	public class Contacts
+	{
+		/// <summary>
+		/// An array of <see cref="Contact"/> items for the user.
+		/// </summary>
+		[XmlElement("contact", Form=XmlSchemaForm.Unqualified)]
+		public Contact[] ContactCollection = new Contact[0];
+	}
+
+	/// <summary>
+	/// Contains details of a contact for a particular user.
+	/// </summary>
+	[System.Serializable]
+	public class Contact
+	{
+		/// <summary>
+		/// The user id of the contact.
+		/// </summary>
+		[XmlAttribute("nsid", Form=XmlSchemaForm.Unqualified)]
+		public string UserId;
+    
+		/// <summary>
+		/// The username (or screen name) of the contact.
+		/// </summary>
+		[XmlAttribute("username", Form=XmlSchemaForm.Unqualified)]
+		public string UserName;
+    
+		/// <summary>
+		/// Is this contact marked as a friend contact?
+		/// </summary>
+		[XmlAttribute("friend", Form=XmlSchemaForm.Unqualified)]
+		public int IsFriend;
+    
+		/// <summary>
+		/// Is this user marked a family contact?
+		/// </summary>
+		[XmlAttribute("family", Form=XmlSchemaForm.Unqualified)]
+		public int IsFamily;
+    
+		/// <summary>
+		/// Unsure how to even set this!
+		/// </summary>
+		[XmlAttribute("ignored", Form=XmlSchemaForm.Unqualified)]
+		public int IsIgnored;
+
+		/// <summary>
+		/// Is the user online at the moment RtmLive)
+		/// </summary>
+		[XmlAttribute("online", Form=XmlSchemaForm.Unqualified)]
+		public int IsOnline;
+
+		/// <summary>
+		/// If the user is online, but marked as away, then this will contains their away message.
+		/// </summary>
+		[XmlText()]
+		public string AwayDescription;
+	}
+}
\ No newline at end of file

Added: trunk/RtmNet/DateGranularity.cs
==============================================================================
--- (empty file)
+++ trunk/RtmNet/DateGranularity.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,24 @@
+using System;
+
+namespace RtmNet
+{
+	/// <summary>
+	/// DateGranularity, used for setting taken date in <see cref="Rtm.PhotosSetDates(string, DateTime, DateGranularity)"/> 
+    /// or <see cref="Rtm.PhotosSetDates(string, DateTime, DateTime, DateGranularity)"/>.
+	/// </summary>
+	public enum DateGranularity
+	{
+		/// <summary>
+		/// The date specified is the exact date the photograph was taken.
+		/// </summary>
+		FullDate = 0,
+		/// <summary>
+		/// The date specified is the year and month the photograph was taken.
+		/// </summary>
+		YearMonthOnly = 4,
+		/// <summary>
+		/// The date specified is the year the photograph was taken.
+		/// </summary>
+		YearOnly = 6
+	}
+}

Added: trunk/RtmNet/Enums.cs
==============================================================================
--- (empty file)
+++ trunk/RtmNet/Enums.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,45 @@
+using System;
+using System.Xml.Serialization;
+
+namespace RtmNet
+{
+	/// <summary>
+	/// A list of service the Rtm.Net API Supports.
+	/// </summary>
+	/// <remarks>
+	/// Not all methods are supported by all service. Behaviour of the library may be unpredictable if not using Rtm
+	/// as your service.
+	/// </remarks>
+	public enum SupportedService
+	{
+		/// <summary>
+		/// Rtm - http://www.Rtm.com/services/api
+		/// </summary>
+		Rtm = 0
+	}
+	
+	/// <summary>
+	/// Used to specify where all tags must be matched or any tag to be matched.
+	/// </summary>
+	[Serializable]
+	public enum TagMode
+	{
+		/// <summary>
+		/// No tag mode specified.
+		/// </summary>
+		None,
+		/// <summary>
+		/// Any tag must match, but not all.
+		/// </summary>
+		AnyTag,
+		/// <summary>
+		/// All tags must be found.
+		/// </summary>
+		AllTags,
+		/// <summary>
+		/// Uncodumented and unsupported tag mode where boolean operators are supported.
+		/// </summary>
+		Boolean
+	}
+
+}

Added: trunk/RtmNet/GroupSearchResults.cs
==============================================================================
--- (empty file)
+++ trunk/RtmNet/GroupSearchResults.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,165 @@
+using System;
+using System.Xml;
+using System.Xml.XPath;
+
+namespace RtmNet
+{
+	/// <summary>
+	/// Returned by <see cref="Rtm.GroupsSearch(string)"/> methods.
+	/// </summary>
+	public class GroupSearchResults
+	{
+		private int page;
+
+		/// <summary>
+		/// The current page that the group search results represents.
+		/// </summary>
+		public int Page { get { return page; } }
+
+		private int pages;
+
+		/// <summary>
+		/// The total number of pages this search would return.
+		/// </summary>
+		public int Pages { get { return pages; } }
+
+		private int perPage;
+
+		/// <summary>
+		/// The number of groups returned per photo.
+		/// </summary>
+		public int PerPage { get { return perPage; } }
+
+		private int total;
+		/// <summary>
+		/// The total number of groups that where returned for the search.
+		/// </summary>
+		public int Total { get { return total; } }
+
+		private GroupSearchResultCollection groups = new GroupSearchResultCollection();
+
+		/// <summary>
+		/// The collection of groups returned for this search.
+		/// </summary>
+		/// <example>
+		/// The following code iterates through the list of groups returned:
+		/// <code>
+		/// GroupSearchResults results = Rtm.GroupsSearch("test");
+		/// foreach(GroupSearchResult result in results.Groups)
+		/// {
+		///		Console.WriteLine(result.GroupName);
+		/// }
+		/// </code>
+		/// </example>
+		public GroupSearchResultCollection Groups { get { return groups; } }
+
+		internal GroupSearchResults(XmlElement element)
+		{
+			page = Convert.ToInt32(element.GetAttribute("page"));
+			pages = Convert.ToInt32(element.GetAttribute("pages"));
+			perPage = Convert.ToInt32(element.GetAttribute("perpage"));
+			total = Convert.ToInt32(element.GetAttribute("total"));
+
+			XmlNodeList gs = element.SelectNodes("group");
+			groups.Clear();
+			for(int i = 0; i < gs.Count; i++)
+			{
+				groups.Add(new GroupSearchResult(gs[i]));
+			}
+		}
+	}
+
+	/// <summary>
+	/// Collection containing list of GroupSearchResult instances
+	/// </summary>
+	public class GroupSearchResultCollection : System.Collections.CollectionBase
+	{
+		/// <summary>
+		/// Method for adding a new <see cref="GroupSearchResult"/> to the collection.
+		/// </summary>
+		/// <param name="result"></param>
+		public void Add(GroupSearchResult result)
+		{
+			List.Add(result);
+		}
+
+		/// <summary>
+		/// Method for adding a collection of <see cref="GroupSearchResult"/> objects (contained within a
+		/// <see cref="GroupSearchResults"/> collection) to this collection.
+		/// </summary>
+		/// <param name="results"></param>
+		public void AddRange(GroupSearchResultCollection results)
+		{
+			foreach(GroupSearchResult result in results)
+				List.Add(result);
+		}
+
+		/// <summary>
+		/// Return a particular <see cref="GroupSearchResult"/> based on the index.
+		/// </summary>
+		public GroupSearchResult this[int index]
+		{
+			get { return (GroupSearchResult)List[index]; }
+			set { List[index] = value; }
+		}
+
+		/// <summary>
+		/// Removes the selected result from the collection.
+		/// </summary>
+		/// <param name="result">The result to remove.</param>
+		public void Remove(GroupSearchResult result)
+		{
+			List.Remove(result);
+		}
+
+		/// <summary>
+		/// Checks if the collection contains the result.
+		/// </summary>
+		/// <param name="result">The result to see if the collection contains.</param>
+		/// <returns>Returns true if the collecton contains the result, otherwise false.</returns>
+		public bool Contains(GroupSearchResult result)
+		{
+			return List.Contains(result);
+		}
+
+		/// <summary>
+		/// Copies the current collection to an array of <see cref="GroupSearchResult"/> objects.
+		/// </summary>
+		/// <param name="array"></param>
+		/// <param name="index"></param>
+		public void CopyTo(GroupSearchResult[] array, int index)
+		{
+			List.CopyTo(array, index);
+		}
+	}
+
+	/// <summary>
+	/// A class which encapsulates a single group search result.
+	/// </summary>
+	public class GroupSearchResult
+	{
+		private string _groupId;
+		private string _groupName;
+		private bool _eighteen;
+
+		/// <summary>
+		/// The group id for the result.
+		/// </summary>
+		public string GroupId { get { return _groupId; } }
+		/// <summary>
+		/// The group name for the result.
+		/// </summary>
+		public string GroupName { get { return _groupName; } }
+		/// <summary>
+		/// True if the group is an over eighteen (adult) group only.
+		/// </summary>
+		public bool EighteenPlus { get { return _eighteen; } }
+
+		internal GroupSearchResult(XmlNode node)
+		{
+			_groupId = node.Attributes["nsid"].Value;
+			_groupName = node.Attributes["name"].Value;
+			_eighteen = Convert.ToInt32(node.Attributes["eighteenplus"].Value)==1;
+		}
+	}
+}

Added: trunk/RtmNet/Groups.cs
==============================================================================
--- (empty file)
+++ trunk/RtmNet/Groups.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,416 @@
+using System;
+using System.Xml.Serialization;
+using System.Xml.Schema;
+using System.Xml;
+
+namespace RtmNet
+{
+	/// <summary>
+	/// Provides details of a particular group.
+	/// </summary>
+	/// <remarks>Used by <see cref="Rtm.GroupsBrowse()"/> and
+	/// <see cref="Rtm.GroupsBrowse(string)"/>.</remarks>
+	[System.Serializable]
+	public class Group
+	{
+		/// <summary>
+		/// The id of the group.
+		/// </summary>
+		[XmlAttribute("nsid", Form=XmlSchemaForm.Unqualified)]
+		public string GroupId;
+    
+		/// <summary>
+		/// The name of the group
+		/// </summary>
+		[XmlAttribute("name", Form=XmlSchemaForm.Unqualified)]
+		public string GroupName;
+
+		/// <summary>
+		/// The number of memebers of the group.
+		/// </summary>
+		[XmlAttribute("members", Form=XmlSchemaForm.Unqualified)]
+		public long Members;
+	}
+
+	/// <summary>
+	/// Provides details of a particular group.
+	/// </summary>
+	/// <remarks>
+	/// Used by the Url methods and <see cref="Rtm.GroupsGetInfo"/> method.
+	/// The reason for a <see cref="Group"/> and <see cref="GroupFullInfo"/> are due to xml serialization
+	/// incompatabilities.
+	/// </remarks>
+	[System.Serializable]
+	public class GroupFullInfo
+	{
+		internal GroupFullInfo()
+		{
+		}
+
+		internal GroupFullInfo(XmlNode node)
+		{
+			if( node.Attributes.GetNamedItem("id") != null )
+				GroupId = node.Attributes.GetNamedItem("id").Value;
+			if( node.SelectSingleNode("name") != null )
+				GroupName = node.SelectSingleNode("name").InnerText;
+			if( node.SelectSingleNode("description") != null )
+				Description = node.SelectSingleNode("description").InnerXml;
+			if( node.SelectSingleNode("members") != null )
+				Members = int.Parse(node.SelectSingleNode("members").InnerText);
+			if( node.SelectSingleNode("privacy") != null )
+				Privacy = (PoolPrivacy)int.Parse(node.SelectSingleNode("privacy").InnerText);
+
+			if( node.SelectSingleNode("throttle") != null )
+			{
+				XmlNode throttle = node.SelectSingleNode("throttle");
+				ThrottleInfo = new GroupThrottleInfo();
+				if( throttle.Attributes.GetNamedItem("count") != null )
+					ThrottleInfo.Count = int.Parse(throttle.Attributes.GetNamedItem("count").Value);
+				if( throttle.Attributes.GetNamedItem("mode") != null )
+					ThrottleInfo.setMode(throttle.Attributes.GetNamedItem("mode").Value);
+				if( throttle.Attributes.GetNamedItem("remaining") != null )
+					ThrottleInfo.Remaining = int.Parse(throttle.Attributes.GetNamedItem("remaining").Value);
+			}
+		}
+
+		/// <remarks/>
+		public string GroupId;
+    
+		/// <remarks/>
+		public string GroupName;
+
+		/// <remarks/>
+		public string Description;
+
+		/// <remarks/>
+		public long Members;
+	
+		/// <remarks/>
+		public PoolPrivacy Privacy;
+	
+		/// <remarks/>
+		public GroupThrottleInfo ThrottleInfo;
+
+		/// <summary>
+		/// Methods for automatically converting a <see cref="GroupFullInfo"/> object into
+		/// and instance of a <see cref="Group"/> object.
+		/// </summary>
+		/// <param name="groupInfo">The incoming object.</param>
+		/// <returns>The <see cref="Group"/> instance.</returns>
+		public static implicit operator Group( GroupFullInfo groupInfo )	
+		{
+			Group g = new Group();
+			g.GroupId = groupInfo.GroupId;
+			g.GroupName = groupInfo.GroupName;
+			g.Members = groupInfo.Members;
+
+			return g;
+		}
+
+		/// <summary>
+		/// Converts the current <see cref="GroupFullInfo"/> into an instance of the
+		/// <see cref="Group"/> class.
+		/// </summary>
+		/// <returns>A <see cref="Group"/> instance.</returns>
+		public Group ToGroup()
+		{
+			return (Group)this;
+		}
+
+	}
+
+	/// <summary>
+	/// Throttle information about a group (i.e. posting limit)
+	/// </summary>
+	public class GroupThrottleInfo
+	{
+		/// <summary>
+		/// The number of posts in each period allowed to this group.
+		/// </summary>
+		public int Count;
+
+		/// <summary>
+		/// The posting limit mode for a group.
+		/// </summary>
+		public GroupThrottleMode Mode;
+
+		internal void setMode(string mode)
+		{
+			switch(mode)
+			{
+				case "day":
+					Mode = GroupThrottleMode.PerDay;
+					break;
+				case "week":
+					Mode = GroupThrottleMode.PerWeek;
+					break;
+				case "month":
+					Mode = GroupThrottleMode.PerMonth;
+					break;
+				case "ever":
+					Mode = GroupThrottleMode.Ever;
+					break;
+				case "none":
+					Mode = GroupThrottleMode.NoLimit;
+					break;
+				case "disabled":
+					Mode = GroupThrottleMode.Disabled;
+					break;
+				default:
+					throw new ArgumentException(string.Format("Unknown mode found {0}", mode), "mode");
+			}
+		}
+
+		/// <summary>
+		/// The number of remainging posts allowed by this user. If unauthenticated then this will be zero.
+		/// </summary>
+		public int Remaining;
+	}
+
+	/// <summary>
+	/// The posting limit most for a group.
+	/// </summary>
+	public enum GroupThrottleMode
+	{
+		/// <summary>
+		/// Per day posting limit.
+		/// </summary>
+		PerDay,
+		/// <summary>
+		/// Per week posting limit.
+		/// </summary>
+		PerWeek,
+		/// <summary>
+		/// Per month posting limit.
+		/// </summary>
+		PerMonth,
+		/// <summary>
+		/// No posting limit.
+		/// </summary>
+		NoLimit,
+		/// <summary>
+		/// Posting limit is total number of photos in the group.
+		/// </summary>
+		Ever,
+		/// <summary>
+		/// Posting is disabled to this group.
+		/// </summary>
+		Disabled
+
+	}
+
+	/// <summary>
+	/// Information about a group the authenticated user is a member of.
+	/// </summary>
+	public class MemberGroupInfo
+	{
+		internal static MemberGroupInfo[] GetMemberGroupInfo(XmlNode node)
+		{
+			XmlNodeList list = node.SelectNodes("//group");
+			MemberGroupInfo[] infos = new MemberGroupInfo[list.Count];
+			for(int i = 0; i < infos.Length; i++)
+			{
+				infos[i] = new MemberGroupInfo(list[i]);
+			}
+			return infos;
+		}
+
+		internal MemberGroupInfo(XmlNode node)
+		{
+			if( node.Attributes["nsid"] != null )
+				_groupId = node.Attributes["nsid"].Value;
+			if( node.Attributes["name"] != null )
+				_groupName = node.Attributes["name"].Value;
+			if( node.Attributes["admin"] != null )
+				_isAdmin = node.Attributes["admin"].Value=="1";
+			if( node.Attributes["privacy"] != null )
+				_privacy = (PoolPrivacy)Enum.Parse(typeof(PoolPrivacy),node.Attributes["privacy"].Value, true);
+			if( node.Attributes["photos"] != null )
+				_numberOfPhotos = Int32.Parse(node.Attributes["photos"].Value);
+			if( node.Attributes["iconserver"] != null )
+				_iconServer = node.Attributes["iconserver"].Value;
+		}
+
+		private string _groupId;
+
+		/// <summary>
+		/// Property which returns the group id for the group.
+		/// </summary>
+		public string GroupId
+		{
+			get { return _groupId; }
+		}
+
+		private string _groupName;
+
+		/// <summary>The group name.</summary>
+		public string GroupName
+		{
+			get { return _groupName; }
+		}
+
+		private bool _isAdmin;
+
+		/// <summary>
+		/// True if the user is the admin for the group, false if they are not.
+		/// </summary>
+		public bool IsAdmin
+		{
+			get { return _isAdmin; }
+		}
+	
+		private long _numberOfPhotos;
+
+		/// <summary>
+		/// The number of photos currently in the group pool.
+		/// </summary>
+		public long NumberOfPhotos
+		{ 
+			get { return _numberOfPhotos; }
+		}
+
+		private PoolPrivacy _privacy;
+
+		/// <summary>
+		/// The privacy of the pool (see <see cref="PoolPrivacy"/>).
+		/// </summary>
+		public PoolPrivacy Privacy
+		{
+			get { return _privacy; }
+		}
+
+		private string _iconServer;
+
+		/// <summary>
+		/// The server number for the group icon.
+		/// </summary>
+		public string IconServer
+		{
+			get { return _iconServer; }
+		}
+
+		/// <summary>
+		/// The URL for the group icon.
+		/// </summary>
+		public Uri GroupIconUrl
+		{
+			get { return new Uri(String.Format("http://static.Rtm.com/{0}/buddyicons/{1}.jpg";, IconServer, GroupId)); }
+		}
+
+		/// <summary>
+		/// The URL for the group web page.
+		/// </summary>
+		public Uri GroupUrl
+		{
+			get { return new Uri(String.Format("http://www.Rtm.com/groups/{0}/";, GroupId)); }
+		}
+
+	}
+
+	/// <summary>
+	/// Information about public groups for a user.
+	/// </summary>
+	[System.Serializable]
+	public class PublicGroupInfo
+	{
+		internal static PublicGroupInfo[] GetPublicGroupInfo(XmlNode node)
+		{
+			XmlNodeList list = node.SelectNodes("//group");
+			PublicGroupInfo[] infos = new PublicGroupInfo[list.Count];
+			for(int i = 0; i < infos.Length; i++)
+			{
+				infos[i] = new PublicGroupInfo(list[i]);
+			}
+			return infos;
+		}
+
+		internal PublicGroupInfo(XmlNode node)
+		{
+			if( node.Attributes["nsid"] != null )
+				_groupId = node.Attributes["nsid"].Value;
+			if( node.Attributes["name"] != null )
+				_groupName = node.Attributes["name"].Value;
+			if( node.Attributes["admin"] != null )
+				_isAdmin = node.Attributes["admin"].Value=="1";
+			if( node.Attributes["eighteenplus"] != null )
+				_isEighteenPlus = node.Attributes["eighteenplus"].Value=="1";
+		}
+
+		private string _groupId;
+    
+		/// <summary>
+		/// Property which returns the group id for the group.
+		/// </summary>
+		public string GroupId
+		{
+			get { return _groupId; }
+		}
+
+		private string _groupName;
+
+		/// <summary>The group name.</summary>
+		public string GroupName
+		{
+			get { return _groupName; }
+		}
+
+		private bool _isAdmin;
+
+		/// <summary>
+		/// True if the user is the admin for the group, false if they are not.
+		/// </summary>
+		public bool IsAdmin
+		{
+			get { return _isAdmin; }
+		}
+
+		private bool _isEighteenPlus;
+	
+		/// <summary>
+		/// Will contain 1 if the group is restricted to people who are 18 years old or over, 0 if it is not.
+		/// </summary>
+		public bool EighteenPlus
+		{
+			get { return _isEighteenPlus; }
+		}
+
+		/// <summary>
+		/// The URL for the group web page.
+		/// </summary>
+		public Uri GroupUrl
+		{
+			get { return new Uri(String.Format("http://www.Rtm.com/groups/{0}/";, GroupId)); }
+		}
+	}
+
+	/// <summary>
+	/// The various pricay settings for a group.
+	/// </summary>
+	[System.Serializable]
+	public enum PoolPrivacy
+	{
+		/// <summary>
+		/// No privacy setting specified.
+		/// </summary>
+		[XmlEnum("0")]
+		None = 0,
+
+		/// <summary>
+		/// The group is a private group. You cannot view pictures or posts until you are a 
+		/// member. The group is also invite only.
+		/// </summary>
+		[XmlEnum("1")]
+		Private = 1,
+		/// <summary>
+		/// A public group where you can see posts and photos in the group. The group is however invite only.
+		/// </summary>
+		[XmlEnum("2")]
+		InviteOnlyPublic = 2,
+		/// <summary>
+		/// A public group.
+		/// </summary>
+		[XmlEnum("3")]
+		OpenPublic = 3
+	}
+
+}

Added: trunk/RtmNet/License.txt
==============================================================================
--- (empty file)
+++ trunk/RtmNet/License.txt	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,27 @@
+Rtm .Net API Library
+-----------------------
+Rtm .Net API librar is based on the Flickr .Net API Library.
+
+It is released under the LGPL (GNU Lesser General Public License), a copy of which can be found at the link below.
+http://www.gnu.org/copyleft/lesser.html
+
+
+All source code and information supplied as part of the Flick .Net API Library is copyright too its contributers.
+
+The source code has been released under a dual license - meaning you can use either licensed version of the library with your code.
+
+It is released under the Common Public License 1.0, a copy of which can be found at the link below.
+http://www.opensource.org/licenses/cpl.php
+
+It is released under the LGPL (GNU Lesser General Public License), a copy of which can be found at the link below.
+http://www.gnu.org/copyleft/lesser.html
+
+You are free to distribute copies of this Program in its compiled, unaltered form, including, but not limited to using it (in library form) in other .Net application, without use of this license.
+You are free to modify this code, however you must release the resulting Contributions under the CPL or the LGPL (or compatible license).
+
+
+Rtm API Key
+--------------
+I do not have permission to include an Rtm API Key in any distribution which includes source code.
+If I do by accident then it is not included in the above license and should not be used. 
+

Added: trunk/RtmNet/List.cs
==============================================================================
--- (empty file)
+++ trunk/RtmNet/List.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,76 @@
+ïusing System;
+using System.Xml.Serialization;
+using System.Xml.Schema;
+using System.Collections;
+
+namespace RtmNet
+{
+	/// <summary>
+	/// Contains a list of <see cref="Contact"/> items for a given user.
+	/// </summary>
+	[System.Serializable]
+	public class Lists
+	{
+		/// <summary>
+		/// An array of <see cref="Contact"/> items for the user.
+		/// </summary>
+		[XmlElement("list", Form=XmlSchemaForm.Unqualified)]
+		public List[] listCollection = new List[0];
+	}
+
+	/// <summary>
+	/// Contains details of a contact for a particular user.
+	/// </summary>
+	[System.Serializable]
+	public class List
+	{
+		/// <summary>
+		/// The user id of the contact.
+		/// </summary>
+		[XmlAttribute("id", Form=XmlSchemaForm.Unqualified)]
+		public string ID;
+    
+		/// <summary>
+		/// The username (or screen name) of the contact.
+		/// </summary>
+		[XmlAttribute("name", Form=XmlSchemaForm.Unqualified)]
+		public string Name;
+    
+		/// <summary>
+		/// Is this contact marked as a friend contact?
+		/// </summary>
+		[XmlAttribute("deleted", Form=XmlSchemaForm.Unqualified)]
+		public int Deleted;
+    
+		/// <summary>
+		/// Is this user marked a family contact?
+		/// </summary>
+		[XmlAttribute("locked", Form=XmlSchemaForm.Unqualified)]
+		public int Locked;
+    
+		/// <summary>
+		/// Unsure how to even set this!
+		/// </summary>
+		[XmlAttribute("archived", Form=XmlSchemaForm.Unqualified)]
+		public int Archived;
+
+		/// <summary>
+		/// Is the user online at the moment RtmLive)
+		/// </summary>
+		[XmlAttribute("position", Form=XmlSchemaForm.Unqualified)]
+		public int Position;
+
+		/// <summary>
+		/// Is the user online at the moment RtmLive)
+		/// </summary>
+		[XmlAttribute("smart", Form=XmlSchemaForm.Unqualified)]
+		public int Smart;
+
+		
+		/// <summary>
+		/// An array of TaskSeries objects
+		/// </summary>
+		[XmlElement("taskseries", Form=XmlSchemaForm.Unqualified)]
+		public TaskSeries[] TaskSeriesCollection = new TaskSeries[0];		
+	}	
+}
\ No newline at end of file

Added: trunk/RtmNet/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/RtmNet/Makefile.am	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,64 @@
+CSC = gmcs
+
+TARGET = RtmNet.dll
+
+if ENABLE_DEBUG
+CSFLAGS =  -t:library -noconfig -codepage:utf8 -warn:4 -debug -d:DEBUG
+endif
+if ENABLE_RELEASE
+CSFLAGS =  -t:library -noconfig -codepage:utf8 -warn:4
+endif
+
+CSFILES = \
+	$(srcdir)/ApiKeyRequiredException.cs		\
+	$(srcdir)/AssemblyInfo.cs			\
+	$(srcdir)/Auth.cs				\
+	$(srcdir)/AuthenticationRequiredException.cs	\
+	$(srcdir)/Categories.cs				\
+	$(srcdir)/Contacts.cs				\
+	$(srcdir)/DateGranularity.cs			\
+	$(srcdir)/Enums.cs				\
+	$(srcdir)/Groups.cs				\
+	$(srcdir)/GroupSearchResults.cs			\
+	$(srcdir)/List.cs				\
+	$(srcdir)/Methods.cs				\
+	$(srcdir)/Note.cs				\
+	$(srcdir)/Response.cs				\
+	$(srcdir)/ResponseXmlException.cs		\
+	$(srcdir)/RtmApiException.cs			\
+	$(srcdir)/Rtm.cs				\
+	$(srcdir)/RtmException.cs			\
+	$(srcdir)/RtmWebException.cs			\
+	$(srcdir)/SignatureRequiredException.cs		\
+	$(srcdir)/Tags.cs				\
+	$(srcdir)/Task.cs				\
+	$(srcdir)/User.cs				\
+	$(srcdir)/Utils.cs
+
+
+RESOURCES = 
+
+ASSEMBLIES =  \
+	-r:System \
+	-r:System.Web \
+	-r:System.Xml
+
+$(TARGET): $(CSFILES)
+	$(CSC) -unsafe -out:$@ $(CSFLAGS) $^ $(ASSEMBLIES) $(RESOURCES)
+
+tasquerlibdir = $(prefix)/lib/tasquer
+tasquerlib_DATA = $(TARGET)	
+
+bin_SCRIPTS = $(WRAPPER)
+
+
+EXTRA_DIST = \
+	$(CSFILES)
+
+CLEANFILES = \
+	$(TARGET)					\
+	$(TARGET).mdb
+
+DISTCLEANFILES =                        \
+	$(TARGET)			\
+	$(TARGET).mdb

Added: trunk/RtmNet/Methods.cs
==============================================================================
--- (empty file)
+++ trunk/RtmNet/Methods.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,136 @@
+using System;
+using System.Xml;
+using System.Xml.Serialization;
+using System.Xml.Schema;
+
+namespace RtmNet
+{
+	/// <summary>
+	/// Summary description for Methods.
+	/// </summary>
+	public class Methods
+	{
+		private Methods()
+		{
+		}
+
+		internal static string[] GetMethods(XmlElement element)
+		{
+			XmlNodeList nodes = element.SelectNodes("method");
+			string[] _methods = new string[nodes.Count];
+			for(int i = 0; i < nodes.Count; i++)
+			{
+				_methods[i] = nodes[i].Value;
+			}
+			return _methods;
+		}
+	}
+
+	/// <summary>
+	/// A method supported by the Rtm API.
+	/// </summary>
+	/// <remarks>
+	/// See <a href="http://www.Rtm.com/services/api";>Rtm API Documentation</a> for a complete list
+	/// of methods.
+	/// </remarks>
+	[Serializable]
+	public class Method
+	{
+		/// <summary>
+		/// Default constructor.
+		/// </summary>
+		public Method()
+		{
+		}
+
+		/// <summary>
+		/// The name of the method.
+		/// </summary>
+		[XmlAttribute("name", Form=XmlSchemaForm.Unqualified)]
+		public string Name;
+
+		/// <summary>
+		/// The description of the method.
+		/// </summary>
+		[XmlElement("description", Form=XmlSchemaForm.Unqualified)]
+		public string Description;
+
+		/// <summary>
+		/// An example response for the method.
+		/// </summary>
+		[XmlElement("response", Form=XmlSchemaForm.Unqualified)]
+		public string Response;
+
+		/// <summary>
+		/// An explanation of the example response for the method.
+		/// </summary>
+		[XmlElement("explanation", Form=XmlSchemaForm.Unqualified)]
+		public string Explanation;
+
+		/// <summary>
+		/// The arguments of the method.
+		/// </summary>
+		[XmlElement("arguments", Form=XmlSchemaForm.Unqualified)]
+		public Arguments Arguments;
+
+		/// <summary>
+		/// The possible errors that could be returned by the method.
+		/// </summary>
+		[XmlArray()]
+		[XmlArrayItem("error", typeof(MethodError), Form=XmlSchemaForm.Unqualified)]
+		public MethodError[] Errors;
+
+	}
+
+	/// <summary>
+	/// An instance containing a collection of <see cref="Argument"/> instances.
+	/// </summary>
+	[Serializable]
+	public class Arguments
+	{
+		/// <summary>
+		/// A collection of <see cref="Argument"/> instances.
+		/// </summary>
+		[XmlElement("argument", Form=XmlSchemaForm.Unqualified)]
+		public Argument[] ArgumentCollection;
+	}
+
+	/// <summary>
+	/// An argument for a method.
+	/// </summary>
+	[Serializable]
+	public class Argument
+	{
+		/// <summary>
+		/// The name of the argument.
+		/// </summary>
+		[XmlElement("name")]
+		public string ArgumentName;
+
+		/// <summary>
+		/// Is the argument optional or not.
+		/// </summary>
+		[XmlElement("optional")]
+		public int Optional;
+
+		/// <summary>
+		/// The description of the argument.
+		/// </summary>
+		[XmlText()]
+		public string ArgumentDescription;
+	}
+
+	/// <summary>
+	/// A possible error that a method can return.
+	/// </summary>
+	[Serializable]
+	public class MethodError
+	{
+		/// <summary>
+		/// The code for the error.
+		/// </summary>
+		[XmlElement("code")]
+		public int Code;
+
+	}
+}

Added: trunk/RtmNet/Note.cs
==============================================================================
--- (empty file)
+++ trunk/RtmNet/Note.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,103 @@
+// Note.cs created with MonoDevelop
+// User: calvin at 11:28 PMÂ2/12/2008
+//
+// To change standard headers go to Edit->Preferences->Coding->Standard Headers
+//
+
+using System;
+using System.Xml.Serialization;
+using System.Xml.Schema;
+
+namespace RtmNet
+{
+	/// <summary>
+	/// Contains a list of <see cref="Contact"/> items for a given user.
+	/// </summary>
+	[System.Serializable]
+	public class Notes
+	{
+		/// <summary>
+		/// An array of <see cref="Contact"/> items for the user.
+		/// </summary>
+		[XmlElement("note", Form=XmlSchemaForm.Unqualified)]
+		public Note[] NoteCollection = new Note[0];
+	}
+
+	/// <remarks/>
+	[System.Serializable]
+	public class Note
+	{
+		private string id;
+		private string rawCreated;
+		private string rawModified;
+		private string title;	
+		private string text;		
+		private DateTime created = DateTime.MinValue;
+		private DateTime modified = DateTime.MinValue;
+
+		/// <remarks/>
+		[XmlAttribute("id", Form=XmlSchemaForm.Unqualified)]
+		public string ID { get { return id; } set { id = value; } }
+    
+		/// <remarks/>
+		[XmlAttribute("created", Form=XmlSchemaForm.Unqualified)]
+		public string RawCreated
+		{
+			get { return rawCreated; }
+			set {
+				if(value.Length > 0) {
+					rawCreated = value;
+					created = Utils.DateStringToDateTime(rawCreated);
+				}
+			}
+		}
+    
+		/// <summary>
+		/// Converts the raw created field to a <see cref="DateTime"/>.
+		/// </summary>	
+		[XmlIgnore]
+		public DateTime Created
+		{
+			get { return created; }
+			set { created = value; }
+		}
+
+		/// <remarks/>
+		[XmlAttribute("modified", Form=XmlSchemaForm.Unqualified)]
+		public string RawModified
+		{
+			get { return rawModified; }
+			set {
+				if(value.Length > 0) {
+					rawModified = value;
+					modified = Utils.DateStringToDateTime(rawModified);
+				}
+			}
+		}
+		/// <summary>
+		/// Converts the raw modified field to a <see cref="DateTime"/>.
+		/// </summary>	
+		[XmlIgnore]
+		public DateTime Modified
+		{
+			get { return modified; }
+			set { modified = value; }
+		}
+
+
+		/// <summary>
+		/// Is this contact marked as a friend contact?
+		/// </summary>
+		[XmlAttribute("title", Form=XmlSchemaForm.Unqualified)]
+		public string Title { get { return title; } set { title = value; } }		
+
+
+
+		/// <summary>
+		/// The text of the note
+		/// </summary>
+		[XmlText()]
+		public string Text { get { return text; } set { text = value; } }		
+
+	}
+}

Added: trunk/RtmNet/Response.cs
==============================================================================
--- (empty file)
+++ trunk/RtmNet/Response.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,115 @@
+ïusing System;
+using System.Xml;
+using System.Xml.Serialization;
+using System.Xml.Schema;
+
+namespace RtmNet
+{
+	/// <summary>
+	/// The root object returned byRtm. Used with Xml Serialization to get the relevant object.
+	/// It is internal to the RtmNet API Library and should not be used elsewhere.
+	/// </summary>
+	[XmlRoot("rsp", Namespace="", IsNullable=false)]
+	[Serializable]
+	public class Response 
+	{
+
+		/// <remarks/>
+		[XmlElement("contacts", Form=XmlSchemaForm.Unqualified)]
+		public Contacts Contacts;
+
+		/// <remarks/>
+		[XmlElement("lists", Form=XmlSchemaForm.Unqualified)]
+		public Lists Lists;
+
+		/// <remarks/>
+		[XmlElement("tasks", Form=XmlSchemaForm.Unqualified)]
+		public Tasks Tasks;
+
+		/// <remarks/>
+		[XmlAttribute("stat", Form=XmlSchemaForm.Unqualified)]
+		public ResponseStatus Status;
+		
+		/// <remarks/>
+		[XmlElement("list", Form=XmlSchemaForm.Unqualified)]
+		public List List;
+		
+		/// <remarks/>
+		[XmlElement("timeline", Form=XmlSchemaForm.Unqualified)]
+		public string Timeline;
+
+		/// <remarks/>
+		[XmlElement("note", Form=XmlSchemaForm.Unqualified)]
+		public Note Note;
+		
+		/// <summary>
+		/// If an error occurs the Error property is populated with 
+		/// a <see cref="ResponseError"/> instance.
+		/// </summary>
+		[XmlElement("err", Form=XmlSchemaForm.Unqualified)]
+		public ResponseError Error;
+
+		/// <summary>
+		/// A <see cref="Method"/> instance.
+		/// </summary>
+		[XmlElement("method", Form=XmlSchemaForm.Unqualified)]
+		public Method Method;
+
+		/// <summary>
+		/// If using Rtm.test.echo this contains all the other elements not covered above.
+		/// </summary>
+		/// <remarks>
+		/// t is an array of <see cref="XmlElement"/> objects. Use the XmlElement Name and InnerXml properties
+		/// to get the name and value of the returned property.
+		/// </remarks>
+		[XmlAnyElement(), NonSerialized()]
+		public XmlElement[] AllElements;
+	}
+
+	/// <summary>
+	/// If an error occurs then Rtm returns this object.
+	/// </summary>
+	[System.Serializable]
+	public class ResponseError
+	{
+		/// <summary>
+		/// The code or number of the error.
+		/// </summary>
+		/// <remarks>
+		/// 100 - Invalid Api Key.
+		/// 99  - User not logged in.
+		/// Other codes are specific to a method.
+		/// </remarks>
+		[XmlAttribute("code", Form=XmlSchemaForm.Unqualified)]
+		public int Code;
+
+		/// <summary>
+		/// The verbose message matching the error code.
+		/// </summary>
+		[XmlAttribute("msg", Form=XmlSchemaForm.Unqualified)]
+		public string Message;
+	}
+
+	/// <summary>
+	/// The status of the response, either ok or fail.
+	/// </summary>
+	public enum ResponseStatus
+	{
+		/// <summary>
+		/// An unknown status, and the default value if not set.
+		/// </summary>
+		[XmlEnum("unknown")]
+		Unknown,
+
+		/// <summary>
+		/// The response returns "ok" on a successful execution of the method.
+		/// </summary>
+		[XmlEnum("ok")]
+		OK,
+		/// <summary>
+		/// The response returns "fail" if there is an error, such as invalid API key or login failure.
+		/// </summary>
+		[XmlEnum("fail")]
+		Failed
+	}
+}
\ No newline at end of file

Added: trunk/RtmNet/ResponseXmlException.cs
==============================================================================
--- (empty file)
+++ trunk/RtmNet/ResponseXmlException.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,18 @@
+using System;
+
+namespace RtmNet
+{
+	/// <summary>
+	/// Exception thrown when an error parsing the returned XML.
+	/// </summary>
+	public class ResponseXmlException : RtmException
+	{
+		internal ResponseXmlException(string message) : base(message)
+		{
+		}
+
+		internal ResponseXmlException(string message, Exception innerException) : base(message, innerException)
+		{
+		}
+	}
+}

Added: trunk/RtmNet/Rtm.cs
==============================================================================
--- (empty file)
+++ trunk/RtmNet/Rtm.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,1300 @@
+using System;
+using System.Net;
+using System.IO;
+using System.Xml;
+using System.Xml.XPath;
+using System.Xml.Serialization;
+using System.Text;
+using System.Collections;
+using System.Collections.Specialized;
+
+namespace RtmNet
+{
+	/// <summary>
+	/// The main Rtm class.
+	/// </summary>
+	/// <remarks>
+	/// Create an instance of this class and then call its methods to perform methods on Rtm.
+	/// </remarks>
+	/// <example>
+	/// <code>RtmNet.Rtm Rtm = new RtmNet.Rtm();
+	/// User user = Rtm.PeopleFindByEmail("cal iamcal com");
+	/// Console.WriteLine("User Id is " + u.UserId);</code>
+	/// </example>
+	//[System.Net.WebPermission(System.Security.Permissions.SecurityAction.Demand, ConnectPattern="http://www.Rtm.com/.*";)]
+	public class Rtm
+	{
+#region [ Private Variables ]
+		private const string AuthUrl = "http://api.rememberthemilk.com/services/auth/";;
+		private const string BaseUrl = "http://api.rememberthemilk.com/services/rest";;
+
+		private string apiKey;
+		private string apiToken;
+		private string sharedSecret;
+		private int timeout = 30000;
+		private const string UserAgent = "Mozilla/4.0 RtmNet API (compatible; MSIE 6.0; Windows NT 5.1)";
+		private string lastRequest;
+		private string lastResponse;
+
+		private WebProxy proxy;// = WebProxy.GetDefaultProxy();
+
+#endregion
+
+#region [ Public Properties ]
+		/// <summary>
+		/// Get or set the API Key to be used by all calls. API key is mandatory for all 
+		/// calls to Rtm.
+		/// </summary>
+		public string ApiKey 
+		{ 
+			get { return apiKey; } 
+			set { apiKey = (value==null||value.Length==0?null:value); }
+		}
+
+		/// <summary>
+		/// API shared secret is required for all calls that require signing, which includes
+		/// all methods that require authentication, as well as the actual Rtm.auth.* calls.
+		/// </summary>
+		public string ApiSecret
+		{
+			get { return sharedSecret; }
+			set { sharedSecret = (value==null||value.Length==0?null:value); }
+		}
+
+		/// <summary>
+		/// The API token is required for all calls that require authentication.
+		/// A <see cref="RtmException"/> will be raised by Rtm if the API token is
+		/// not set when required.
+		/// </summary>
+		/// <remarks>
+		/// It should be noted that some methods will work without the API token, but
+		/// will return different results if used with them (such as group pool requests, 
+		/// and results which include private pictures the authenticated user is allowed to see
+		/// (their own, or others).
+		/// </remarks>
+		[Obsolete("Renamed to AuthToken to be more consistent with the Rtm API")]
+			public string ApiToken 
+			{
+				get { return apiToken; }
+				set { apiToken = (value==null||value.Length==0?null:value); }
+			}
+
+		/// <summary>
+		/// The authentication token is required for all calls that require authentication.
+		/// A <see cref="RtmException"/> will be raised by Rtm if the authentication token is
+		/// not set when required.
+		/// </summary>
+		/// <remarks>
+		/// It should be noted that some methods will work without the authentication token, but
+		/// will return different results if used with them (such as group pool requests, 
+		/// and results which include private pictures the authenticated user is allowed to see
+		/// (their own, or others).
+		/// </remarks>
+		public string AuthToken 
+		{
+			get { return apiToken; }
+			set { apiToken = (value==null||value.Length==0?null:value); }
+		}
+
+		/// <summary>
+		/// The default service to use for new Rtm instances
+		/// </summary>
+		public static SupportedService DefaultService
+		{
+			get { return SupportedService.Rtm; }
+		}
+
+		/// <summary>
+		/// The current service that the Rtm API is using.
+		/// </summary>
+		public SupportedService CurrentService
+		{
+			get { return SupportedService.Rtm; } 
+		}
+
+		/// <summary>
+		/// Internal timeout for all web requests in milliseconds. Defaults to 30 seconds.
+		/// </summary>
+		public int HttpTimeout
+		{
+			get { return timeout; } 
+			set { timeout = value; }
+		}
+
+		/// <summary>
+		/// Checks to see if a shared secret and an api token are stored in the object.
+		/// Does not check if these values are valid values.
+		/// </summary>
+		public bool IsAuthenticated
+		{
+			get { return (sharedSecret != null && apiToken != null); }
+		}
+
+		/// <summary>
+		/// Returns the raw XML returned from the last response.
+		/// Only set it the response was not returned from cache.
+		/// </summary>
+		public string LastResponse
+		{
+			get { return lastResponse; }
+		}
+
+		/// <summary>
+		/// Returns the last URL requested. Includes API signing.
+		/// </summary>
+		public string LastRequest
+		{
+			get { return lastRequest; }
+		}
+
+		/// <summary>
+		/// You can set the <see cref="WebProxy"/> or alter its properties.
+		/// It defaults to your internet explorer proxy settings.
+		/// </summary>
+		public WebProxy Proxy { get { return proxy; } set { proxy = value; } }
+#endregion
+
+#region [ Constructors ]
+
+		/// <summary>
+		/// Constructor loads configuration settings from app.config or web.config file if they exist.
+		/// </summary>
+		public Rtm()
+		{
+		}
+
+		/// <summary>
+		/// Create a new instance of the <see cref="Rtm"/> class with no authentication.
+		/// </summary>
+		/// <param name="apiKey">Your Rtm API Key.</param>
+		public Rtm(string apiKey) : this(apiKey, "", "")
+		{
+		}
+
+		/// <summary>
+		/// Creates a new instance of the <see cref="Rtm"/> class with an API key and a Shared Secret.
+		/// This is only useful really useful for calling the Auth functions as all other
+		/// authenticationed methods also require the API Token.
+		/// </summary>
+		/// <param name="apiKey">Your Rtm API Key.</param>
+		/// <param name="sharedSecret">Your Rtm Shared Secret.</param>
+		public Rtm(string apiKey, string sharedSecret) : this(apiKey, sharedSecret, "")
+		{
+		}
+
+		/// <summary>
+		/// Create a new instance of the <see cref="Rtm"/> class with the email address and password given
+		/// </summary>
+		/// <param name="apiKey">Your Rtm API Key</param>
+		/// <param name="sharedSecret">Your Rtm Shared Secret.</param>
+		/// <param name="token">The token for the user who has been authenticated.</param>
+		public Rtm(string apiKey, string sharedSecret, string token) : this()
+		{
+			this.apiKey = apiKey;
+			this.sharedSecret = sharedSecret;
+			this.apiToken = token;
+		}
+#endregion
+
+#region [ Private Methods ]
+		/// <summary>
+		/// A private method which performs the actual HTTP web request if
+		/// the details are not found within the cache.
+		/// </summary>
+		/// <param name="url">The URL to download.</param>
+		/// <param name="variables">The query string parameters to be added to the end of the URL.</param>
+		/// <returns>A <see cref="RtmNet.Response"/> object.</returns>
+		/// <remarks>If the final length of the URL would be greater than 2000 characters 
+		/// then they are sent as part of the body instead.</remarks>
+		private string DoGetResponse(string url, string variables)
+		{
+			HttpWebRequest req = null;
+			HttpWebResponse res = null;
+
+			if( variables.Length < 2000 )
+			{
+				url += "?" + variables;
+				variables = "";
+			}
+
+			// Initialise the web request
+			req = (HttpWebRequest)HttpWebRequest.Create(url);
+			req.Method = "POST";
+
+			if (req.Method == "POST") req.ContentLength = variables.Length;
+
+			req.UserAgent = UserAgent;
+			if( Proxy != null ) req.Proxy = Proxy;
+			req.Timeout = HttpTimeout;
+			req.KeepAlive = false;
+			if (variables.Length > 0)
+			{
+				req.ContentType = "application/x-www-form-urlencoded";
+				StreamWriter sw = new StreamWriter(req.GetRequestStream());
+				sw.Write(variables);
+				sw.Close();
+			}
+			else
+			{
+				// This is needed in the Compact Framework
+				// See for more details: http://msdn2.microsoft.com/en-us/library/1afx2b0f.aspx
+				req.GetRequestStream().Close();
+			}
+
+			try
+			{
+				// Get response from the internet
+				res = (HttpWebResponse)req.GetResponse();
+			}
+			catch(WebException ex)
+			{
+				if( ex.Status == WebExceptionStatus.ProtocolError )
+				{
+					HttpWebResponse res2 = (HttpWebResponse)ex.Response;
+					if( res2 != null )
+					{
+						throw new RtmWebException(String.Format("HTTP Error {0}, {1}", (int)res2.StatusCode, res2.StatusDescription), ex);
+					}
+				}
+				throw new RtmWebException(ex.Message, ex);
+			}
+
+			string responseString = string.Empty;
+
+			using (StreamReader sr = new StreamReader(res.GetResponseStream()))
+			{
+				responseString = sr.ReadToEnd();
+			}
+
+			return responseString;
+		}
+
+#endregion
+
+#region [ GetResponse methods ]
+		private Response GetResponse(Hashtable parameters)
+		{
+			CheckApiKey();
+
+			// Calulate URL 
+			string url = BaseUrl;
+			
+			StringBuilder UrlStringBuilder = new StringBuilder("", 2 * 1024);
+			StringBuilder HashStringBuilder = new StringBuilder(sharedSecret, 2 * 1024);
+
+			parameters["api_key"] = apiKey;
+
+			if( apiToken != null && apiToken.Length > 0 )
+			{
+				parameters["auth_token"] = apiToken;
+			}
+
+			string[] keys = new string[parameters.Keys.Count];
+			parameters.Keys.CopyTo(keys, 0);
+			Array.Sort(keys);
+
+			foreach(string key in keys)
+			{
+				if( UrlStringBuilder.Length > 0 ) UrlStringBuilder.Append("&");
+				UrlStringBuilder.Append(key);
+				UrlStringBuilder.Append("=");
+				UrlStringBuilder.Append(Utils.UrlEncode(Convert.ToString(parameters[key])));
+				HashStringBuilder.Append(key);
+				HashStringBuilder.Append(parameters[key]);
+			}
+
+			if (sharedSecret != null && sharedSecret.Length > 0) 
+			{
+				if (UrlStringBuilder.Length > BaseUrl.Length + 1)
+				{
+					UrlStringBuilder.Append("&");
+				}
+				UrlStringBuilder.Append("api_sig=");
+				UrlStringBuilder.Append(Md5Hash(HashStringBuilder.ToString()));
+			}
+
+			string variables = UrlStringBuilder.ToString();
+			lastRequest = url;
+			lastResponse = string.Empty;
+
+			string responseXml = DoGetResponse(url, variables);
+			lastResponse = responseXml;
+			return Utils.Deserialize(responseXml);
+		}
+
+#endregion
+
+
+#region [ Auth ]
+		/// <summary>
+		/// Retrieve a temporary FROB from the Rtm service, to be used in redirecting the
+		/// user to the Rtm web site for authentication. Only required for desktop authentication.
+		/// </summary>
+		/// <remarks>
+		/// Pass the FROB to the <see cref="AuthCalcUrl"/> method to calculate the url.
+		/// </remarks>
+		/// <example>
+		/// <code>
+		/// string frob = Rtm.AuthGetFrob();
+		/// string url = Rtm.AuthCalcUrl(frob, AuthLevel.Read);
+		/// 
+		/// // redirect the user to the url above and then wait till they have authenticated and return to the app.
+		/// 
+		/// Auth auth = Rtm.AuthGetToken(frob);
+		/// 
+		/// // then store the auth.Token for later use.
+		/// string token = auth.Token;
+		/// </code>
+		/// </example>
+		/// <returns>The FROB.</returns>
+		public string AuthGetFrob()
+		{
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "rtm.auth.getFrob");
+
+			RtmNet.Response response = GetResponse(parameters);
+			if( response.Status == ResponseStatus.OK )
+			{
+				return response.AllElements[0].InnerText;
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}
+
+		/// <summary>
+		/// Calculates the URL to redirect the user to Rtm web site for
+		/// authentication. Used by desktop application. 
+		/// See <see cref="AuthGetFrob"/> for example code.
+		/// </summary>
+		/// <param name="frob">The FROB to be used for authentication.</param>
+		/// <param name="authLevel">The <see cref="AuthLevel"/> stating the maximum authentication level your application requires.</param>
+		/// <returns>The url to redirect the user to.</returns>
+		public string AuthCalcUrl(string frob, AuthLevel authLevel)
+		{
+			if( sharedSecret == null ) throw new SignatureRequiredException();
+
+			string hash = sharedSecret + "api_key" + apiKey + "frob" + frob + "perms" + authLevel.ToString().ToLower();
+			hash = Md5Hash(hash);
+			string url = AuthUrl + "?api_key=" + apiKey + "&perms=" + authLevel.ToString().ToLower() + "&frob=" + frob;
+			url += "&api_sig=" + hash;
+
+			return url;
+		}
+
+		/// <summary>
+		/// Calculates the URL to redirect the user to Rtm web site for
+		/// auehtntication. Used by Web applications. 
+		/// See <see cref="AuthGetFrob"/> for example code.
+		/// </summary>
+		/// <remarks>
+		/// The Rtm web site provides 'tiny urls' that can be used in place
+		/// of this URL when you specify your return url in the API key page.
+		/// It is recommended that you use these instead as they do not include
+		/// your API or shared secret.
+		/// </remarks>
+		/// <param name="authLevel">The <see cref="AuthLevel"/> stating the maximum authentication level your application requires.</param>
+		/// <returns>The url to redirect the user to.</returns>
+		public string AuthCalcWebUrl(AuthLevel authLevel)
+		{
+			if( sharedSecret == null ) throw new SignatureRequiredException();
+
+			string hash = sharedSecret + "api_key" + apiKey + "perms" + authLevel.ToString().ToLower();
+			hash = Md5Hash(hash);
+			string url = AuthUrl + "?api_key=" + apiKey + "&perms=" + authLevel.ToString().ToLower();
+			url += "&api_sig=" + hash;
+
+			return url;
+		}
+
+		/// <summary>
+		/// After the user has authenticated your application on the Rtm web site call this 
+		/// method with the FROB (either stored from <see cref="AuthGetFrob"/> or returned in the URL
+		/// from the Rtm web site) to get the users token.
+		/// </summary>
+		/// <param name="frob">The string containing the FROB.</param>
+		/// <returns>A <see cref="Auth"/> object containing user and token details.</returns>
+		public Auth AuthGetToken(string frob)
+		{
+			if( sharedSecret == null ) throw new SignatureRequiredException();
+
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "rtm.auth.getToken");
+			parameters.Add("frob", frob);
+
+			RtmNet.Response response = GetResponse(parameters);
+			if( response.Status == ResponseStatus.OK )
+			{
+				Auth auth = new Auth(response.AllElements[0]);
+				return auth;
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}
+
+		/// <summary>
+		/// Gets the full token details for a given mini token, entered by the user following a 
+		/// web based authentication.
+		/// </summary>
+		/// <param name="miniToken">The mini token.</param>
+		/// <returns>An instance <see cref="Auth"/> class, detailing the user and their full token.</returns>
+		public Auth AuthGetFullToken(string miniToken)
+		{
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "rtm.auth.getFullToken");
+			parameters.Add("mini_token", miniToken.Replace("-", ""));
+			RtmNet.Response response = GetResponse(parameters);
+
+			if( response.Status == ResponseStatus.OK )
+			{
+				Auth auth = new Auth(response.AllElements[0]);
+				return auth;
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}
+
+		/// <summary>
+		/// Checks a authentication token with the Rtm service to make
+		/// sure it is still valid.
+		/// </summary>
+		/// <param name="token">The authentication token to check.</param>
+		/// <returns>The <see cref="Auth"/> object detailing the user for the token.</returns>
+		public Auth AuthCheckToken(string token)
+		{
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "rtm.auth.checkToken");
+			parameters.Add("auth_token", token);
+
+			RtmNet.Response response = GetResponse(parameters);
+			if( response.Status == ResponseStatus.OK )
+			{
+				Auth auth = new Auth(response.AllElements[0]);
+				return auth;
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+
+		}		
+#endregion
+
+
+#region [ Timeline ]
+		public string TimelineCreate()
+		{
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "rtm.timelines.create");
+			RtmNet.Response response = GetResponse(parameters);
+
+			if( response.Status == ResponseStatus.OK )
+			{
+				return response.Timeline;
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}
+#endregion
+
+
+#region [ Lists ]
+		/// <summary>
+		/// Gets a list of contacts for the logged in user.
+		/// Requires authentication.
+		/// </summary>
+		/// <returns>An instance of the <see cref="Contacts"/> class containing the list of contacts.</returns>
+		public Lists ListsGetList()
+		{
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "rtm.lists.getList");
+			RtmNet.Response response = GetResponse(parameters);
+
+			if( response.Status == ResponseStatus.OK )
+			{
+				return response.Lists;
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}
+#endregion
+
+		
+#region [ Tasks ]
+		/// <summary>
+		/// Gets a list of contacts for the logged in user.
+		/// Requires authentication.
+		/// </summary>
+		/// <returns>An instance of the <see cref="Contacts"/> class containing the list of contacts.</returns>
+		public Tasks TasksGetList(string listID)
+		{
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "rtm.tasks.getList");
+			if(listID != null)
+				parameters.Add("list_id", listID);
+			
+			RtmNet.Response response = GetResponse(parameters);
+
+			if( response.Status == ResponseStatus.OK )
+			{
+				return response.Tasks;
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}
+
+
+		/// <summary>
+		/// Sets the priority on a task
+		/// </summary>
+		/// <param name="listID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="taskSeriesID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="taskID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="priority">
+		/// A <see cref="System.String"/>
+		/// </param>
+		public List TasksSetPriority(string timeline, string listID, string taskSeriesID, string taskID, string priority)
+		{
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "rtm.tasks.setPriority");
+			parameters.Add("timeline", timeline);
+			parameters.Add("list_id", listID);
+			parameters.Add("taskseries_id", taskSeriesID);
+			parameters.Add("task_id", taskID);
+			if(priority.CompareTo("N") != 0)
+				parameters.Add("priority", priority);
+
+			RtmNet.Response response = GetResponse(parameters);
+
+			if( response.Status == ResponseStatus.OK )
+			{
+				return response.List;
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}
+
+		
+		/// <summary>
+		/// Sets the priority on a task
+		/// </summary>
+		/// <param name="listID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="taskSeriesID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="taskID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="priority">
+		/// A <see cref="System.String"/>
+		/// </param>
+		public List TasksSetName(string timeline, string listID, string taskSeriesID, string taskID, string name)
+		{
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "rtm.tasks.setName");
+			parameters.Add("timeline", timeline);
+			parameters.Add("list_id", listID);
+			parameters.Add("taskseries_id", taskSeriesID);
+			parameters.Add("task_id", taskID);
+			parameters.Add("name", name);
+
+			RtmNet.Response response = GetResponse(parameters);
+
+			if( response.Status == ResponseStatus.OK )
+			{
+				return response.List;
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}
+
+		/// <summary>
+		/// Sets the due date of a task
+		/// </summary>
+		/// <param name="timeline">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="listID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="taskSeriesID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="taskID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="name">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <returns>
+		/// A <see cref="List"/>
+		/// </returns>
+		public List TasksSetDueDate(string timeline, string listID, string taskSeriesID, string taskID)
+		{
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "rtm.tasks.setDueDate");
+			parameters.Add("timeline", timeline);
+			parameters.Add("list_id", listID);
+			parameters.Add("taskseries_id", taskSeriesID);
+			parameters.Add("task_id", taskID);
+
+			RtmNet.Response response = GetResponse(parameters);
+
+			if( response.Status == ResponseStatus.OK )
+			{
+				return response.List;
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}
+
+
+
+		/// <summary>
+		/// Sets the due date of a task
+		/// </summary>
+		/// <param name="timeline">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="listID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="taskSeriesID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="taskID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="name">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <returns>
+		/// A <see cref="List"/>
+		/// </returns>
+		public List TasksSetDueDate(string timeline, string listID, string taskSeriesID, string taskID, string due)
+		{
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "rtm.tasks.setDueDate");
+			parameters.Add("timeline", timeline);
+			parameters.Add("list_id", listID);
+			parameters.Add("taskseries_id", taskSeriesID);
+			parameters.Add("task_id", taskID);
+			parameters.Add("due", due);
+
+			RtmNet.Response response = GetResponse(parameters);
+
+			if( response.Status == ResponseStatus.OK )
+			{
+				return response.List;
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}
+
+
+		/// <summary>
+		/// Sets the due date of a task
+		/// </summary>
+		/// <param name="timeline">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="listID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="taskSeriesID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="taskID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="name">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <returns>
+		/// A <see cref="List"/>
+		/// </returns>
+		public List TasksSetDueDateParse(string timeline, string listID, string taskSeriesID, string taskID, string due)
+		{
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "rtm.tasks.setDueDate");
+			parameters.Add("timeline", timeline);
+			parameters.Add("list_id", listID);
+			parameters.Add("taskseries_id", taskSeriesID);
+			parameters.Add("task_id", taskID);
+			parameters.Add("due", due);
+			parameters.Add("parse", "1");
+
+			RtmNet.Response response = GetResponse(parameters);
+
+			if( response.Status == ResponseStatus.OK )
+			{
+				return response.List;
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}
+
+
+		/// <summary>
+		/// Marks a task complete
+		/// </summary>
+		/// <param name="timeline">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="listID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="taskSeriesID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="taskID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <returns>
+		/// A <see cref="List"/>
+		/// </returns>
+		public List TasksComplete(string timeline, string listID, string taskSeriesID, string taskID)
+		{
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "rtm.tasks.complete");
+			parameters.Add("timeline", timeline);
+			parameters.Add("list_id", listID);
+			parameters.Add("taskseries_id", taskSeriesID);
+			parameters.Add("task_id", taskID);
+
+			RtmNet.Response response = GetResponse(parameters);
+
+			if( response.Status == ResponseStatus.OK )
+			{
+				return response.List;
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}
+		
+		
+		/// <summary>
+		/// Marks a task as uncomplete
+		/// </summary>
+		/// <param name="timeline">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="listID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="taskSeriesID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="taskID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <returns>
+		/// A <see cref="List"/>
+		/// </returns>
+		public List TasksUncomplete(string timeline, string listID, string taskSeriesID, string taskID)
+		{
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "rtm.tasks.uncomplete");
+			parameters.Add("timeline", timeline);
+			parameters.Add("list_id", listID);
+			parameters.Add("taskseries_id", taskSeriesID);
+			parameters.Add("task_id", taskID);
+
+			RtmNet.Response response = GetResponse(parameters);
+
+			if( response.Status == ResponseStatus.OK )
+			{
+				return response.List;
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}
+		
+		/// <summary>
+		/// Moves a task from one list to another
+		/// </summary>
+		/// <param name="timeline">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="fromListID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="toListID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="taskSeriesID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="taskID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <returns>
+		/// A <see cref="List"/>
+		/// </returns>
+		public List TasksMoveTo(string timeline, string fromListID, string toListID, string taskSeriesID, string taskID)
+		{
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "rtm.tasks.moveTo");
+			parameters.Add("timeline", timeline);
+			parameters.Add("from_list_id", fromListID);
+			parameters.Add("to_list_id", toListID);
+			parameters.Add("taskseries_id", taskSeriesID);
+			parameters.Add("task_id", taskID);
+
+			RtmNet.Response response = GetResponse(parameters);
+
+			if( response.Status == ResponseStatus.OK )
+			{
+				return response.List;
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}	
+		
+	
+		public List TasksAdd(string timeline, string name)
+		{
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "rtm.tasks.add");
+			parameters.Add("timeline", timeline);
+			parameters.Add("name", name);
+
+			RtmNet.Response response = GetResponse(parameters);
+
+			if( response.Status == ResponseStatus.OK )
+			{
+				return response.List;
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}
+
+
+		public List TasksAdd(string timeline, string name, string listID)
+		{
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "rtm.tasks.add");
+			parameters.Add("timeline", timeline);
+			parameters.Add("list_id", listID);
+			parameters.Add("name", name);
+
+			RtmNet.Response response = GetResponse(parameters);
+
+			if( response.Status == ResponseStatus.OK )
+			{
+				return response.List;
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}
+
+
+		public List TasksDelete(string timeline, string listID, string taskSeriesID, string taskID)
+		{
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "rtm.tasks.delete");
+			parameters.Add("timeline", timeline);
+			parameters.Add("list_id", listID);	
+			parameters.Add("taskseries_id", taskSeriesID);
+			parameters.Add("task_id", taskID);
+
+			RtmNet.Response response = GetResponse(parameters);
+
+			if( response.Status == ResponseStatus.OK )
+			{
+				return response.List;
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}
+
+		
+		/// <summary>
+		/// Add a selection of tags to a photo.
+		/// </summary>
+		/// <param name="photoId">The photo id of the photo.</param>
+		/// <param name="tags">An array of strings containing the tags.</param>
+		/// <returns>True if the tags are added successfully.</returns>
+		public void TasksAddTags(string photoId, string[] tags)
+		{	
+			string s = string.Join(",", tags);
+			TasksAddTags(photoId, s);
+		}
+
+		/// <summary>
+		/// Add a selection of tags to a photo.
+		/// </summary>
+		/// <param name="photoId">The photo id of the photo.</param>
+		/// <param name="tags">An string of comma delimited tags.</param>
+		/// <returns>True if the tags are added successfully.</returns>
+		public void TasksAddTags(string photoId, string tags)
+		{
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "rtm.tasks.addTags");
+			parameters.Add("photo_id", photoId);
+			parameters.Add("tags", tags);
+
+			RtmNet.Response response = GetResponse(parameters);
+
+			if( response.Status == ResponseStatus.OK )
+			{
+				return;
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}
+#endregion
+
+		
+		
+		
+		
+		
+		
+
+#region [ Contacts ]
+		/// <summary>
+		/// Gets a list of contacts for the logged in user.
+		/// Requires authentication.
+		/// </summary>
+		/// <returns>An instance of the <see cref="Contacts"/> class containing the list of contacts.</returns>
+		public Contacts ContactsGetList()
+		{
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "rtm.contacts.getList");
+			RtmNet.Response response = GetResponse(parameters);
+
+			if( response.Status == ResponseStatus.OK )
+			{
+				return response.Contacts;
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}
+
+		/// <summary>
+		/// Gets a list of the given users contact, or those that are publically avaiable.
+		/// </summary>
+		/// <param name="userId">The Id of the user who's contacts you want to return.</param>
+		/// <returns>An instance of the <see cref="Contacts"/> class containing the list of contacts.</returns>
+		public Contacts ContactsGetPublicList(string userId)
+		{
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "rtm.contacts.getPublicList");
+			parameters.Add("user_id", userId);
+			RtmNet.Response response = GetResponse(parameters);
+
+			if( response.Status == ResponseStatus.OK )
+			{
+				return response.Contacts;
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}
+#endregion
+
+
+
+#region [ Notes ]
+		/// <summary>
+		/// Adds a note to a task
+		/// </summary>
+		/// <param name="timeline">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="listID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="taskSeriesID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="taskID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="noteTitle">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="noteText">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <returns>
+		/// A <see cref="Note"/>
+		/// </returns>
+		public Note NotesAdd(string timeline, string listID, string taskSeriesID, string taskID, string noteTitle, string noteText)
+		{
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "rtm.tasks.notes.add");
+			parameters.Add("timeline", timeline);
+			parameters.Add("list_id", listID);
+			parameters.Add("taskseries_id", taskSeriesID);
+			parameters.Add("task_id", taskID);
+			parameters.Add("note_title", noteTitle);
+			parameters.Add("note_text", noteText);
+
+			RtmNet.Response response = GetResponse(parameters);
+
+			if( response.Status == ResponseStatus.OK )
+			{
+				return response.Note;
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}
+
+
+		/// <summary>
+		/// Deletes a note
+		/// </summary>
+		/// <param name="timeline">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="noteID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		public void NotesDelete(string timeline, string noteID)
+		{
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "rtm.tasks.notes.delete");
+			parameters.Add("timeline", timeline);
+			parameters.Add("note_id", noteID);
+
+			RtmNet.Response response = GetResponse(parameters);
+
+			if( response.Status != ResponseStatus.OK )
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}
+
+
+		/// <summary>
+		/// Modifies an existing note
+		/// </summary>
+		/// <param name="timeline">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="noteID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="noteTitle">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <param name="noteText">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <returns>
+		/// A <see cref="Note"/>
+		/// </returns>
+		public Note NotesEdit(string timeline, string noteID, string noteTitle, string noteText)
+		{
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "rtm.tasks.notes.edit");
+			parameters.Add("timeline", timeline);
+			parameters.Add("note_id", noteID);			
+			parameters.Add("note_title", noteTitle);
+			parameters.Add("note_text", noteText);
+
+			RtmNet.Response response = GetResponse(parameters);
+
+			if( response.Status == ResponseStatus.OK )
+			{
+				return response.Note;
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}
+
+
+#endregion
+
+
+
+
+#region [ Tests ]
+		/// <summary>
+		/// Can be used to call unsupported methods in the Rtm API.
+		/// </summary>
+		/// <remarks>
+		/// Use of this method is not supported. 
+		/// The way the RtmNet API Library works may mean that some methods do not return an expected result 
+		/// when using this method.
+		/// </remarks>
+		/// <param name="method">The method name, e.g. "Rtm.test.null".</param>
+		/// <param name="parameters">A list of parameters. Note, api_key is added by default and is not included. Can be null.</param>
+		/// <returns>An array of <see cref="XmlElement"/> instances which is the expected response.</returns>
+		public XmlElement[] TestGeneric(string method, NameValueCollection parameters)
+		{
+			Hashtable _parameters = new Hashtable();
+			if( parameters != null )
+			{
+				foreach(string key in parameters.AllKeys)
+				{
+					_parameters.Add(key, parameters[key]);
+				}
+			}
+			_parameters.Add("method", method);
+
+			RtmNet.Response response = GetResponse(_parameters);
+
+			if( response.Status == ResponseStatus.OK )
+			{
+				return response.AllElements;
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}
+		/// <summary>
+		/// Runs the Rtm.test.echo method and returned an array of <see cref="XmlElement"/> items.
+		/// </summary>
+		/// <param name="echoParameter">The parameter to pass to the method.</param>
+		/// <param name="echoValue">The value to pass to the method with the parameter.</param>
+		/// <returns>An array of <see cref="XmlElement"/> items.</returns>
+		/// <remarks>
+		/// The APi Key has been removed from the returned array and will not be shown.
+		/// </remarks>
+		/// <example>
+		/// <code>
+		/// XmlElement[] elements = Rtm.TestEcho("&amp;param=value");
+		/// foreach(XmlElement element in elements)
+		/// {
+		///		if( element.Name = "method" )
+		///			Console.WriteLine("Method = " + element.InnerXml);
+		///		if( element.Name = "param" )
+		///			Console.WriteLine("Param = " + element.InnerXml);
+		/// }
+		/// </code>
+		/// </example>
+		public XmlElement[] TestEcho(string echoParameter, string echoValue)
+		{
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "Rtm.test.echo");
+			parameters.Add("api_key", apiKey);
+			if( echoParameter != null && echoParameter.Length > 0 )
+			{
+				parameters.Add(echoParameter, echoValue);
+			}
+
+			RtmNet.Response response = GetResponse(parameters);
+
+			if( response.Status == ResponseStatus.OK )
+			{
+				// Remove the api_key element from the array.
+				XmlElement[] elements = new XmlElement[response.AllElements.Length - 1];
+				int c = 0;
+				foreach(XmlElement element in response.AllElements)
+				{
+					if(element.Name != "api_key" )
+						elements[c++] = element;
+				}
+				return elements;
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}
+
+		/// <summary>
+		/// Test the logged in state of the current Filckr object.
+		/// </summary>
+		/// <returns>The <see cref="FoundUser"/> object containing the username and userid of the current user.</returns>
+		public FoundUser TestLogin()
+		{
+			Hashtable parameters = new Hashtable();
+			parameters.Add("method", "Rtm.test.login");
+
+			RtmNet.Response response = GetResponse(parameters);
+
+			if( response.Status == ResponseStatus.OK )
+			{
+				return new FoundUser(response.AllElements[0]);
+			}
+			else
+			{
+				throw new RtmApiException(response.Error);
+			}
+		}
+#endregion
+
+#region [ MD5 Hash ]
+		private static string Md5Hash(string unhashed)
+		{
+			System.Security.Cryptography.MD5CryptoServiceProvider csp = new System.Security.Cryptography.MD5CryptoServiceProvider();
+			byte[] bytes = System.Text.Encoding.UTF8.GetBytes(unhashed);
+			byte[] hashedBytes = csp.ComputeHash(bytes, 0, bytes.Length);
+			return BitConverter.ToString(hashedBytes).Replace("-", "").ToLower();
+		}
+#endregion
+
+		private void CheckApiKey()
+		{
+			if( ApiKey == null || ApiKey.Length == 0 )
+				throw new ApiKeyRequiredException();
+		}
+		/*private void CheckRequiresAuthentication()
+		  {
+		  CheckApiKey();
+
+		  if( ApiSecret == null || ApiSecret.Length == 0 )
+		  throw new SignatureRequiredException();
+		  if( AuthToken == null || AuthToken.Length == 0 )
+		  throw new AuthenticationRequiredException();
+
+		  }
+		 */
+	}
+
+}

Added: trunk/RtmNet/RtmApiException.cs
==============================================================================
--- (empty file)
+++ trunk/RtmNet/RtmApiException.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,46 @@
+using System;
+
+namespace RtmNet
+{
+	/// <summary>
+	/// Exception thrown when the Rtm API returned a specifi error code.
+	/// </summary>
+	public class RtmApiException : RtmException
+	{
+		private int code;
+		private string msg = "";
+
+		internal RtmApiException(ResponseError error)
+		{
+			code = error.Code;
+			msg = error.Message;
+		}
+
+		/// <summary>
+		/// Get the code of the Rtm error.
+		/// </summary>
+		public int Code
+		{
+			get { return code; }
+		}
+
+		/// <summary>
+		/// Gets the verbose message returned by Rtm.
+		/// </summary>
+		public string Verbose
+		{
+			get { return msg; }
+		}
+		
+		/// <summary>
+		/// Overrides the message to return custom error message.
+		/// </summary>
+		public override string Message
+		{
+			get
+			{
+				return msg + " (" + code + ")";
+			}
+		}
+	}
+}

Added: trunk/RtmNet/RtmException.cs
==============================================================================
--- (empty file)
+++ trunk/RtmNet/RtmException.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,23 @@
+using System;
+
+namespace RtmNet
+{
+	/// <summary>
+	/// Generic Rtm.Net Exception.
+	/// </summary>
+	[Serializable]
+	public class RtmException : Exception
+	{
+		internal RtmException()
+		{
+		}
+
+		internal RtmException(string message) : base(message)
+		{
+		}
+
+		internal RtmException(string message, Exception innerException) : base(message, innerException)
+		{
+		}
+	}
+}

Added: trunk/RtmNet/RtmNet.mdp
==============================================================================
--- (empty file)
+++ trunk/RtmNet/RtmNet.mdp	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,59 @@
+<Project name="RtmNet" fileversion="2.0" language="C#" clr-version="Net_2_0" ctype="DotNetProject">
+  <Configurations active="Debug">
+    <Configuration name="Debug" ctype="DotNetProjectConfiguration">
+      <Output directory="bin/Debug" assemblyKeyFile="." assembly="RtmNet" />
+      <Build debugmode="True" target="Library" />
+      <Execution runwithwarnings="True" consolepause="True" runtime="MsNet" clr-version="Net_2_0" />
+      <CodeGeneration compiler="Mcs" warninglevel="4" optimize="True" unsafecodeallowed="False" generateoverflowchecks="True" generatexmldocumentation="False" ctype="CSharpCompilerParameters" />
+    </Configuration>
+    <Configuration name="Release" ctype="DotNetProjectConfiguration">
+      <Output directory="bin/Release" assemblyKeyFile="." assembly="RtmNet" />
+      <Build debugmode="False" target="Library" />
+      <Execution runwithwarnings="True" consolepause="True" runtime="MsNet" clr-version="Net_2_0" />
+      <CodeGeneration compiler="Mcs" warninglevel="4" optimize="True" unsafecodeallowed="False" generateoverflowchecks="True" generatexmldocumentation="False" ctype="CSharpCompilerParameters" />
+    </Configuration>
+  </Configurations>
+  <Contents>
+    <File name="ApiKeyRequiredException.cs" subtype="Code" buildaction="Compile" />
+    <File name="AssemblyInfo.cs" subtype="Code" buildaction="Compile" />
+    <File name="Auth.cs" subtype="Code" buildaction="Compile" />
+    <File name="AuthenticationRequiredException.cs" subtype="Code" buildaction="Compile" />
+    <File name="Categories.cs" subtype="Code" buildaction="Compile" />
+    <File name="Contacts.cs" subtype="Code" buildaction="Compile" />
+    <File name="DateGranularity.cs" subtype="Code" buildaction="Compile" />
+    <File name="Enums.cs" subtype="Code" buildaction="Compile" />
+    <File name="example_app.config" subtype="Code" buildaction="Nothing" />
+    <File name="Groups.cs" subtype="Code" buildaction="Compile" />
+    <File name="GroupSearchResults.cs" subtype="Code" buildaction="Compile" />
+    <File name="License.txt" subtype="Code" buildaction="Nothing" />
+    <File name="List.cs" subtype="Code" buildaction="Compile" />
+    <File name="Methods.cs" subtype="Code" buildaction="Compile" />
+    <File name="Response.cs" subtype="Code" buildaction="Compile" />
+    <File name="ResponseXmlException.cs" subtype="Code" buildaction="Compile" />
+    <File name="Rtm.cs" subtype="Code" buildaction="Compile" />
+    <File name="RtmApiException.cs" subtype="Code" buildaction="Compile" />
+    <File name="RtmException.cs" subtype="Code" buildaction="Compile" />
+    <File name="RtmWebException.cs" subtype="Code" buildaction="Compile" />
+    <File name="SignatureRequiredException.cs" subtype="Code" buildaction="Compile" />
+    <File name="Tags.cs" subtype="Code" buildaction="Compile" />
+    <File name="Task.cs" subtype="Code" buildaction="Compile" />
+    <File name="User.cs" subtype="Code" buildaction="Compile" />
+    <File name="Utils.cs" subtype="Code" buildaction="Compile" />
+    <File name="Note.cs" subtype="Code" buildaction="Compile" />
+  </Contents>
+  <References>
+    <ProjectReference type="Gac" localcopy="True" refto="System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
+    <ProjectReference type="Gac" localcopy="True" refto="System.Configuration.Install, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
+    <ProjectReference type="Gac" localcopy="True" refto="System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
+    <ProjectReference type="Gac" localcopy="True" refto="System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
+  </References>
+  <MonoDevelop.Autotools.MakefileInfo IntegrationEnabled="True" RelativeMakefileName="./Makefile.am" IsAutotoolsProject="True" RelativeConfigureInPath="../">
+    <BuildFilesVar Name="CSFILES" />
+    <DeployFilesVar />
+    <ResourcesVar Name="RESOURCES" />
+    <OthersVar />
+    <GacRefVar Name="ASSEMBLIES" Prefix="-r:" />
+    <AsmRefVar Name="ASSEMBLIES" Prefix="-r:" />
+    <ProjectRefVar Name="ASSEMBLIES" Prefix="-r:" />
+  </MonoDevelop.Autotools.MakefileInfo>
+</Project>
\ No newline at end of file

Added: trunk/RtmNet/RtmWebException.cs
==============================================================================
--- (empty file)
+++ trunk/RtmNet/RtmWebException.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,14 @@
+using System;
+
+namespace RtmNet
+{
+	/// <summary>
+	/// Exception thrown when a communication error occurs with a web call.
+	/// </summary>
+	public class RtmWebException : RtmException
+	{
+		internal RtmWebException(string message, Exception innerException) : base(message, innerException)
+		{
+		}
+	}
+}

Added: trunk/RtmNet/SignatureRequiredException.cs
==============================================================================
--- (empty file)
+++ trunk/RtmNet/SignatureRequiredException.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,14 @@
+using System;
+
+namespace RtmNet
+{
+	/// <summary>
+	/// Thrown when a method requires a valid signature but no shared secret has been supplied.
+	/// </summary>
+	public class SignatureRequiredException : RtmException
+	{
+		internal SignatureRequiredException() : base("Method requires signing but no shared secret supplied.")
+		{
+		}
+	}
+}

Added: trunk/RtmNet/Tags.cs
==============================================================================
--- (empty file)
+++ trunk/RtmNet/Tags.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,42 @@
+using System;
+using System.Xml;
+
+namespace RtmNet
+{
+	/// <summary>
+	/// A simple tag class, containing a tag name and optional count (for <see cref="Rtm.TagsGetListUserPopular()"/>)
+	/// </summary>
+	public class Tag
+	{
+		private string _tagName;
+		private int _count;
+
+		/// <summary>
+		/// The name of the tag.
+		/// </summary>
+		public string TagName
+		{
+			get { return _tagName; }
+		}
+
+		/// <summary>
+		/// The poularity of the tag. Will be 0 where the popularity is not retreaved.
+		/// </summary>
+		public int Count
+		{
+			get { return _count; }
+		}
+
+		internal Tag(XmlNode node)
+		{
+			if( node.Attributes["count"] != null ) _count = Convert.ToInt32(node.Attributes["count"].Value);
+			_tagName = node.InnerText;
+		}
+
+		internal Tag(string tagName, int count)
+		{
+			_tagName = tagName;
+			_count = count;
+		}
+	}
+}

Added: trunk/RtmNet/Task.cs
==============================================================================
--- (empty file)
+++ trunk/RtmNet/Task.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,233 @@
+using System;
+using System.Xml.Serialization;
+using System.Xml.Schema;
+
+namespace RtmNet
+{
+	/// <summary>
+	/// Contains a list of <see cref="Contact"/> items for a given user.
+	/// </summary>
+	[System.Serializable]
+	public class Tasks
+	{
+		/// <summary>
+		/// An array of <see cref="Contact"/> items for the user.
+		/// </summary>
+		[XmlElement("list", Form=XmlSchemaForm.Unqualified)]
+		public List[] ListCollection = new List[0];
+	}
+
+
+	/// <remarks/>
+	[System.Serializable]
+	public class TaskSeries
+	{
+		private string id = System.Guid.NewGuid().ToString();
+		private string name;
+		private string rawCreated;
+		private string rawModified;
+		private DateTime created = DateTime.MinValue;
+		private DateTime modified = DateTime.MinValue;
+
+		/// <remarks/>
+		[XmlAttribute("id", Form=XmlSchemaForm.Unqualified)]
+		public string TaskID { get { return id; } set { id = value; } }
+
+
+		/// <remarks/>
+		[XmlAttribute("created", Form=XmlSchemaForm.Unqualified)]
+		public string RawCreated
+		{
+			get { return rawCreated; }
+			set {
+				if(value.Length > 0) {
+					rawCreated = value;
+					created = Utils.DateStringToDateTime(rawCreated);
+				}
+			}
+		}
+
+		/// <summary>
+		/// Converts the raw created field to a <see cref="DateTime"/>.
+		/// </summary>
+		[XmlIgnore]
+		public DateTime Created
+		{
+			get { return created; }
+			set { created = value; }
+		}
+		
+		/// <remarks/>
+		[XmlAttribute("modified", Form=XmlSchemaForm.Unqualified)]
+		public string RawModified
+		{
+			get { return rawModified; }
+			set {
+				if(value.Length > 0) {
+					rawModified = value;
+					modified = Utils.DateStringToDateTime(rawModified);
+				}
+			}
+		}		
+    
+		/// <summary>
+		/// Converts the raw modified field to a <see cref="DateTime"/>.
+		/// </summary>
+		[XmlIgnore]
+		public DateTime Modified
+		{
+			get { return modified; }
+			set { modified = value; }
+		}
+
+		/// <remarks/>
+		[XmlAttribute("name", Form=XmlSchemaForm.Unqualified)]
+		public string Name { get { return name; } set { name = value; } }
+
+		/// <remarks/>
+		[XmlAttribute("source", Form=XmlSchemaForm.Unqualified)]
+		public string source;
+
+		[XmlElement("task", Form=XmlSchemaForm.Unqualified)]
+		public Task Task;
+
+		/// <remarks/>
+		[XmlElement("notes", Form=XmlSchemaForm.Unqualified)]
+		public Notes Notes;		
+	}
+
+
+	/// <remarks/>
+	[System.Serializable]
+	public class Task
+	{
+		private string id;
+		private string rawDue;
+		private string rawAdded;
+		private string rawCompleted;
+		private string rawDeleted;
+		private DateTime due = DateTime.MinValue;
+		private DateTime added = DateTime.MinValue;
+		private DateTime completed = DateTime.MinValue;
+		private DateTime deleted = DateTime.MinValue;
+
+		/// <remarks/>
+		[XmlAttribute("id", Form=XmlSchemaForm.Unqualified)]
+		public string TaskID { get { return id; } set { id = value; } }
+    
+		/// <remarks/>
+		[XmlAttribute("due", Form=XmlSchemaForm.Unqualified)]
+		public string RawDue
+		{
+			get { return rawDue; }
+			set {
+				if(value.Length > 0) {
+					rawDue = value;
+					due = Utils.DateStringToDateTime(rawDue);
+				}
+			}
+		}
+    
+		/// <summary>
+		/// Converts the raw created field to a <see cref="DateTime"/>.
+		/// </summary>	
+		[XmlIgnore]
+		public DateTime Due
+		{
+			get { return due; }
+			set { due = value; }
+		}
+
+		/// <summary>
+		/// Is this contact marked as a friend contact?
+		/// </summary>
+		[XmlAttribute("has_due_time", Form=XmlSchemaForm.Unqualified)]
+		public int HasDueTime;
+
+
+		/// <remarks/>
+		[XmlAttribute("added", Form=XmlSchemaForm.Unqualified)]
+		public string RawAdded
+		{
+			get { return rawAdded; }
+			set {
+				if(value.Length > 0) {
+					rawAdded = value;
+					added = Utils.DateStringToDateTime(rawAdded);
+				}
+			}
+		}		
+
+
+		/// <value>
+		/// Holds the date time for when the task was added
+		/// </value>
+		[XmlIgnore]
+		public DateTime Added
+		{
+			get { return added; }
+			set { added = value; }
+		}
+
+
+		/// <remarks/>
+		[XmlAttribute("completed", Form=XmlSchemaForm.Unqualified)]
+		public string RawCompleted
+		{
+			get { return rawCompleted; }
+			set {
+				if(value.Length > 0) {
+					rawCompleted = value;
+					completed = Utils.DateStringToDateTime(rawCompleted);
+				}
+			}
+		}
+		
+    
+		/// <summary>
+		/// Converts the raw created field to a <see cref="DateTime"/>.
+		/// </summary>
+		[XmlIgnore]
+		public DateTime Completed
+		{
+			get { return completed; }
+			set { completed = value; }
+		}
+
+		/// <remarks/>
+		[XmlAttribute("deleted", Form=XmlSchemaForm.Unqualified)]
+		public string RawDeleted
+		{
+			get { return rawDeleted; }
+			set {
+				if(value.Length > 0) {
+					rawDeleted = value;
+					deleted = Utils.DateStringToDateTime(rawDeleted);
+				}
+			}
+		}
+    
+		/// <summary>
+		/// Converts the raw created field to a <see cref="DateTime"/>.
+		/// </summary>
+		[XmlIgnore]
+		public DateTime Deleted
+		{
+			get { return deleted; }
+			set { deleted = value; }
+		}
+
+		/// <remarks/>
+		[XmlAttribute("priority", Form=XmlSchemaForm.Unqualified)]
+		public string Priority;
+
+		/// <remarks/>
+		[XmlAttribute("postponed", Form=XmlSchemaForm.Unqualified)]
+		public string Postponed;
+
+		/// <remarks/>
+		[XmlAttribute("estimate", Form=XmlSchemaForm.Unqualified)]
+		public string Estimate;
+    
+	}
+}

Added: trunk/RtmNet/User.cs
==============================================================================
--- (empty file)
+++ trunk/RtmNet/User.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,144 @@
+ïusing System;
+using System.Xml;
+
+namespace RtmNet
+{
+	/// <summary>
+	/// Contains details of a user
+	/// </summary>
+	[System.Serializable]
+	public class FoundUser
+	{
+		private string _userId;
+		private string _username;
+
+		/// <summary>
+		/// The ID of the found user.
+		/// </summary>
+		public string UserId
+		{
+			get { return _userId; }
+		}
+
+		/// <summary>
+		/// The username of the found user.
+		/// </summary>
+		public string Username 
+		{
+			get { return _username; }
+		}
+
+		internal FoundUser(string userId, string username)
+		{
+			_userId = userId;
+			_username = username;
+		}
+
+		internal FoundUser(XmlNode node)
+		{
+			if( node.Attributes["nsid"] != null )
+				_userId = node.Attributes["nsid"].Value;
+			if( node.Attributes["id"] != null )
+				_userId = node.Attributes["id"].Value;
+			if( node.Attributes["username"] != null )
+				_username = node.Attributes["username"].Value;
+			if( node.SelectSingleNode("username") != null )
+				_username = node.SelectSingleNode("username").InnerText;
+		}
+	}
+
+	/// <summary>
+	/// The upload status of the user, as returned by <see cref=Rtm.PeopleGetUploadStatus"/>.
+	/// </summary>
+	[System.Serializable]
+	public class UserStatus
+	{
+		private bool _isPro;
+		private string _userId;
+		private string _username;
+		private long _bandwidthMax;
+		private long _bandwidthUsed;
+		private long _filesizeMax;
+
+		internal UserStatus(XmlNode node)
+		{
+			if( node == null ) 
+				throw new ArgumentNullException("node");
+
+			if( node.Attributes["id"] != null )
+				_userId = node.Attributes["id"].Value;
+			if( node.Attributes["nsid"] != null )
+				_userId = node.Attributes["nsid"].Value;
+			if( node.Attributes["ispro"] != null )
+				_isPro = node.Attributes["ispro"].Value=="1";
+			if( node.SelectSingleNode("username") != null )
+				_username = node.SelectSingleNode("username").InnerText;
+			XmlNode bandwidth = node.SelectSingleNode("bandwidth");
+			if( bandwidth != null )
+			{
+				_bandwidthMax = Convert.ToInt64(bandwidth.Attributes["max"].Value);
+				_bandwidthUsed = Convert.ToInt64(bandwidth.Attributes["used"].Value);
+			}
+			XmlNode filesize = node.SelectSingleNode("filesize");
+			if( filesize != null )
+			{
+				_filesizeMax = Convert.ToInt64(filesize.Attributes["max"].Value);
+			}
+		}
+		/// <summary>
+		/// The id of the user object.
+		/// </summary>
+		public string UserId
+		{
+			get { return _userId; }
+		}
+
+		/// <summary>
+		/// The Username of the selected user.
+		/// </summary>
+		public string UserName
+		{
+			get { return _username; }
+		}
+
+		/// <summary>
+		/// Is the current user a Pro account.
+		/// </summary>
+		public bool IsPro
+		{
+			get { return _isPro; }
+		}
+
+		/// <summary>
+		/// The maximum bandwidth (in bytes) that the user can use each month.
+		/// </summary>
+		public long BandwidthMax
+		{
+			get { return _bandwidthMax; }
+		}
+
+		/// <summary>
+		/// The number of bytes of the current months bandwidth that the user has used.
+		/// </summary>
+		public long BandwidthUsed
+		{
+			get { return _bandwidthUsed; }
+		}
+
+		/// <summary>
+		/// The maximum filesize (in bytes) that the user is allowed to upload.
+		/// </summary>
+		public long FilesizeMax
+		{
+			get { return _filesizeMax; }
+		}
+
+		/// <summary>
+		/// <see cref="Double"/> representing the percentage bandwidth used so far. Will range from 0 to 1.
+		/// </summary>
+		public Double PercentageUsed
+		{
+			get { return BandwidthUsed * 1.0 / BandwidthMax; }
+		}
+	}
+}
\ No newline at end of file

Added: trunk/RtmNet/Utils.cs
==============================================================================
--- (empty file)
+++ trunk/RtmNet/Utils.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,233 @@
+using System;
+using System.IO;
+using System.Collections;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Xml.Serialization;
+
+namespace RtmNet
+{
+	/// <summary>
+	/// Internal class providing certain utility functions to other classes.
+	/// </summary>
+	internal sealed class Utils
+	{
+		private static readonly DateTime unixStartDate = new DateTime(1970, 1, 1, 0, 0, 0);
+
+		private Utils()
+		{
+		}
+
+#if !WindowsCE
+		internal static string UrlEncode(string oldString)
+		{
+			if( oldString == null ) return null;
+
+			string a = System.Web.HttpUtility.UrlEncode(oldString);
+			a = a.Replace("&", "%26");
+			a = a.Replace("=", "%3D");
+			a = a.Replace(" ", "%20");
+			return a;
+		}
+#else
+        internal static string UrlEncode(string oldString)
+        {
+            if (oldString == null) return String.Empty;
+            StringBuilder sb = new StringBuilder(oldString.Length * 2);
+            Regex reg = new Regex("[a-zA-Z0-9$-_.+!*'(),]");
+
+            foreach (char c in oldString)
+            {
+                if (reg.IsMatch(c.ToString()))
+                {
+                    sb.Append(c);
+                }
+                else
+                {
+                    sb.Append(ToHex(c));
+                }
+            }
+            return sb.ToString();
+        }
+
+        private static string ToHex(char c)
+        {
+            return ((int)c).ToString("X");
+        }
+#endif
+
+		/// <summary>
+		/// Converts a <see cref="DateTime"/> object into a unix timestamp number.
+		/// </summary>
+		/// <param name="date">The date to convert.</param>
+		/// <returns>A long for the number of seconds since 1st January 1970, as per unix specification.</returns>
+		internal static long DateToUnixTimestamp(DateTime date)
+		{
+			TimeSpan ts = date - unixStartDate;
+			return (long)ts.TotalSeconds;
+		}
+
+		/// <summary>
+		/// Converts a string, representing a unix timestamp number into a <see cref="DateTime"/> object.
+		/// </summary>
+		/// <param name="timestamp">The timestamp, as a string.</param>
+		/// <returns>The <see cref="DateTime"/> object the time represents.</returns>
+		internal static DateTime UnixTimestampToDate(string timestamp)
+		{
+			if( timestamp == null || timestamp.Length == 0 ) return DateTime.MinValue;
+
+			
+			return UnixTimestampToDate(long.Parse(timestamp));
+		}
+
+		/// <summary>
+		/// Converts a <see cref="long"/>, representing a unix timestamp number into a <see cref="DateTime"/> object.
+		/// </summary>
+		/// <param name="timestamp">The unix timestamp.</param>
+		/// <returns>The <see cref="DateTime"/> object the time represents.</returns>
+		internal static DateTime UnixTimestampToDate(long timestamp)
+		{
+			return unixStartDate.AddSeconds(timestamp);
+		}
+
+
+		internal static DateTime DateStringToDateTime(string timestring)
+		{
+			if (timestring == null | timestring.Length == 0) return DateTime.MinValue;
+			DateTime dt = DateTime.Parse(timestring);
+			return dt;
+		}
+
+		internal static void WriteInt32(Stream s, int i)
+		{
+			s.WriteByte((byte) (i & 0xFF));
+			s.WriteByte((byte) ((i >> 8) & 0xFF));
+			s.WriteByte((byte) ((i >> 16) & 0xFF));
+			s.WriteByte((byte) ((i >> 24) & 0xFF));
+		}
+
+		internal static void WriteString(Stream s, string str)
+		{
+			WriteInt32(s, str.Length);
+			foreach (char c in str)
+			{
+				s.WriteByte((byte) (c & 0xFF));
+				s.WriteByte((byte) ((c >> 8) & 0xFF));
+			}
+		}
+
+		internal static void WriteAsciiString(Stream s, string str)
+		{
+			WriteInt32(s, str.Length);
+			foreach (char c in str)
+			{
+				s.WriteByte((byte) (c & 0x7F));
+			}
+		}
+
+		internal static int ReadInt32(Stream s)
+		{
+			int i = 0, b;
+			for (int j = 0; j < 4; j++)
+			{
+				b = s.ReadByte();
+				if (b == -1)
+					throw new IOException("Unexpected EOF encountered");
+				i |= (b << (j * 8));
+			}
+			return i;
+		}
+
+		internal static string ReadString(Stream s)
+		{
+			int len = ReadInt32(s);
+			char[] chars = new char[len];
+			for (int i = 0; i < len; i++)
+			{
+				int hi, lo;
+				lo = s.ReadByte();
+				hi = s.ReadByte();
+				if (lo == -1 || hi == -1)
+					throw new IOException("Unexpected EOF encountered");
+				chars[i] = (char) (lo | (hi << 8));
+			}
+			return new string(chars);
+		}
+
+		internal static string ReadAsciiString(Stream s)
+		{
+			int len = ReadInt32(s);
+			char[] chars = new char[len];
+			for (int i = 0; i < len; i++)
+			{
+				int c = s.ReadByte();
+				if (c == -1)
+					throw new IOException("Unexpected EOF encountered");
+				chars[i] = (char) (c & 0x7F);
+			}
+			return new string(chars);
+		}
+	
+		private const string photoUrl = "http://farm{0}.static.Rtm.com/{1}/{2}_{3}{4}.{5}";;
+
+		private static readonly Hashtable _serializers = new Hashtable();
+
+		private static XmlSerializer GetSerializer(Type type)
+		{
+			if( _serializers.ContainsKey(type.Name) )
+				return (XmlSerializer)_serializers[type.Name];
+			else
+			{
+				XmlSerializer s = new XmlSerializer(type);
+				_serializers.Add(type.Name, s);
+				return s;
+			}
+		}
+		/// <summary>
+		/// Converts the response string (in XML) into the <see cref="Response"/> object.
+		/// </summary>
+		/// <param name="responseString">The response from Rtm.</param>
+		/// <returns>A <see cref="Response"/> object containing the details of the </returns>
+		internal static Response Deserialize(string responseString)
+		{
+			XmlSerializer serializer = GetSerializer(typeof(RtmNet.Response));
+			try
+			{
+				// Deserialise the web response into the Rtm response object
+				StringReader responseReader = new StringReader(responseString);
+				RtmNet.Response response = (RtmNet.Response)serializer.Deserialize(responseReader);
+				responseReader.Close();
+
+				return response;
+			}
+			catch(InvalidOperationException ex)
+			{
+				// Serialization error occurred!
+				throw new ResponseXmlException("Invalid response received from Rtm.", ex);
+			}
+		}
+
+		internal static object Deserialize(System.Xml.XmlNode node, Type type)
+		{
+			XmlSerializer serializer = GetSerializer(type);
+			try
+			{
+				// Deserialise the web response into the Rtm response object
+				System.Xml.XmlNodeReader reader = new System.Xml.XmlNodeReader(node);
+				object o = serializer.Deserialize(reader);
+				reader.Close();
+
+				return o;
+			}
+			catch(InvalidOperationException ex)
+			{
+				// Serialization error occurred!
+				throw new ResponseXmlException("Invalid response received from Rtm.", ex);
+			}
+		}
+
+
+
+	}
+
+}

Added: trunk/RtmNet/example_app.config
==============================================================================
--- (empty file)
+++ trunk/RtmNet/example_app.config	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+
+	<configSections>
+		<section name="flickrNet" type="FlickrNet.FlickrConfigurationManager,FlickrNet"/>
+	</configSections>
+
+	<!-- FLICKR.NET CONFIGURATION
+
+	<flickrNet 
+		apiKey="1234567890abc"	// optional - your API Key
+		secret="2134123"		// optional - your shared secret
+		token="234234"			// optional - if using the same 
+		cacheSize="1234"		// optional, in bytes (defaults to 50 * 1024 * 1024 = 50MB)
+		cacheTimeout="[d.]HH:mm:ss"	// optional, defaults to 1 day (1.00:00:00) - day component is optional
+					// e.g. 10 minutes = 00:10:00 or 1 hour = 01:00:00 or 2 days, 12 hours = 2.12:00:00
+		>
+		<proxy		// proxy element is optional, but if included the attributes below are mandatory as mentioned
+			ipaddress="127.0.0.1"	// mandatory
+			port="8000"				// mandatory
+			username="username"		// optional, but must have password if included
+			password="password"		// optional, see username
+			domain="domain"			// optional, used for Microsoft authenticated proxy servers
+			/>
+	</flickrNet>
+	-->
+	
+	<flickrNet apiKey="1234567890abc" secret="2134123" token="234234" cacheSize="1234" cacheTimeout="[d.]HH:mm:ss">
+		<proxy ipaddress="127.0.0.1" port="8000" username="username" password="password" domain="domain"/>
+	</flickrNet>
+
+</configuration>

Added: trunk/TODO
==============================================================================
--- (empty file)
+++ trunk/TODO	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,3 @@
+Our task list is now kept in RTM and managed using Tasquer:
+
+http://www.rememberthemilk.com/home/btimothy/2588357/

Added: trunk/autogen.sh
==============================================================================
--- (empty file)
+++ trunk/autogen.sh	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,20 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+PKG_NAME="tasquer"
+
+(test -f $srcdir/configure.ac) || {
+    echo -n "**Error**: Directory "\`$srcdir\'" does not look like the"
+    echo " top-level $PKG_NAME directory"
+    exit 1
+}
+
+which gnome-autogen.sh || {
+    echo "You need to install gnome-common from the GNOME CVS"
+    exit 1
+}
+
+REQUIRED_AUTOMAKE_VERSION=1.9 USE_GNOME2_MACROS=1 . gnome-autogen.sh

Added: trunk/configure.ac
==============================================================================
--- (empty file)
+++ trunk/configure.ac	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,218 @@
+dnl Warning: This is an automatically generated file, do not edit!
+dnl Process this file with autoconf to produce a configure script.
+AC_PREREQ([2.54])
+AC_INIT([tasquer], [0.1.5])
+AM_INIT_AUTOMAKE([foreign tar-ustar])
+AM_MAINTAINER_MODE
+
+GNOME_COMMON_INIT
+
+AC_PROG_INSTALL
+IT_PROG_INTLTOOL([0.35])
+AC_PROG_LIBTOOL
+
+dnl pkg-config
+AC_PATH_PROG(PKG_CONFIG, pkg-config, no)
+if test "x$PKG_CONFIG" = "xno"; then
+        AC_MSG_ERROR([You need to install pkg-config])
+fi
+
+AC_PROG_INSTALL
+
+AC_PATH_PROG(GMCS, gmcs, no)
+if test "x$GMCS" = "xno"; then
+        AC_MSG_ERROR([gmcs Not found])
+fi
+
+#
+# Setup GETTEXT
+#
+
+GETTEXT_PACKAGE=tasquer
+AC_SUBST(GETTEXT_PACKAGE)
+AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE", [Gettext package])
+AM_GLIB_GNU_GETTEXT
+
+#
+# Find gconftool-2
+#
+AC_PATH_PROG(GCONFTOOL, gconftool-2)
+AM_GCONF_SOURCE_2
+
+PKG_CHECK_MODULES([GLIB_SHARP_20], [glib-sharp-2.0])
+PKG_CHECK_MODULES([GNOME_SHARP_20], [gnome-sharp-2.0])
+PKG_CHECK_MODULES([GTK_DOTNET_20], [gtk-dotnet-2.0])
+PKG_CHECK_MODULES([NDESK_DBUS_10], [ndesk-dbus-1.0])
+PKG_CHECK_MODULES([NDESK_DBUS_GLIB_10], [ndesk-dbus-glib-1.0])
+
+#
+# Allow the project to build without notify-sharp
+#
+PKG_CHECK_MODULES(NOTIFY_SHARP, notify-sharp, enable_notify_sharp="yes", enable_notify_sharp="no")
+AM_CONDITIONAL(ENABLE_NOTIFY_SHARP, test "x$enable_notify_sharp" = "xyes")
+AC_SUBST(NOTIFY_SHARP_LIBS)
+
+AC_ARG_ENABLE(debug,
+	AC_HELP_STRING([--enable-debug],
+		[Use 'Debug' Configuration [default=yes]]),
+		enable_debug=yes, enable_debug=no)
+AM_CONDITIONAL(ENABLE_DEBUG, test "x$enable_debug" = "xyes")
+if test "x$enable_debug" = "xyes" ; then
+#	DEBUG_CONFIG_LIBRARIES=' ${pkglibdir}/Db4objects.Db4o.dll'
+#	DEBUG_CONFIG_LIBS=' -r:${pkglibdir}/Db4objects.Db4o.dll'
+#	AC_SUBST(DEBUG_CONFIG_LIBRARIES)
+#	AC_SUBST(DEBUG_CONFIG_LIBS)
+	CONFIG_REQUESTED="yes"
+fi
+AC_ARG_ENABLE(release,
+	AC_HELP_STRING([--enable-release],
+		[Use 'Release' Configuration [default=no]]),
+		enable_release=yes, enable_release=no)
+AM_CONDITIONAL(ENABLE_RELEASE, test "x$enable_release" = "xyes")
+if test "x$enable_release" = "xyes" ; then
+#	RELEASE_CONFIG_LIBRARIES=' ${pkglibdir}/Db4objects.Db4o.dll'
+#	RELEASE_CONFIG_LIBS=' -r:${pkglibdir}/Db4objects.Db4o.dll'
+#	AC_SUBST(RELEASE_CONFIG_LIBRARIES)
+#	AC_SUBST(RELEASE_CONFIG_LIBS)
+	CONFIG_REQUESTED="yes"
+fi
+if test -z "$CONFIG_REQUESTED" ; then
+#	DEBUG_CONFIG_LIBRARIES=' ${pkglibdir}/Db4objects.Db4o.dll'
+#	DEBUG_CONFIG_LIBS=' -r:${pkglibdir}/Db4objects.Db4o.dll'
+#	AC_SUBST(DEBUG_CONFIG_LIBRARIES)
+#	AC_SUBST(DEBUG_CONFIG_LIBS)
+	AM_CONDITIONAL(ENABLE_DEBUG, true)
+fi
+
+
+#
+# DEFINES uses for #if statements
+#
+CSC_DEFINES=""
+if pkg-config --atleast-version=2.10 gtk-sharp-2.0; then
+	CSC_DEFINES="-d:GTK_2_10"
+fi
+AC_SUBST(CSC_DEFINES)
+
+
+AC_ARG_WITH(dbus_service_dir, [  --with-dbus-service-dir=DIR            Where to install Tasquer's DBus service file.])
+AM_CONDITIONAL(WITH_DBUS_SERVICE_DIR, test "x$with_dbus_service_dir" != "x")
+if test "x$with_dbus_service_dir" != "x"; then
+	DBUS_SERVICE_DIR=$with_dbus_service_dir
+else
+	DBUS_SERVICE_DIR=${datadir}/dbus-1/services
+fi
+AC_SUBST(DBUS_SERVICE_DIR)
+
+#
+# Dummy Backend (for debugging)
+#
+AC_ARG_ENABLE(backend_dummy,
+	AC_HELP_STRING([--enable-backend-dummy],
+		[Enable the Dummy (Debug) Backend [default=no]]),
+		enable_backend_dummy=yes, enable_backend_dummy=no)
+AM_CONDITIONAL(ENABLE_BACKEND_DUMMY, test "x$enable_backend_dummy" = "xyes")
+
+#
+# Remember the Milk Backend
+#
+AC_ARG_ENABLE(backend_rtm,
+	AC_HELP_STRING([--enable-backend-rtm],
+		[Enable the RTM Backend [default=yes]]),
+		enable_backend_rtm=yes, enable_backend_rtm=no)
+
+#
+# SQLite Backend
+#
+AC_ARG_ENABLE(backend_sqlite,
+	AC_HELP_STRING([--enable-backend-sqlite],
+		[Enable the SQLite Backend [default=no]]),
+		enable_backend_sqlite=yes, enable_backend_sqlite=no)
+AM_CONDITIONAL(ENABLE_BACKEND_SQLITE, test "x$enable_backend_sqlite" = "xyes")
+
+#
+# ICEcore for IceBackend Support
+#
+PKG_CHECK_MODULES(ICE_DESKTOP, Novell.IceDesktop, enable_ice_desktop="yes", enable_ice_desktop="no")
+AC_SUBST(ICE_DESKTOP_LIBS)
+AC_ARG_ENABLE(backend_icecore,
+	AC_HELP_STRING([--enable-backend-icecore],
+		[Enable the ICEcore Backend [default=no]]),
+		enable_backend_icecore=yes, enable_backend_icecore=no)
+AM_CONDITIONAL(ENABLE_BACKEND_ICECORE, test "x$enable_backend_icecore" = "xyes")
+
+#
+# Evolution-Sharp for EDSBackend Support
+#
+
+AC_ARG_ENABLE(backend_eds,
+       AC_HELP_STRING([--enable-backend-eds],
+               [Enable the EDS Backend [default=no]])  ,
+               enable_backend_eds=yes, enable_backend_eds=no)]
+AM_CONDITIONAL(ENABLE_BACKEND_EDS, test "x$enable_backend_eds" = "xyes")
+if test "x$enable_backend_eds" = "xyes" ; then
+       # FIXME : Is this the right way to do this ?
+       PKG_CHECK_MODULES(EVOLUTION_SHARP, evolution-sharp)
+fi
+AC_SUBST(EVOLUTION_SHARP_LIBS)
+
+#
+# If no backends were enabled,
+# enable the RTM Backend by default.
+#
+if test "x$enable_backend_dummy" = "xno" ; then
+	if test "x$enable_backend_rtm" = "xno" ; then
+		if test "x$enable_backend_sqlite" = "xno" ; then
+			if test "x$enable_backend_sqlite" = "xno" ; then
+				if test "x$enable_backend_eds" = "xno" ; then
+					# No other backend was enabled, so enable
+					# the RTM Backend by default
+					enable_backend_rtm=yes
+				fi
+			fi
+		fi
+	fi
+fi
+# Define ENABLE_BACKEND_RTM here so it only gets defined once
+AM_CONDITIONAL(ENABLE_BACKEND_RTM, test "x$enable_backend_rtm" = "xyes")
+
+### Begin GAC tool ###
+
+AC_PATH_PROG(GACUTIL, gacutil, no)
+if test "x$GACUTIL" = "xno" ; then
+        AC_MSG_ERROR([No gacutil tool found])
+fi
+
+AC_SUBST(GACUTIL)
+GACUTIL_FLAGS='/gacdir $(DESTDIR)$(prefix)'
+AC_SUBST(GACUTIL_FLAGS)
+
+### End GAC tool ###
+
+AC_CONFIG_FILES([
+./Makefile
+./RtmNet/Makefile
+./src/Makefile
+./src/tasquer.pc
+./data/Makefile
+./data/images/Makefile
+./data/sounds/Makefile
+./po/Makefile.in
+])
+
+AC_OUTPUT
+
+echo "
+Configuration:
+
+	Prefix:            ${prefix}
+	Debug build:       ${enable_debug}
+	Release build:     ${enable_release}
+	Notification:      ${enable_notify_sharp}
+	Dummy Backend:     ${enable_backend_dummy}
+	Evolution Backend: ${enable_backend_eds}
+	ICECore Backend:   ${enable_backend_icecore}
+	RTM Backend:       ${enable_backend_rtm}
+	SQLite Backend:    ${enable_backend_sqlite}
+"
+

Added: trunk/create-task.py
==============================================================================
--- (empty file)
+++ trunk/create-task.py	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+import sys, dbus, dbus.glib
+
+#import pdb
+
+# Get the D-Bus Session Bus
+bus = dbus.SessionBus()
+
+#Access the ICEcore Daemon Object
+obj = bus.get_object("org.gnome.Tasky",
+	"/org/gnome/Tasky/RemoteControl")
+
+#Access the remote control interface
+tasky = dbus.Interface(obj, "org.gnome.Tasky.RemoteControl")
+
+for n in tasky.GetCategoryNames():
+	print n
+
+taskId = tasky.CreateTask ("Tasky", "Create a task via DBus", True)
+
+print "taskId: " + taskId

Added: trunk/data/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/data/Makefile.am	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,31 @@
+
+SUBDIRS = images sounds 
+
+ INTLTOOL_DESKTOP_RULE@
+
+desktop_in_files = tasquer.desktop.in
+desktop_files    = $(desktop_in_files:.desktop.in=.desktop)
+
+desktopdir   = $(datadir)/applications
+desktop_DATA = $(desktop_files)
+
+dbusservicedir       = $(DBUS_SERVICE_DIR)
+dbusservice_in_files = org.gnome.Tasquer.service.in
+dbusservice_DATA     = $(dbusservice_in_files:.service.in=.service)
+
+$(dbusservice_DATA): $(dbusservice_in_files) Makefile
+	sed -e "s|\ bindir\@|$(bindir)|g"	\
+	    -e "s|\ wrapper\@|tasquer|g"		\
+	    < $< > $@
+
+noinst_DATA = 
+
+EXTRA_DIST = 					\
+	$(desktop_in_files)     		\
+	$(noinst_DATA)				\
+	org.gnome.Tasquer.service.in
+
+DISTCLEANFILES = 				\
+	$(desktop_files)			\
+	org.gnome.Tasquer.service
+	

Added: trunk/data/images/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/data/images/Makefile.am	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,62 @@
+
+pixmapdir = $(datadir)/pixmaps
+pixmap_DATA = 	tasquer-16.png \
+		tasquer-22.png \
+		tasquer-24.png \
+		tasquer-32.png \
+		tasquer-48.png \
+		note-16.png \
+		rtmLogo.png
+
+hicolordir = $(datadir)/icons/hicolor
+
+tango_icons =	tasquer-16.png \
+		tasquer-22.png \
+		tasquer-24.png \
+		tasquer-32.png \
+		tasquer-48.png \
+		note-16.png \
+		rtmLogo.png
+
+clock_icons = clock-16-0.png \
+		clock-16-1.png \
+		clock-16-2.png \
+		clock-16-3.png \
+		clock-16-4.png \
+		clock-16-5.png \
+		clock-16-6.png \
+		clock-16-7.png \
+		clock-16-8.png \
+		clock-16-9.png \
+		clock-16-10.png \
+		clock-16-11.png 
+
+install-data-local:
+	@-$(mkinstalldirs) $(DESTDIR)$(hicolordir)/16x16/apps $(DESTDIR)$(hicolordir)/22x22/apps $(DESTDIR)$(hicolordir)/24x24/apps $(DESTDIR)$(hicolordir)/32x32/apps $(DESTDIR)$(hicolordir)/48x48/apps
+	$(INSTALL_DATA) $(srcdir)/tasquer-16.png $(DESTDIR)$(hicolordir)/16x16/apps/tasquer.png
+	$(INSTALL_DATA) $(srcdir)/tasquer-22.png $(DESTDIR)$(hicolordir)/22x22/apps/tasquer.png
+	$(INSTALL_DATA) $(srcdir)/tasquer-24.png $(DESTDIR)$(hicolordir)/24x24/apps/tasquer.png
+	$(INSTALL_DATA) $(srcdir)/tasquer-32.png $(DESTDIR)$(hicolordir)/32x32/apps/tasquer.png
+	$(INSTALL_DATA) $(srcdir)/tasquer-48.png $(DESTDIR)$(hicolordir)/48x48/apps/tasquer.png
+
+gtk_update_icon_cache = gtk-update-icon-cache -f -t $(datadir)/icons/hicolor
+
+install-data-hook:
+	@-if test -z "$(DESTDIR)"; then \
+		echo "Updating Gtk icon cache."; \
+		$(gtk_update_icon_cache); \
+	else \
+		echo "*** Icon cache not updated.  After install, run this:"; \
+		echo "***   $(gtk_update_icon_cache)"; \
+	fi
+
+uninstall-hook:
+	rm -f $(DESTDIR)$(hicolordir)/16x16/apps/tasquer.png
+	rm -f $(DESTDIR)$(hicolordir)/22x22/apps/tasquer.png
+	rm -f $(DESTDIR)$(hicolordir)/24x24/apps/tasquer.png
+	rm -f $(DESTDIR)$(hicolordir)/32x32/apps/tasquer.png
+	rm -f $(DESTDIR)$(hicolordir)/48x48/apps/tasquer.png
+
+noinst_DATA =
+
+EXTRA_DIST = $(noinst_DATA) $(pixmap_DATA) $(tango_icons) $(clock_icons)

Added: trunk/data/images/clock-16-0.png
==============================================================================
Binary file. No diff available.

Added: trunk/data/images/clock-16-0.svg
==============================================================================
--- (empty file)
+++ trunk/data/images/clock-16-0.svg	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://web.resource.org/cc/";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   width="16px"
+   height="16px"
+   id="svg2160"
+   sodipodi:version="0.32"
+   inkscape:version="0.45.1"
+   sodipodi:docbase="/home/bean/sandbox/images/inkscape"
+   sodipodi:docname="timer-16-0.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape">
+  <defs
+     id="defs2162" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="22.197802"
+     inkscape:cx="8"
+     inkscape:cy="8"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="872"
+     inkscape:window-height="624"
+     inkscape:window-x="166"
+     inkscape:window-y="106" />
+  <metadata
+     id="metadata2165">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer">
+    <path
+       sodipodi:type="arc"
+       style="fill:#babdb6;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       id="path2170"
+       sodipodi:cx="8.0188122"
+       sodipodi:cy="8.0487623"
+       sodipodi:rx="8.0188122"
+       sodipodi:ry="8.041337"
+       d="M 16.037624 8.0487623 A 8.0188122 8.041337 0 1 1  0,8.0487623 A 8.0188122 8.041337 0 1 1  16.037624 8.0487623 z"
+       transform="matrix(0,-0.997654,0.9948594,0,-7.3868077e-3,16)" />
+  </g>
+</svg>

Added: trunk/data/images/clock-16-1.png
==============================================================================
Binary file. No diff available.

Added: trunk/data/images/clock-16-10.png
==============================================================================
Binary file. No diff available.

Added: trunk/data/images/clock-16-11.png
==============================================================================
Binary file. No diff available.

Added: trunk/data/images/clock-16-2.png
==============================================================================
Binary file. No diff available.

Added: trunk/data/images/clock-16-3.png
==============================================================================
Binary file. No diff available.

Added: trunk/data/images/clock-16-4.png
==============================================================================
Binary file. No diff available.

Added: trunk/data/images/clock-16-5.png
==============================================================================
Binary file. No diff available.

Added: trunk/data/images/clock-16-6.png
==============================================================================
Binary file. No diff available.

Added: trunk/data/images/clock-16-7.png
==============================================================================
Binary file. No diff available.

Added: trunk/data/images/clock-16-8.png
==============================================================================
Binary file. No diff available.

Added: trunk/data/images/clock-16-9.png
==============================================================================
Binary file. No diff available.

Added: trunk/data/images/note-16.png
==============================================================================
Binary file. No diff available.

Added: trunk/data/images/note-22.svg
==============================================================================
--- (empty file)
+++ trunk/data/images/note-22.svg	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,366 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://web.resource.org/cc/";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:xlink="http://www.w3.org/1999/xlink";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   width="22"
+   height="22"
+   id="svg2"
+   sodipodi:version="0.32"
+   inkscape:version="0.45.1"
+   version="1.0"
+   sodipodi:docbase="/home/jimmac/src/tomboy/data/images"
+   sodipodi:docname="tomboy-notebook-22.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   inkscape:export-filename="/home/jimmac/src/tomboy/data/images/tomboy-notebook-22.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3537">
+      <stop
+         style="stop-color:#4e9a06;stop-opacity:1;"
+         offset="0"
+         id="stop3539" />
+      <stop
+         id="stop3545"
+         offset="0.5"
+         style="stop-color:#acf962;stop-opacity:1;" />
+      <stop
+         style="stop-color:#4e9a06;stop-opacity:1"
+         offset="1"
+         id="stop3541" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient3505">
+      <stop
+         style="stop-color:#e5e5e2;stop-opacity:1"
+         offset="0"
+         id="stop3507" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1"
+         offset="1"
+         id="stop3509" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient3497">
+      <stop
+         style="stop-color:#babdb6;stop-opacity:1;"
+         offset="0"
+         id="stop3499" />
+      <stop
+         style="stop-color:#757872;stop-opacity:1"
+         offset="1"
+         id="stop3501" />
+    </linearGradient>
+    <filter
+       inkscape:collect="always"
+       x="-0.1536982"
+       width="1.3073964"
+       y="-1.0062964"
+       height="3.0125928"
+       id="filter3467">
+      <feGaussianBlur
+         inkscape:collect="always"
+         stdDeviation="1.1699417"
+         id="feGaussianBlur3469" />
+    </filter>
+    <filter
+       inkscape:collect="always"
+       x="-0.059383396"
+       width="1.1187668"
+       y="-0.38879634"
+       height="1.7775927"
+       id="filter3493">
+      <feGaussianBlur
+         inkscape:collect="always"
+         stdDeviation="0.45202294"
+         id="feGaussianBlur3495" />
+    </filter>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3497"
+       id="linearGradient3503"
+       x1="9.4540577"
+       y1="6.3363867"
+       x2="10.013865"
+       y2="18.920727"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(1,1)" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3505"
+       id="linearGradient3511"
+       x1="14.492193"
+       y1="8.6543407"
+       x2="14.492193"
+       y2="15.763665"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(1,1)" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3537"
+       id="linearGradient3543"
+       x1="5.4877682"
+       y1="2.056761"
+       x2="5.0294805"
+       y2="4.9089522"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3537"
+       id="linearGradient3549"
+       gradientUnits="userSpaceOnUse"
+       x1="5.4877682"
+       y1="2.056761"
+       x2="5.0294805"
+       y2="4.9089522" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3537"
+       id="linearGradient3553"
+       gradientUnits="userSpaceOnUse"
+       x1="5.4877682"
+       y1="2.056761"
+       x2="5.0294805"
+       y2="4.9089522" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3537"
+       id="linearGradient3557"
+       gradientUnits="userSpaceOnUse"
+       x1="5.4877682"
+       y1="2.056761"
+       x2="5.0294805"
+       y2="4.9089522" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3537"
+       id="linearGradient3561"
+       gradientUnits="userSpaceOnUse"
+       x1="5.4877682"
+       y1="2.056761"
+       x2="5.0294805"
+       y2="4.9089522" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3537"
+       id="linearGradient3565"
+       gradientUnits="userSpaceOnUse"
+       x1="5.4877682"
+       y1="2.056761"
+       x2="5.0294805"
+       y2="4.9089522" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#e0e0e0"
+     borderopacity="1"
+     gridtolerance="10000"
+     guidetolerance="10"
+     objecttolerance="10"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1"
+     inkscape:cx="17.75999"
+     inkscape:cy="1.0313139"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     width="22px"
+     height="22px"
+     borderlayer="true"
+     inkscape:showpageshadow="false"
+     inkscape:object-points="false"
+     showgrid="false"
+     inkscape:window-width="872"
+     inkscape:window-height="627"
+     inkscape:window-x="0"
+     inkscape:window-y="25" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1">
+    <g
+       id="g3513"
+       style="opacity:0.43434341"
+       transform="translate(1,1)">
+      <rect
+         transform="matrix(0.9163789,0,0,0.8099374,0.847444,3.4962296)"
+         style="opacity:0.42424239;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;filter:url(#filter3493)"
+         id="rect3471"
+         width="18.26866"
+         height="2.7902913"
+         x="1"
+         y="17"
+         rx="1.5224551"
+         ry="1.7225353" />
+      <rect
+         ry="1.3951457"
+         rx="1.3951457"
+         y="17"
+         x="1"
+         height="2.7902913"
+         width="18.26866"
+         id="rect3333"
+         style="opacity:0.42424239;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;filter:url(#filter3467)" />
+    </g>
+    <rect
+       style="fill:url(#linearGradient3511);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient3503);stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect2362"
+       width="15.027728"
+       height="15.920728"
+       x="3.5"
+       y="3.5"
+       rx="1.2247444"
+       ry="1.2147088" />
+    <rect
+       style="opacity:0.43434341;fill:#757872;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3517"
+       width="10"
+       height="1.0493827"
+       x="6"
+       y="7.0277271"
+       rx="0.52469134"
+       ry="0.52469134" />
+    <rect
+       ry="0.52469134"
+       rx="0.52469134"
+       y="9.0277271"
+       x="6"
+       height="1.0493827"
+       width="10"
+       id="rect3519"
+       style="opacity:0.43434341;fill:#757872;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+    <rect
+       style="opacity:0.43434341;fill:#757872;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3521"
+       width="4"
+       height="1.0493827"
+       x="6"
+       y="11.027727"
+       rx="0.52469134"
+       ry="0.52469134" />
+    <rect
+       ry="0.52469134"
+       rx="0.52469134"
+       y="14"
+       x="6"
+       height="1.0493827"
+       width="10"
+       id="rect3523"
+       style="opacity:0.43434341;fill:#757872;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+    <rect
+       style="opacity:0.43434341;fill:#757872;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3525"
+       width="7"
+       height="1.0493827"
+       x="6"
+       y="16"
+       rx="0.52469134"
+       ry="0.52469134" />
+    <path
+       style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1"
+       d="M 4,18 L 4,5 C 4.067658,4.3345725 4.2711745,3.8389681 5,4 L 17,4 C 17.491215,4.1359811 18.140502,4.3243727 18,5 L 18,14 L 17,5 L 5,5 L 4,18 z "
+       id="path3567"
+       sodipodi:nodetypes="ccccccccc" />
+    <path
+       sodipodi:type="arc"
+       style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient3543);stroke-width:1.90178537;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="path3527"
+       sodipodi:cx="6.5625"
+       sodipodi:cy="3.5625"
+       sodipodi:rx="1.4375"
+       sodipodi:ry="2.25"
+       d="M 7.3834052,5.4095367 A 1.4375,2.25 0 1 1 7.9363953,2.9006187"
+       sodipodi:start="0.96299433"
+       sodipodi:end="5.9845989"
+       sodipodi:open="true"
+       transform="matrix(0.3330556,0,0,0.8301571,3.8567391,0.6604365)" />
+    <path
+       transform="matrix(0.3330556,0,0,0.8301571,5.7930679,0.6604365)"
+       sodipodi:open="true"
+       sodipodi:end="5.9845989"
+       sodipodi:start="0.96299433"
+       d="M 7.3834052,5.4095367 A 1.4375,2.25 0 1 1 7.9363953,2.9006187"
+       sodipodi:ry="2.25"
+       sodipodi:rx="1.4375"
+       sodipodi:cy="3.5625"
+       sodipodi:cx="6.5625"
+       id="path3547"
+       style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient3549);stroke-width:1.90178537;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       sodipodi:type="arc" />
+    <path
+       sodipodi:type="arc"
+       style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient3553);stroke-width:1.90178537;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="path3551"
+       sodipodi:cx="6.5625"
+       sodipodi:cy="3.5625"
+       sodipodi:rx="1.4375"
+       sodipodi:ry="2.25"
+       d="M 7.3834052,5.4095367 A 1.4375,2.25 0 1 1 7.9363953,2.9006187"
+       sodipodi:start="0.96299433"
+       sodipodi:end="5.9845989"
+       sodipodi:open="true"
+       transform="matrix(0.3330556,0,0,0.8301571,7.7930679,0.6604365)" />
+    <path
+       transform="matrix(0.3330556,0,0,0.8301571,9.7930679,0.6604365)"
+       sodipodi:open="true"
+       sodipodi:end="5.9845989"
+       sodipodi:start="0.96299433"
+       d="M 7.3834052,5.4095367 A 1.4375,2.25 0 1 1 7.9363953,2.9006187"
+       sodipodi:ry="2.25"
+       sodipodi:rx="1.4375"
+       sodipodi:cy="3.5625"
+       sodipodi:cx="6.5625"
+       id="path3555"
+       style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient3557);stroke-width:1.90178537;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       sodipodi:type="arc" />
+    <path
+       sodipodi:type="arc"
+       style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient3561);stroke-width:1.90178537;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="path3559"
+       sodipodi:cx="6.5625"
+       sodipodi:cy="3.5625"
+       sodipodi:rx="1.4375"
+       sodipodi:ry="2.25"
+       d="M 7.3834052,5.4095367 A 1.4375,2.25 0 1 1 7.9363953,2.9006187"
+       sodipodi:start="0.96299433"
+       sodipodi:end="5.9845989"
+       sodipodi:open="true"
+       transform="matrix(0.3330556,0,0,0.8301571,11.793068,0.6604365)" />
+    <path
+       transform="matrix(0.3330556,0,0,0.8301571,13.730568,0.6604365)"
+       sodipodi:open="true"
+       sodipodi:end="5.9845989"
+       sodipodi:start="0.96299433"
+       d="M 7.3834052,5.4095367 A 1.4375,2.25 0 1 1 7.9363953,2.9006187"
+       sodipodi:ry="2.25"
+       sodipodi:rx="1.4375"
+       sodipodi:cy="3.5625"
+       sodipodi:cx="6.5625"
+       id="path3563"
+       style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient3565);stroke-width:1.90178537;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       sodipodi:type="arc" />
+  </g>
+</svg>

Added: trunk/data/images/rtmLogo.png
==============================================================================
Binary file. No diff available.

Added: trunk/data/images/tasky.svg
==============================================================================
--- (empty file)
+++ trunk/data/images/tasky.svg	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://web.resource.org/cc/";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   width="744.09448819"
+   height="1052.3622047"
+   id="svg2"
+   sodipodi:version="0.32"
+   inkscape:version="0.45.1"
+   sodipodi:docbase="/home/calvin/code/tasky/data/images"
+   sodipodi:docname="tasky.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   inkscape:export-filename="/home/calvin/code/tasky/data/images/tasky-256.png"
+   inkscape:export-xdpi="67.760002"
+   inkscape:export-ydpi="67.760002">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     gridtolerance="10000"
+     guidetolerance="10"
+     objecttolerance="10"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="0.61349736"
+     inkscape:cx="-119.83098"
+     inkscape:cy="905.06846"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     showguides="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1152"
+     inkscape:window-x="0"
+     inkscape:window-y="21" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1">
+    <path
+       style="fill:#eeeeec;fill-rule:evenodd;stroke:#555753;stroke-width:33.60154343;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M -1503.1992,-170.83705 L -1503.1992,115.5614 L -1216.8008,115.5614 L -1216.8008,-170.83705 L -1503.1992,-170.83705 z "
+       id="path2172"
+       inkscape:export-filename="/home/calvin/code/tasky/data/images/tasky-48.png"
+       inkscape:export-xdpi="12.71"
+       inkscape:export-ydpi="12.71" />
+    <path
+       style="fill:#ce5c00;stroke:#ce5c00;stroke-width:45.33768463;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none"
+       id="path5102"
+       d="M -1417.5164,-54.770532 C -1406.8274,-47.708522 -1398.2116,-38.297819 -1389.6001,-28.915163 C -1379.0687,-15.301258 -1368.9907,-1.3430004 -1358.5581,12.34821 C -1352.7265,19.895106 -1347.1338,27.587691 -1342.2662,35.786105 C -1341.1481,38.964137 -1338.5517,41.671857 -1336.9206,44.167032 C -1365.5836,59.744548 -1364.7236,65.277742 -1359.8744,41.4654 C -1354.5507,15.022872 -1343.7478,-9.8123823 -1333.0241,-34.391976 C -1318.0313,-68.88434 -1300.2158,-101.51521 -1277.3163,-131.28425 C -1264.4623,-148.12727 -1248.5179,-161.9012 -1231.1523,-173.77123 L -1202.6688,-184.96898 C -1220.6301,-173.40426 -1237.1671,-159.79771 -1250.4603,-142.865 C -1273.5438,-113.50057 -1291.6289,-81.217243 -1306.2318,-46.755228 C -1316.7248,-22.604696 -1327.449,1.8290553 -1332.1643,27.91764 C -1336.0803,49.941889 -1332.8167,50.284389 -1363.5655,61.253707 C -1365.6177,58.095551 -1368.1281,55.163739 -1369.464,51.453906 C -1374.03,43.289039 -1379.3489,35.614025 -1384.9497,28.117235 C -1395.0868,
 14.339547 -1405.1217,0.47010689 -1415.8381,-12.859362 C -1424.3773,-21.964634 -1432.8463,-31.322974 -1443.8314,-37.511223 L -1417.5164,-54.770532 z "
+       inkscape:export-filename="/home/calvin/code/tasky/data/images/tasky-48.png"
+       inkscape:export-xdpi="12.71"
+       inkscape:export-ydpi="12.71" />
+  </g>
+</svg>

Added: trunk/data/images/tasquer-16.png
==============================================================================
Binary file. No diff available.

Added: trunk/data/images/tasquer-22.png
==============================================================================
Binary file. No diff available.

Added: trunk/data/images/tasquer-24.png
==============================================================================
Binary file. No diff available.

Added: trunk/data/images/tasquer-32.png
==============================================================================
Binary file. No diff available.

Added: trunk/data/images/tasquer-48.png
==============================================================================
Binary file. No diff available.

Added: trunk/data/org.gnome.Tasquer.service.in
==============================================================================
--- (empty file)
+++ trunk/data/org.gnome.Tasquer.service.in	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,5 @@
+
+[D-BUS Service]
+Name=org.gnome.Tasquer
+Exec= bindir@/@wrapper@
+

Added: trunk/data/sounds/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/data/sounds/Makefile.am	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,15 @@
+SOUND_FILES = \
+	$(srcdir)/notify.wav 
+
+SOUNDSDIR = $(DESTDIR)$(datadir)/tasquer/sounds
+
+install-exec-local: all
+	$(mkinstalldirs) $(SOUNDSDIR)
+	$(INSTALL_PROGRAM) $(SOUND_FILES) $(SOUNDSDIR)
+
+uninstall-local:
+	rm -rf $(SOUNDSDIR)
+
+EXTRA_DIST = \
+	$(SOUND_FILES) 
+

Added: trunk/data/sounds/notify.wav
==============================================================================
Binary file. No diff available.

Added: trunk/data/tasquer.desktop.in
==============================================================================
--- (empty file)
+++ trunk/data/tasquer.desktop.in	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,14 @@
+
+[Desktop Entry]
+_Name=Tasquer
+_Comment=Easy quick task management
+_GenericName=Task Manager
+Exec=tasquer
+Icon=tasquer
+StartupNotify=false
+Terminal=false
+Type=Application
+Categories=GNOME;GTK;Office;X-SuSE-Core-Office;
+X-GNOME-Bugzilla-Bugzilla=GNOME
+X-GNOME-Bugzilla-Product=tasquer
+X-GNOME-Bugzilla-Component=General

Added: trunk/po/LINGUAS
==============================================================================
--- (empty file)
+++ trunk/po/LINGUAS	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,3 @@
+# please keep this list sorted alphabetically
+#
+fi

Added: trunk/po/Makefile.in.in
==============================================================================
--- (empty file)
+++ trunk/po/Makefile.in.in	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,218 @@
+# Makefile for program source directory in GNU NLS utilities package.
+# Copyright (C) 1995, 1996, 1997 by Ulrich Drepper <drepper gnu ai mit edu>
+#
+# This file file be copied and used freely without restrictions.  It can
+# be used in projects which are not available under the GNU Public License
+# but which still want to provide support for the GNU gettext functionality.
+# Please note that the actual code is *not* freely available.
+#
+# - Modified by Owen Taylor <otaylor redhat com> to use GETTEXT_PACKAGE
+#   instead of PACKAGE and to look for po2tbl in ./ not in intl/
+#
+# - Modified by jacob berkman <jacob ximian com> to install
+#   Makefile.in.in and po2tbl.sed.in for use with glib-gettextize
+#
+# - Modified by Rodney Dawes <dobey novell com> for use with intltool
+#
+# We have the following line for use by intltoolize:
+# INTLTOOL_MAKEFILE
+
+GETTEXT_PACKAGE = @GETTEXT_PACKAGE@
+PACKAGE = @PACKAGE@
+VERSION = @VERSION@
+
+SHELL = /bin/sh
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+top_builddir = @top_builddir@
+VPATH = @srcdir@
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+datadir = @datadir@
+datarootdir = @datarootdir@
+libdir = @libdir@
+DATADIRNAME = @DATADIRNAME@
+itlocaledir = $(prefix)/$(DATADIRNAME)/locale
+subdir = po
+install_sh = @install_sh@
+# Automake >= 1.8 provides @mkdir_p  
+# Until it can be supposed, use the safe fallback:
+mkdir_p = $(install_sh) -d
+
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+
+GMSGFMT = @GMSGFMT@
+MSGFMT = @MSGFMT@
+XGETTEXT = @XGETTEXT@
+INTLTOOL_UPDATE = @INTLTOOL_UPDATE@
+INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@
+MSGMERGE = INTLTOOL_EXTRACT=$(INTLTOOL_EXTRACT) srcdir=$(srcdir) $(INTLTOOL_UPDATE) --gettext-package $(GETTEXT_PACKAGE) --dist
+GENPOT   = INTLTOOL_EXTRACT=$(INTLTOOL_EXTRACT) srcdir=$(srcdir) $(INTLTOOL_UPDATE) --gettext-package $(GETTEXT_PACKAGE) --pot
+
+ALL_LINGUAS = @ALL_LINGUAS@
+
+PO_LINGUAS=$(shell if test -r $(srcdir)/LINGUAS; then grep -v "^\#" $(srcdir)/LINGUAS; fi)
+
+USER_LINGUAS=$(shell if test -n "$(LINGUAS)"; then LLINGUAS="$(LINGUAS)"; ALINGUAS="$(ALL_LINGUAS)"; for lang in $$LLINGUAS; do if test -n "`grep ^$$lang$$ $(srcdir)/LINGUAS`" -o -n "`echo $$ALINGUAS|grep ' ?$$lang ?'`"; then printf "$$lang "; fi; done; fi)
+
+USE_LINGUAS=$(shell if test -n "$(USER_LINGUAS)"; then LLINGUAS="$(USER_LINGUAS)"; else if test -n "$(PO_LINGUAS)"; then LLINGUAS="$(PO_LINGUAS)"; else LLINGUAS="$(ALL_LINGUAS)"; fi; fi; for lang in $$LLINGUAS; do printf "$$lang "; done)
+
+POFILES=$(shell LINGUAS="$(USE_LINGUAS)"; for lang in $$LINGUAS; do printf "$$lang.po "; done)
+
+DISTFILES = ChangeLog Makefile.in.in POTFILES.in $(POFILES)
+EXTRA_DISTFILES = POTFILES.skip Makevars LINGUAS
+
+POTFILES = \
+# This comment gets stripped out
+
+CATALOGS=$(shell LINGUAS="$(USE_LINGUAS)"; for lang in $$LINGUAS; do printf "$$lang.gmo "; done)
+
+.SUFFIXES:
+.SUFFIXES: .po .pox .gmo .mo .msg .cat
+
+.po.pox:
+	$(MAKE) $(GETTEXT_PACKAGE).pot
+	$(MSGMERGE) $< $(GETTEXT_PACKAGE).pot -o $*.pox
+
+.po.mo:
+	$(MSGFMT) -o $@ $<
+
+.po.gmo:
+	file=`echo $* | sed 's,.*/,,'`.gmo \
+	  && rm -f $$file && $(GMSGFMT) -o $$file $<
+
+.po.cat:
+	sed -f ../intl/po2msg.sed < $< > $*.msg \
+	  && rm -f $@ && gencat $@ $*.msg
+
+
+all: all- USE_NLS@
+
+all-yes: $(CATALOGS)
+all-no:
+
+$(GETTEXT_PACKAGE).pot: $(POTFILES)
+	$(GENPOT)
+
+install: install-data
+install-data: install-data- USE_NLS@
+install-data-no: all
+install-data-yes: all
+	$(mkdir_p) $(DESTDIR)$(itlocaledir)
+	linguas="$(USE_LINGUAS)"; \
+	for lang in $$linguas; do \
+	  dir=$(DESTDIR)$(itlocaledir)/$$lang/LC_MESSAGES; \
+	  $(mkdir_p) $$dir; \
+	  if test -r $$lang.gmo; then \
+	    $(INSTALL_DATA) $$lang.gmo $$dir/$(GETTEXT_PACKAGE).mo; \
+	    echo "installing $$lang.gmo as $$dir/$(GETTEXT_PACKAGE).mo"; \
+	  else \
+	    $(INSTALL_DATA) $(srcdir)/$$lang.gmo $$dir/$(GETTEXT_PACKAGE).mo; \
+	    echo "installing $(srcdir)/$$lang.gmo as" \
+		 "$$dir/$(GETTEXT_PACKAGE).mo"; \
+	  fi; \
+	  if test -r $$lang.gmo.m; then \
+	    $(INSTALL_DATA) $$lang.gmo.m $$dir/$(GETTEXT_PACKAGE).mo.m; \
+	    echo "installing $$lang.gmo.m as $$dir/$(GETTEXT_PACKAGE).mo.m"; \
+	  else \
+	    if test -r $(srcdir)/$$lang.gmo.m ; then \
+	      $(INSTALL_DATA) $(srcdir)/$$lang.gmo.m \
+		$$dir/$(GETTEXT_PACKAGE).mo.m; \
+	      echo "installing $(srcdir)/$$lang.gmo.m as" \
+		   "$$dir/$(GETTEXT_PACKAGE).mo.m"; \
+	    else \
+	      true; \
+	    fi; \
+	  fi; \
+	done
+
+# Empty stubs to satisfy archaic automake needs
+dvi info tags TAGS ID:
+
+# Define this as empty until I found a useful application.
+installcheck:
+
+uninstall:
+	linguas="$(USE_LINGUAS)"; \
+	for lang in $$linguas; do \
+	  rm -f $(DESTDIR)$(itlocaledir)/$$lang/LC_MESSAGES/$(GETTEXT_PACKAGE).mo; \
+	  rm -f $(DESTDIR)$(itlocaledir)/$$lang/LC_MESSAGES/$(GETTEXT_PACKAGE).mo.m; \
+	done
+
+check: all $(GETTEXT_PACKAGE).pot
+	rm -f missing notexist
+	srcdir=$(srcdir) $(INTLTOOL_UPDATE) -m
+	if [ -r missing -o -r notexist ]; then \
+	  exit 1; \
+	fi
+
+mostlyclean:
+	rm -f *.pox $(GETTEXT_PACKAGE).pot *.old.po cat-id-tbl.tmp
+	rm -f .intltool-merge-cache
+
+clean: mostlyclean
+
+distclean: clean
+	rm -f Makefile Makefile.in POTFILES stamp-it
+	rm -f *.mo *.msg *.cat *.cat.m *.gmo
+
+maintainer-clean: distclean
+	@echo "This command is intended for maintainers to use;"
+	@echo "it deletes files that may require special tools to rebuild."
+	rm -f Makefile.in.in
+
+distdir = ../$(PACKAGE)-$(VERSION)/$(subdir)
+dist distdir: $(DISTFILES)
+	dists="$(DISTFILES)"; \
+	extra_dists="$(EXTRA_DISTFILES)"; \
+	for file in $$extra_dists; do \
+	  test -f $(srcdir)/$$file && dists="$$dists $(srcdir)/$$file"; \
+	done; \
+	for file in $$dists; do \
+	  test -f $$file || file="$(srcdir)/$$file"; \
+	  ln $$file $(distdir) 2> /dev/null \
+	    || cp -p $$file $(distdir); \
+	done
+
+update-po: Makefile
+	$(MAKE) $(GETTEXT_PACKAGE).pot
+	tmpdir=`pwd`; \
+	linguas="$(USE_LINGUAS)"; \
+	for lang in $$linguas; do \
+	  echo "$$lang:"; \
+	  result="`$(MSGMERGE) -o $$tmpdir/$$lang.new.po $$lang`"; \
+	  if $$result; then \
+	    if cmp $(srcdir)/$$lang.po $$tmpdir/$$lang.new.po >/dev/null 2>&1; then \
+	      rm -f $$tmpdir/$$lang.new.po; \
+            else \
+	      if mv -f $$tmpdir/$$lang.new.po $$lang.po; then \
+	        :; \
+	      else \
+	        echo "msgmerge for $$lang.po failed: cannot move $$tmpdir/$$lang.new.po to $$lang.po" 1>&2; \
+	        rm -f $$tmpdir/$$lang.new.po; \
+	        exit 1; \
+	      fi; \
+	    fi; \
+	  else \
+	    echo "msgmerge for $$lang.gmo failed!"; \
+	    rm -f $$tmpdir/$$lang.new.po; \
+	  fi; \
+	done
+
+Makefile POTFILES: stamp-it
+	@if test ! -f $@; then \
+	  rm -f stamp-it; \
+	  $(MAKE) stamp-it; \
+	fi
+
+stamp-it: Makefile.in.in $(top_builddir)/config.status POTFILES.in
+	cd $(top_builddir) \
+	  && CONFIG_FILES=$(subdir)/Makefile.in CONFIG_HEADERS= CONFIG_LINKS= \
+	       $(SHELL) ./config.status
+
+# Tell versions [3.59,3.63) of GNU make not to export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:

Added: trunk/po/POTFILES.in
==============================================================================
--- (empty file)
+++ trunk/po/POTFILES.in	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,3 @@
+# List of source files containing translatable strings.
+# Please keep this file sorted alphabetically.
+src/Application.cs

Added: trunk/src/AbstractTask.cs
==============================================================================
--- (empty file)
+++ trunk/src/AbstractTask.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,180 @@
+// AbstractTask.cs created with MonoDevelop
+// User: boyd at 6:52 AMÂ2/12/2008
+
+using System;
+using System.Collections.Generic;
+
+namespace Tasquer
+{
+	public abstract class AbstractTask : ITask
+	{
+		private uint timerID = 0;
+		
+		#region Properties
+		public abstract string Name
+		{
+			get;
+			set;
+		}
+		
+		public abstract DateTime DueDate
+		{
+			get;
+			set;
+		}
+		
+		public abstract DateTime CompletionDate
+		{
+			get;
+			set;
+		}
+		
+		public abstract bool IsComplete 
+		{
+			get;
+		}
+		
+		public abstract TaskPriority Priority
+		{
+			get;
+			set;
+		}
+		
+		public abstract bool HasNotes
+		{
+			get;
+		}
+		
+		public abstract bool SupportsMultipleNotes
+		{
+			get;
+		}
+		
+		public abstract TaskState State
+		{
+			get;
+		}
+		
+		public abstract ICategory Category
+		{
+			get; 
+			set;
+		}
+		
+		public abstract List<INote> Notes
+		{
+			get;
+		}
+
+		/// <value>
+		/// The ID of the timer used to complete a task after being marked
+		/// inactive.
+		/// </value>
+		public uint TimerID
+		{
+			get { return timerID; }
+			set { timerID = value; }
+		}		
+		#endregion // Properties
+		
+		#region Methods
+		
+		public abstract void Activate ();
+		public abstract void Inactivate ();
+		public abstract void Complete ();
+		public abstract void Delete ();
+		public abstract INote CreateNote(string text);
+		public abstract void DeleteNote(INote note);
+		public abstract void SaveNote(INote note);		
+		
+		public int CompareTo (ITask task)
+		{
+			bool isSameDate = true;
+			if (DueDate.Year != task.DueDate.Year
+					|| DueDate.DayOfYear != task.DueDate.DayOfYear)
+				isSameDate = false;
+			
+			if (isSameDate == false) {
+				if (DueDate == DateTime.MinValue) {
+					// No due date set on this task. Since we already tested to see
+					// if the dates were the same above, we know that the passed-in
+					// task has a due date set and it should be "higher" in a sort.
+					return 1;
+				} else if (task.DueDate == DateTime.MinValue) {
+					// This task has a due date and should be "first" in sort order.
+					return -1;
+				}
+				
+				int result = DueDate.CompareTo (task.DueDate);
+				
+				if (result != 0) {
+					return result;
+				}
+			}
+			
+			// The due dates match, so now sort based on priority and name
+			return CompareByPriorityAndName (task);
+		}
+		
+		public int CompareToByCompletionDate (ITask task)
+		{
+			bool isSameDate = true;
+			if (CompletionDate.Year != task.CompletionDate.Year
+					|| CompletionDate.DayOfYear != task.CompletionDate.DayOfYear)
+				isSameDate = false;
+			
+			if (isSameDate == false) {
+				if (CompletionDate == DateTime.MinValue) {
+					// No completion date set for some reason.  Since we already
+					// tested to see if the dates were the same above, we know
+					// that the passed-in task has a CompletionDate set, so the
+					// passed-in task should be "higher" in the sort.
+					return 1;
+				} else if (task.CompletionDate == DateTime.MinValue) {
+					// "this" task has a completion date and should evaluate
+					// higher than the passed-in task which doesn't have a
+					// completion date.
+					return -1;
+				}
+				
+				return CompletionDate.CompareTo (task.CompletionDate);
+			}
+			
+			// The completion dates are the same, so no sort based on other
+			// things.
+			return CompareByPriorityAndName (task);
+		}
+		#endregion // Methods
+		
+		#region Private Methods
+		
+		private int CompareByPriorityAndName (ITask task)
+		{
+			// The due dates match, so now sort based on priority
+			if (Priority != task.Priority) {
+				switch (Priority) {
+				case TaskPriority.High:
+					return -1;
+				case TaskPriority.Medium:
+					if (task.Priority == TaskPriority.High) {
+						return 1;
+					} else {
+						return -1;
+					}
+				case TaskPriority.Low:
+					if (task.Priority == TaskPriority.None) {
+						return -1;
+					} else {
+						return 1;
+					}
+				case TaskPriority.None:
+					return 1;
+				}
+			}
+			
+			// Due dates and priorities match, now sort by name
+			return Name.CompareTo (task.Name);
+		}
+		#endregion // Private Methods
+	}
+}

Added: trunk/src/AllCategory.cs
==============================================================================
--- (empty file)
+++ trunk/src/AllCategory.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,54 @@
+// AllCategory.cs created with MonoDevelop
+// User: boyd at 3:45 PMÂ2/12/2008
+
+using System;
+using System.Collections.Generic;
+using Mono.Unix;
+
+namespace Tasquer
+{
+	public class AllCategory : ICategory
+	{
+		// A "set" of categories specified by the user to show when the "All"
+		// category is selected in the TaskWindow.  If the list is empty, tasks
+		// from all categories will be shown.  Otherwise, only tasks from the
+		// specified lists will be shown.
+		List<string> categoriesToShow;
+		
+		public AllCategory ()
+		{
+			Preferences preferences = Application.Preferences;
+			categoriesToShow =
+				preferences.GetStringList (Preferences.ShowInAllCategory);
+			Application.Preferences.SettingChanged += OnSettingChanged;
+		}
+		
+		public string Name
+		{
+			get { return Catalog.GetString ("All"); }
+		}
+		
+		public bool ContainsTask(ITask task)
+		{
+			// Filter out tasks based on the user's preferences of which
+			// categories should be displayed in the AllCategory.
+			ICategory category = task.Category;
+			if (category == null)
+				return false;
+			
+			if (categoriesToShow.Count == 0)
+				return true;
+			
+			return categoriesToShow.Contains (category.Name);
+		}
+		
+		private void OnSettingChanged (Preferences preferences, string settingKey)
+		{
+			if (settingKey.CompareTo (Preferences.ShowInAllCategory) != 0)
+				return;
+			
+			categoriesToShow =
+				preferences.GetStringList (Preferences.ShowInAllCategory);
+		}
+	}
+}

Added: trunk/src/Application.cs
==============================================================================
--- (empty file)
+++ trunk/src/Application.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,549 @@
+/***************************************************************************
+ *  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 Gnome;
+using Mono.Unix;
+using Mono.Unix.Native;
+#if ENABLE_NOTIFY_SHARP
+using Notifications;
+#endif
+using Tasquer.Backends;
+
+namespace Tasquer
+{
+	class Application
+	{
+		private static Tasquer.Application application = null;
+		private static System.Object locker = new System.Object();
+
+		private Gnome.Program program;
+		private RemoteControl remoteControl;
+		private Gdk.Pixbuf normalPixBuf;
+		private Gtk.Image trayImage;
+		private Egg.TrayIcon trayIcon;	
+		private Preferences preferences;
+		private EventBox eb;
+		private IBackend backend;
+		private PreferencesDialog preferencesDialog;
+		
+		/// <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 tasquer = Application.Instance;
+				if (tasquer.backend != null) {
+					// Cleanup the old backend
+					try {
+						Logger.Debug ("Cleaning up backend: {0}",
+									  tasquer.backend.Name);
+						tasquer.backend.Cleanup ();
+					} catch (Exception e) {
+						Logger.Warn ("Exception cleaning up '{0}': {1}",
+									 tasquer.backend.Name,
+									 e.Message);
+					}
+				}
+					
+				// Initialize the new backend
+				tasquer.backend = value;
+				if (tasquer.backend == null) {
+					return;
+				}
+					
+				Logger.Info ("Using backend: {0} ({1})",
+							 tasquer.backend.Name,
+							 tasquer.backend.GetType ().ToString ());
+				tasquer.backend.Initialize();
+				
+				TaskWindow.Reinitialize ();
+				
+				Logger.Debug("Configuration status: {0}",
+							 tasquer.backend.Configured.ToString());
+				if (tasquer.backend.Configured == false) {
+					Application.ShowPreferences();
+				}
+			}
+		}
+		
+		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) {
+					if(application == null) {
+						lock(locker) {
+							application = new Application();
+						}
+					}
+					return application;
+				}
+			}
+		}
+
+		public static Preferences Preferences
+		{
+			get { return Application.Instance.preferences; }
+		}
+
+		private Application ()
+		{
+			Init(null);
+		}
+
+		private Application (string[] args)
+		{
+			Init(args);
+		}
+
+		private void Init(string[] args)
+		{
+			program = new Gnome.Program (
+							"Tasquer",
+							Defines.Version,
+							Gnome.Modules.UI,
+							args);
+
+			preferences = new Preferences();
+			
+			// Register Tasquer RemoteControl
+			try {
+				remoteControl = RemoteControlProxy.Register ();
+				if (remoteControl != null) {
+					Logger.Debug ("Tasquer remote control active.");
+				} else {
+					// If Tasquer is already running, open the tasks window
+					// so the user gets some sort of feedback when they
+					// attempt to run Tasquer again.
+					RemoteControl remote = null;
+					try {
+						remote = RemoteControlProxy.GetInstance ();
+						remote.ShowTasks ();
+					} catch {}
+
+					Logger.Debug ("Tasquer is already running.  Exiting...");
+					System.Environment.Exit (-1);
+				}
+			} catch (Exception e) {
+				Logger.Debug ("Tasquer remote control disabled (DBus exception): {0}",
+				            e.Message);
+			}
+			
+			// Read the args and check to see if a specific backend is specified
+			if (args.Length > 0) {
+Logger.Debug ("args [0]: {0}", args [0]);
+				// We're only looking at the first argument
+				string potentialBackendClassName = args [0];
+				
+				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);
+		}
+		
+		/// <summary>
+		/// Load all the available backends that Tasquer can find.  First look in
+		/// Tasquer.exe and then for other DLLs in the same directory Tasquer.ex
+		/// resides.
+		/// </summary>
+		private void LoadAvailableBackends ()
+		{
+			availableBackends = new Dictionary<string,IBackend> ();
+			
+			List<IBackend> backends = new List<IBackend> ();
+			
+			Assembly tasquerAssembly = Assembly.GetCallingAssembly ();
+			
+			// Look for other backends in Tasquer.exe
+			backends.AddRange (GetBackendsFromAssembly (tasquerAssembly));
+			
+			// Look through the assemblies located in the same directory as
+			// Tasquer.exe.
+			Logger.Debug ("Tasquer.exe location:  {0}", tasquerAssembly.Location);
+			
+			DirectoryInfo loadPathInfo =
+				Directory.GetParent (tasquerAssembly.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) == true)
+					continue;
+				
+				Logger.Debug ("Storing '{0}' = '{1}'", typeId, backend.Name);
+				availableBackends [typeId] = backend;
+			}
+		}
+		
+		private List<IBackend> GetBackendsFromAssembly (Assembly asm)
+		{
+			List<IBackend> backends = new List<IBackend> ();
+			
+			foreach (Type type in asm.GetTypes ()) {
+				if (type.IsClass == false)
+					continue; // Skip non-class types
+				if (type.GetInterface ("Tasquer.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 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];
+				}
+			}
+			
+			SetupTrayIcon ();
+			
+			if (backend == null) {
+				// Pop open the preferences dialog so the user can choose a
+				// backend service to use.
+				Application.ShowPreferences ();
+			} else {
+				TaskWindow.ShowWindow();
+			}
+			
+			return false;
+		}
+
+		private void UpdateTrayIcon()
+		{
+		}
+
+		private void SetupTrayIcon ()
+		{
+			eb = new EventBox();
+			normalPixBuf = Utilities.GetIcon ("tasquer-24", 24);
+			trayImage = new Gtk.Image(normalPixBuf);
+			eb.Add(trayImage);
+
+			// hooking event
+			eb.ButtonPressEvent += OnTrayIconClick;
+			trayIcon = new Egg.TrayIcon("Tasquer");
+			trayIcon.Add(eb); 
+
+//			trayIcon.EnterNotifyEvent += OnTrayIconEnterNotifyEvent;
+//			trayIcon.LeaveNotifyEvent += OnTrayIconLeaveNotifyEvent;
+			// showing the trayicon
+			trayIcon.ShowAll();			
+		}
+
+
+		private void OnPreferences (object sender, EventArgs args)
+		{
+			Logger.Info ("OnPreferences called");
+			if (preferencesDialog == null) {
+				preferencesDialog = new PreferencesDialog ();
+				preferencesDialog.Hidden += OnPreferencesDialogHidden;
+			}
+			
+			preferencesDialog.Present ();
+		}
+		
+		private void OnPreferencesDialogHidden (object sender, EventArgs args)
+		{
+			preferencesDialog.Destroy ();
+			preferencesDialog = null;
+		}
+		
+		public static void ShowPreferences()
+		{
+			application.OnPreferences(null, EventArgs.Empty);
+		}
+
+		private void OnAbout (object sender, EventArgs args)
+		{
+			string [] authors = new string [] {
+				"Boyd Timothy <btimothy gmail com>",
+				"Calvin Gaisford <calvinrg gmail com>"
+			};
+
+			/* string [] documenters = new string [] {
+			   "Calvin Gaisford <calvinrg gmail com>"
+			   };
+
+			   string translators = Catalog.GetString ("translator-credits");
+			   if (translators == "translator-credits")
+			   translators = null;
+			 */
+
+
+			Gtk.AboutDialog about = new Gtk.AboutDialog ();
+			about.Name = "Tasquer";
+			about.Version = Defines.Version;
+			about.Logo = Utilities.GetIcon("tasquer-48", 48);
+			about.Copyright =
+				Catalog.GetString ("Copyright \xa9 2008 Novell, Inc.");
+			about.Comments = Catalog.GetString ("A Useful Task List");
+			about.Website = "http://code.google.com/p/tasquer";;
+			about.WebsiteLabel = Catalog.GetString("Tasquer Project Homepage");
+			about.Authors = authors;
+			//about.Documenters = documenters;
+			//about.TranslatorCredits = translators;
+			about.IconName = "tasquer";
+			about.SetSizeRequest(300, 300);
+			about.Run ();
+			about.Destroy ();
+
+		}
+
+
+		private void OnShowTaskWindow (object sender, EventArgs args)
+		{
+			TaskWindow.ShowWindow();
+		}
+		
+		private void OnNewTask (object sender, EventArgs args)
+		{
+			// Show the TaskWindow and then cause a new task to be created
+			TaskWindow.ShowWindow ();
+			TaskWindow.AddTask ();
+		}
+
+		private void OnQuit (object sender, EventArgs args)
+		{
+			Logger.Info ("OnQuitAction called - terminating application");
+			if (backend != null) {
+				backend.Cleanup();
+			}
+			TaskWindow.SavePosition();			
+			program.Quit (); // Should this be called instead?
+		}
+		
+		private void OnRefreshAction (object sender, EventArgs args)
+		{
+			Application.Backend.Refresh();
+		}
+		
+		
+
+		private void OnTrayIconClick (object o, ButtonPressEventArgs args) // handler for mouse click
+		{
+			if (args.Event.Button == 1) {
+				TaskWindow.ShowWindow();
+			} else if (args.Event.Button == 3) {
+				// FIXME: Eventually get all these into UIManagerLayout.xml file
+				Menu popupMenu = new Menu();
+
+				ImageMenuItem showTasksItem = new ImageMenuItem
+					(Catalog.GetString ("Show Tasks ..."));
+
+				showTasksItem.Image = new Gtk.Image(Utilities.GetIcon ("tasquer-24", 24));
+				showTasksItem.Sensitive = backend != null && backend.Initialized;
+				showTasksItem.Activated += OnShowTaskWindow;
+				popupMenu.Add (showTasksItem);
+				
+				ImageMenuItem newTaskItem = new ImageMenuItem
+					(Catalog.GetString ("New Task ..."));
+				newTaskItem.Image = new Gtk.Image (Gtk.Stock.New, IconSize.Menu);
+				newTaskItem.Sensitive = backend != null && backend.Initialized;
+				newTaskItem.Activated += OnNewTask;
+				popupMenu.Add (newTaskItem);
+
+				SeparatorMenuItem separator = new SeparatorMenuItem ();
+				popupMenu.Add (separator);
+
+				ImageMenuItem preferences = new ImageMenuItem (Gtk.Stock.Preferences, null);
+				preferences.Activated += OnPreferences;
+				popupMenu.Add (preferences);
+
+				ImageMenuItem about = new ImageMenuItem (Gtk.Stock.About, null);
+				about.Activated += OnAbout;
+				popupMenu.Add (about);
+
+				separator = new SeparatorMenuItem ();
+				popupMenu.Add (separator);
+
+				ImageMenuItem refreshAction = new ImageMenuItem
+					(Catalog.GetString ("Refresh Tasks"));
+
+				refreshAction.Image = new Gtk.Image(Utilities.GetIcon (Gtk.Stock.Execute, 24));
+				refreshAction.Sensitive = backend != null && backend.Initialized;
+				refreshAction.Activated += OnRefreshAction;
+				popupMenu.Add (refreshAction);
+				
+				separator = new SeparatorMenuItem ();
+				popupMenu.Add (separator);
+				
+				ImageMenuItem quit = new ImageMenuItem ( Gtk.Stock.Quit, null);
+				quit.Activated += OnQuit;
+				popupMenu.Add (quit);
+
+				popupMenu.ShowAll(); // shows everything
+				//popupMenu.Popup(null, null, null, IntPtr.Zero, args.Event.Button, args.Event.Time);
+				popupMenu.Popup(null, null, null, args.Event.Button, args.Event.Time);
+			}
+		}		
+
+
+
+		public static void Main(string[] args)
+		{
+			try 
+			{
+				Utilities.SetProcessName ("Tasquer");
+				application = GetApplicationWithArgs(args);
+				application.StartMainLoop ();
+			} 
+			catch (Exception e)
+			{
+				Tasquer.Logger.Debug("Exception is: {0}", e);
+				Exit (-1);
+			}
+		}
+
+		public static Application GetApplicationWithArgs(string[] args)
+		{
+			lock(locker)
+			{
+				if(application == null)
+				{
+					lock(locker)
+					{
+						application = new Application(args);
+					}
+				}
+				return application;
+			}
+		}
+
+		public static void OnExitSignal (int signal)
+		{
+			if (ExitingEvent != null) ExitingEvent (null, EventArgs.Empty);
+			if (signal >= 0) System.Environment.Exit (0);
+		}
+
+		public static event EventHandler ExitingEvent = null;
+
+		public static void Exit (int exitcode)
+		{
+			OnExitSignal (-1);
+			System.Environment.Exit (exitcode);
+		}
+
+#if ENABLE_NOTIFY_SHARP
+		public static void ShowAppNotification(Notification notification)
+		{
+			notification.AttachToWidget(
+					Tasquer.Application.Instance.trayIcon);
+			notification.Show();
+		}
+#endif
+
+
+		public void StartMainLoop ()
+		{
+			program.Run ();
+		}
+
+		public void QuitMainLoop ()
+		{
+			//	actionManager ["QuitAction"].Activate ();
+		}
+
+	}
+}

Added: trunk/src/Backends/Dummy/DummyBackend.cs
==============================================================================
--- (empty file)
+++ trunk/src/Backends/Dummy/DummyBackend.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,327 @@
+// DummyBackend.cs created with MonoDevelop
+// User: boyd at 7:10 AMÂ2/11/2008
+
+using System;
+using System.Collections.Generic;
+using Mono.Unix;
+using Tasquer.Backends;
+
+namespace Tasquer.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;
+
+		public event BackendInitializedHandler BackendInitialized;
+		public event BackendSyncStartedHandler BackendSyncStarted;
+		public event BackendSyncFinishedHandler BackendSyncFinished;
+		
+		DummyCategory homeCategory;
+		DummyCategory workCategory;
+		DummyCategory projectsCategory;
+		
+		public DummyBackend ()
+		{
+			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);
+		}
+		
+		#region Public Properties
+		public string Name
+		{
+			get { return "Debugging System"; }
+		}
+		
+		/// <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; }
+		}
+		
+		/// <value>
+		/// Indication that the dummy backend is configured
+		/// </value>
+		public bool Configured 
+		{
+			get { return configured; }
+		}
+		
+		/// <value>
+		/// Inidication that the backend is initialized
+		/// </value>
+		public bool Initialized
+		{
+			get { return initialized; }
+		}		
+		#endregion // Public Properties
+		
+		#region Public Methods
+		public ITask CreateTask (string taskName, ICategory category)		
+		{
+			// not sure what to do here with the category
+			DummyTask task = new DummyTask (this, newTaskId, taskName);
+			
+			// Determine and set the task category
+			if (category == null || category is Tasquer.AllCategory)
+				task.Category = workCategory; // Default to work
+			else
+				task.Category = category;
+			
+			Gtk.TreeIter iter = taskStore.AppendNode ();
+			taskStore.SetValue (iter, 0, task);
+			taskIters [newTaskId] = iter;
+			newTaskId++;
+			
+			return task;
+		}
+		
+		public void DeleteTask(ITask task)
+		{}
+		
+		public void Refresh()
+		{}
+		
+		public void Initialize()
+		{
+			Gtk.TreeIter iter;
+			
+			//
+			// Add in the "All" Category
+			//
+			AllCategory allCategory = new Tasquer.AllCategory ();
+			iter = categoryListStore.Append ();
+			categoryListStore.SetValue (iter, 0, allCategory);
+			
+			//
+			// Add in some fake categories
+			//
+			homeCategory = new DummyCategory ("Home");
+			iter = categoryListStore.Append ();
+			categoryListStore.SetValue (iter, 0, homeCategory);
+			
+			workCategory = new DummyCategory ("Work");
+			iter = categoryListStore.Append ();
+			categoryListStore.SetValue (iter, 0, workCategory);
+			
+			projectsCategory = new DummyCategory ("Projects");
+			iter = categoryListStore.Append ();
+			categoryListStore.SetValue (iter, 0, projectsCategory);
+			
+			//
+			// Add in some fake tasks
+			//
+			
+			DummyTask task = new DummyTask (this, newTaskId, "Buy some nails");
+			task.Category = projectsCategory;
+			task.DueDate = DateTime.Now.AddDays (1);
+			task.Priority = TaskPriority.Medium;
+			iter = taskStore.AppendNode ();
+			taskStore.SetValue (iter, 0, task);
+			taskIters [newTaskId] = iter;
+			newTaskId++;
+			
+			task = new DummyTask (this, newTaskId, "Call Roger");
+			task.Category = homeCategory;
+			task.DueDate = DateTime.Now.AddDays (-1);
+			task.Complete ();
+			task.CompletionDate = task.DueDate;
+			iter = taskStore.AppendNode ();
+			taskStore.SetValue (iter, 0, task);
+			taskIters [newTaskId] = iter;
+			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;
+			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;
+			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;
+			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;
+			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;
+			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;
+			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;
+			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;
+			newTaskId++;
+			
+			task = new DummyTask (this, newTaskId, "Test task overdue");
+			task.Category = workCategory;
+			task.DueDate = DateTime.Now.AddDays (-89);
+			task.Priority = TaskPriority.None;
+			task.Complete ();
+			iter = taskStore.AppendNode ();
+			taskStore.SetValue (iter, 0, task);
+			taskIters [newTaskId] = iter;
+			newTaskId++;
+			
+			initialized = true;
+			if(BackendInitialized != null) {
+				BackendInitialized();
+			}		
+		}
+
+		public void Cleanup()
+		{}
+		
+		public Gtk.Widget GetPreferencesWidget ()
+		{
+			// TODO: Replace this with returning null once things are going
+			// so that the Preferences Dialog doesn't waste space.
+			return new Gtk.Label ("Debugging System (this message is a test)");
+		}
+		#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 Tasquer.AllCategory)
+				return -1;
+			else if (categoryB is Tasquer.AllCategory)
+				return 1;
+			
+			return (categoryA.Name.CompareTo (categoryB.Name));
+		}
+		
+		public void UpdateTask (DummyTask task)
+		{
+			// Set the task in the store so the model will update the UI.
+			Gtk.TreeIter iter;
+			
+			if (taskIters.ContainsKey (task.Id) == false)
+				return;
+				
+			iter = taskIters [task.Id];
+			
+			if (task.State == TaskState.Deleted) {
+				taskIters.Remove (task.Id);
+				if (taskStore.Remove (ref iter) == false) {
+					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);
+			}
+		}
+		#endregion // Private Methods
+		
+		#region Event Handlers
+		#endregion // Event Handlers
+	}
+}

Added: trunk/src/Backends/Dummy/DummyCategory.cs
==============================================================================
--- (empty file)
+++ trunk/src/Backends/Dummy/DummyCategory.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,37 @@
+// DummyCategory.cs created with MonoDevelop
+// User: boyd at 9:06 AMÂ2/11/2008
+//
+// To change standard headers go to Edit->Preferences->Coding->Standard Headers
+//
+
+using System;
+using Tasquer;
+
+namespace Tasquer.Backends.Dummy
+{
+	public class DummyCategory : ICategory
+	{
+		private string name;
+		
+		public DummyCategory (string name)
+		{
+			this.name = name;
+		}
+		
+		public string Name
+		{
+			get {
+				return name;
+			}
+		}
+
+		public bool ContainsTask(ITask task)
+		{
+			if(task.Category is DummyCategory)
+				return (task.Category.Name.CompareTo(name) == 0);
+			else
+				return false;
+		}
+		
+	}
+}

Added: trunk/src/Backends/Dummy/DummyNote.cs
==============================================================================
--- (empty file)
+++ trunk/src/Backends/Dummy/DummyNote.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,29 @@
+// DummyNote.cs created with MonoDevelop
+// User: calvin at 10:56 AMÂ2/12/2008
+//
+// To change standard headers go to Edit->Preferences->Coding->Standard Headers
+//
+
+using System;
+using Tasquer;
+
+namespace Tasquer.Backends.Dummy
+{
+	public class DummyNote : INote
+	{
+		public string Name
+		{
+			get { return ""; }
+			set { // TODO: Implement something 
+			}
+		}
+   
+		public string Text
+		{
+			get { return ""; }
+			set { // TODO: Implement something 
+			}
+		}
+
+	}
+}
\ No newline at end of file

Added: trunk/src/Backends/Dummy/DummyTask.cs
==============================================================================
--- (empty file)
+++ trunk/src/Backends/Dummy/DummyTask.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,169 @@
+// DummyTask.cs created with MonoDevelop
+// User: boyd at 8:50 PMÂ2/10/2008
+
+using System;
+using Tasquer;
+using System.Collections.Generic;
+
+namespace Tasquer.Backends.Dummy
+{
+	public class DummyTask : AbstractTask
+	{
+		DummyBackend backend;
+		string name;
+		DateTime dueDate;
+		DateTime completionDate;
+		TaskPriority priority;
+		TaskState state;
+		int id;
+		DummyCategory category;
+		
+		public DummyTask(DummyBackend backend, int id, string taskName)
+		{
+			this.backend = backend;
+			this.id = id;
+			this.name = taskName;
+			this.dueDate = DateTime.MinValue; // No due date specified
+			this.completionDate = DateTime.MinValue; // No due date specified
+			this.priority = TaskPriority.None;
+			this.state = TaskState.Active;
+		}
+		
+		#region Public Properties
+		
+		public int Id
+		{
+			get { return id; }
+			set { id = value; }
+		}
+		
+		public override string Name
+		{
+			get { return name; }
+			set {
+Logger.Debug ("Setting new task name");
+				if (value == null)
+					name = string.Empty;
+				
+				name = value.Trim ();
+				
+				backend.UpdateTask (this);
+			}
+		}
+		
+		public override DateTime DueDate
+		{
+			get { return dueDate; }
+			set {
+Logger.Debug ("Setting new task due date");
+				dueDate = value;
+				
+				backend.UpdateTask (this);
+			}
+		}
+		
+		public override DateTime CompletionDate
+		{
+			get { return completionDate; }
+			set {
+Logger.Debug ("Setting new task completion date");
+				completionDate = value;
+				
+				backend.UpdateTask (this);
+			}
+		}
+		
+		public override bool IsComplete
+		{
+			get { return state == TaskState.Completed; }
+		}
+		
+		public override TaskPriority Priority
+		{
+			get { return priority; }
+			set {
+Logger.Debug ("Setting new task priority");
+				priority = value;
+				
+				backend.UpdateTask (this);
+			}
+		}
+
+		public override bool HasNotes
+		{
+			get { return true; }
+		}
+		
+		public override bool SupportsMultipleNotes
+		{
+			get { return true; }
+		}
+		
+		public override TaskState State
+		{
+			get { return state; }
+		}
+		
+		public override ICategory Category
+		{
+			get { return category; } 
+			set {
+				category = value as DummyCategory;
+			}
+		}
+		
+		public override List<INote> Notes
+		{
+			get { return null; }
+		}		
+		
+		#endregion // Public Properties
+		
+		#region Public Methods
+		public override void Activate ()
+		{
+Logger.Debug ("DummyTask.Activate ()");
+			completionDate = DateTime.MinValue;
+			state = TaskState.Active;
+			backend.UpdateTask (this);
+		}
+		
+		public override void Inactivate ()
+		{
+Logger.Debug ("DummyTask.Inactivate ()");
+			completionDate = DateTime.Now;
+			state = TaskState.Inactive;
+			backend.UpdateTask (this);
+		}
+		
+		public override void Complete ()
+		{
+			Logger.Debug ("DummyTask.Complete ()");
+			CompletionDate = DateTime.Now;
+			state = TaskState.Completed;
+			backend.UpdateTask (this);
+		}
+		
+		public override void Delete ()
+		{
+Logger.Debug ("DummyTask.Delete ()");
+			state = TaskState.Deleted;
+			backend.UpdateTask (this);
+		}
+		
+		public override INote CreateNote(string text)
+		{
+			return null;
+		}
+		
+		public override void DeleteNote(INote note)
+		{
+		}
+
+		public override void SaveNote(INote note)
+		{
+		}
+
+		#endregion // Public Methods
+	}
+}

Added: trunk/src/Backends/EDS/EDSBackends.cs
==============================================================================
--- (empty file)
+++ trunk/src/Backends/EDS/EDSBackends.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,312 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+// EDSBackend.cs
+// User: Johnny
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using Mono.Unix;
+using Evolution;
+using Tasquer.Backends;
+using GLib;
+
+namespace Tasquer.Backends.EDS
+{
+       public class EDSBackend : 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<string, Gtk.TreeIter> taskIters;
+               private Gtk.TreeStore taskStore;
+               private Gtk.TreeModelSort sortedTasksModel;
+               private bool initialized;
+               private object taskLock;
+
+               private Gtk.ListStore categoryListStore;
+               private Gtk.TreeModelSort sortedCategoriesModel;
+
+               public event BackendInitializedHandler BackendInitialized;
+               public event BackendSyncStartedHandler BackendSyncStarted;
+               public event BackendSyncFinishedHandler BackendSyncFinished;
+
+               public EDSBackend ()
+               {
+                       initialized = false;
+
+                       taskIters = new Dictionary<string, Gtk.TreeIter> ();
+                       taskStore = new Gtk.TreeStore (typeof (ITask));
+                       taskLock = new object ();
+
+                       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);
+               }
+
+               #region Public Properties
+
+               public string Name
+               {
+                       get { return "Evolution Data Server"; }
+               }
+
+               /// <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; }
+               }
+
+               /// <value>
+               /// Inidication that the backend is initialized
+               /// </value>
+               public bool Initialized
+               {
+                       get { return initialized; }
+               }
+               #endregion // Public Properties
+
+               #region Public Methods
+               public ITask CreateTask (string taskName, ICategory category)
+               {
+
+                       Console.WriteLine ("CreateTask reached");
+
+                       if (category == null || category is Tasquer.AllCategory)
+                               return null;
+
+                       EDSCategory edsCategory = category as EDSCategory;
+                       CalComponent task = new CalComponent (edsCategory.TaskList);
+                       task.Summary = taskName;
+                       task.Commit ();
+
+                       return null;
+               }
+
+	       public void DeleteTask(ITask task)
+	       {
+		       EDSTask edsTask = task as EDSTask;
+		       edsTask.Remove();
+	       }
+
+               public void Refresh()
+               {}
+
+               public void Initialize()
+               {
+                       Gtk.TreeIter iter;
+
+                       AllCategory allCategory = new Tasquer.AllCategory ();
+                       iter = categoryListStore.Append ();
+                       categoryListStore.SetValue (iter, 0, allCategory);
+
+                       try {
+                               UpdateCategories ();
+                       } catch (Exception e) {
+                               Console.WriteLine ("Oops! : " + e);
+                       }
+
+                       initialized = true;
+                       if(BackendInitialized != null) {
+                               BackendInitialized();
+                       }
+               }
+
+               public Gtk.Widget GetPreferencesWidget ()
+               {
+                       return null;
+               }
+
+               public void Cleanup()
+               {}
+               #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 Tasquer.AllCategory)
+                               return -1;
+                       else if (categoryB is Tasquer.AllCategory)
+                               return 1;
+
+                       return (categoryA.Name.CompareTo (categoryB.Name));
+               }
+
+               public bool Configured
+               {
+                       get { return true; }
+               }
+
+               public void TasksAdded (object o, Evolution.ObjectsAddedArgs args)
+               {
+                       CalComponent[] addedTasks = CalUtil.ICalToCalComponentArray (args.Objects.Handle, ((CalView) o).Client);
+                       lock (taskLock) {
+                               Gtk.TreeIter taskIter;
+                               EDSTask edsTask;
+                               EDSCategory edsCategory;
+                               foreach (CalComponent task in addedTasks) {
+                                       edsCategory = new EDSCategory (task.Source);
+                                       edsTask = new EDSTask (task, edsCategory);
+                                       taskIter = taskStore.AppendNode ();
+                                       taskStore.SetValue (taskIter, 0, edsTask);
+                                       taskIters [task.Uid] = taskIter;
+                               }
+                       }
+               }
+
+               public void TasksModified (object o, Evolution.ObjectsModifiedArgs args)
+               {
+                       Gtk.TreeIter iter;
+                       EDSTask edsTask;
+                       EDSCategory edsCategory;
+
+                       CalComponent[] modifiedTasks = CalUtil.ICalToCalComponentArray (args.Objects.Handle, ((CalView) o).Client);
+
+                       foreach (CalComponent task in modifiedTasks) {
+                               edsCategory = new EDSCategory (task.Source);
+                               edsTask = new EDSTask (task, edsCategory);
+
+                               if(taskIters.ContainsKey(edsTask.Id)) {
+                                       iter = taskIters[edsTask.Id];
+                                       taskStore.SetValue (iter, 0, edsTask);
+                               }
+                       }
+               }
+
+               //FIXME : in evolution-sharp. Add this type.
+               [StructLayout (LayoutKind.Sequential)]
+               private struct CalComponentId {
+                       public string Uid;
+                       public string Rid;
+               }
+
+               public void TasksRemoved (object o, Evolution.ObjectsRemovedArgs args)
+               {
+                       Gtk.TreeIter iter;
+
+                       GLib.List removedTasksList = new GLib.List (args.Uids.Handle,
+                                                                      typeof (CalComponentId));
+
+                       foreach (CalComponentId id in removedTasksList) {
+                               if(taskIters.ContainsKey(id.Uid)) {
+                                       iter = taskIters[id.Uid];
+                                       taskStore.Remove (ref iter);
+                               }
+
+                               Console.WriteLine (id.Uid);
+                       }
+
+                       Logger.Debug ("{0} Tasks removed in EDS", removedTasksList.Count);
+
+               }
+
+               public void UpdateCategories ()
+               {
+                       SourceList slist = new SourceList ("/apps/evolution/tasks/sources");
+                       SList taskGroupList = slist.Groups;
+                       EDSCategory edsCategory;
+
+                       Gtk.TreeIter iter;
+
+                       foreach (SourceGroup taskGroup in taskGroupList) {
+                               Logger.Debug ("\nGroup UID:{0}, Name:{1}", taskGroup.Uid, taskGroup.Name);
+
+                               SList categoriesList = taskGroup.Sources;
+
+                               foreach (Evolution.Source taskListSource in categoriesList) {
+                                       if (taskListSource.IsLocal()) {
+                                               Cal taskList = new Cal (taskListSource, CalSourceType.Todo);
+
+                                               edsCategory = new EDSCategory (taskListSource, taskList);
+                                               iter = categoryListStore.Append ();
+                                               categoryListStore.SetValue (iter, 0, edsCategory);
+
+                                               if (!taskList.Open (true)) {
+                                                       Logger.Debug ("laskList Open failed");
+                                                       continue;
+                                               }
+
+                                               CalView query = taskList.GetCalView ("#t");
+                                               if (query == null) {
+                                                       Logger.Debug ("Query object creation failed");
+                                                       continue;
+                                               } else
+                                                       query.Start ();
+
+                                               query.ObjectsModified += TasksModified;
+                                               query.ObjectsAdded += TasksAdded;
+                                               query.ObjectsRemoved += TasksRemoved;
+                                       }
+                               }
+                       }
+               }
+
+               public void UpdateTask (EDSTask task)
+               {
+                       // Set the task in the store so the model will update the UI.
+                       Gtk.TreeIter iter;
+
+                       if (taskIters.ContainsKey (task.Id) == false)
+                               return;
+
+                       iter = taskIters [task.Id];
+
+                       if (task.State == TaskState.Deleted) {
+                               taskIters.Remove (task.Id);
+                               if (taskStore.Remove (ref iter) == false) {
+                                       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);
+                       }
+               }
+               #endregion // Private Methods
+
+               #region Event Handlers
+               #endregion // Event Handlers
+       }
+}

Added: trunk/src/Backends/EDS/EDSCategory.cs
==============================================================================
--- (empty file)
+++ trunk/src/Backends/EDS/EDSCategory.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,58 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+// EDSCategory.cs
+// User: Johnny <johnnyjacob gmail com>
+//
+// To change standard headers go to Edit->Preferences->Coding->Standard Headers
+//
+
+using System;
+using Tasquer;
+using Evolution;
+
+namespace Tasquer.Backends.EDS
+{
+       public class EDSCategory : ICategory
+       {
+               private string name;
+               private string uid;
+               private Evolution.Cal taskList;
+
+               public EDSCategory (Evolution.Source source, Evolution.Cal taskList)
+               {
+                       this.name = source.Name;
+                       this.uid = source.Uid;
+                       this.taskList = taskList;
+               }
+
+               public EDSCategory (Evolution.Source source)
+               {
+                       this.name = source.Name;
+                       this.uid = source.Uid;
+                       this.taskList = new Cal (source, CalSourceType.Todo);
+               }
+
+               public string Name
+               {
+                       get { return name; }
+               }
+
+               public string UID
+               {
+                       get { return uid; }
+               }
+
+               public Evolution.Cal TaskList
+               {
+                       get { return taskList;}
+               }
+
+               public bool ContainsTask(ITask task)
+               {
+                       if(task.Category is EDSCategory)
+                               return (task.Category.Name.CompareTo(name) == 0);
+                       else
+                               return false;
+               }
+
+       }
+}

Added: trunk/src/Backends/EDS/EDSNote.cs
==============================================================================
--- (empty file)
+++ trunk/src/Backends/EDS/EDSNote.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,31 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+// EDSNote.cs
+// User: Johnny Jacob <johnnyjacob gmail com>
+//
+// To change standard headers go to Edit->Preferences->Coding->Standard Headers
+//
+
+using System;
+using Tasquer;
+
+namespace Tasquer.Backends.EDS
+{
+       public class EDSNote : INote
+       {
+	       private string description;
+
+	       public EDSNote(string description)
+	       {
+		       this.description = description;	
+	       }
+
+               public string Text
+               {
+                       get { return this.description; }
+                       set { 
+			       this.description = value;
+                       }
+               }
+
+       }
+}

Added: trunk/src/Backends/EDS/EDSTask.cs
==============================================================================
--- (empty file)
+++ trunk/src/Backends/EDS/EDSTask.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,254 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+// EDSTask.cs
+// User: Johnny
+
+using System;
+using Tasquer;
+using System.Collections.Generic;
+using Evolution;
+
+namespace Tasquer.Backends.EDS
+{
+       public class EDSTask : AbstractTask
+       {
+               private CalComponent taskComp;
+               private string name;
+               private DateTime dueDate;
+               private DateTime completionDate;
+               private TaskPriority priority;
+               private TaskState state;
+               private string id;
+               private EDSCategory category;
+
+	       private List<INote> notes;		
+
+               public EDSTask(CalComponent task, EDSCategory category)
+               {
+
+                       this.name = task.Summary;
+                       Logger.Debug ("Creating New Task Object : {0}",this.name);
+                       this.id = task.Uid;
+                       this.category = category;
+                       this.completionDate = task.Dtend;
+                       this.taskComp = task;
+
+		       notes = new List<INote>();
+
+		       foreach(string description in task.Descriptions) {
+			       EDSNote edsNote = new EDSNote (description);
+			       Logger.Debug ("Note :" + description);
+			       notes.Add(edsNote);
+		       }
+
+               }
+
+               #region Public Properties
+
+	       public void Remove () {
+		       Logger.Debug ("Removing task : {0} - {1}", this.Name, this.Id);
+		       this.taskComp.ECal.RemoveObject (this.Id);
+               }
+
+               private void Commit () {
+                       this.taskComp.Commit ();
+               }
+
+               public string Id
+               {
+                       get { return id; }
+                       set { id = value; }
+               }
+
+               public override string Name
+               {
+                       get {
+                               // BUG : issue with e# ?? :(
+                               // Should be using taskComp.Summary here.
+                               return this.name;
+                       }
+                       set {
+                               Logger.Debug ("Setting new task name");
+                               if (value == null)
+                                       this.taskComp.Summary = string.Empty;
+
+                               // BUG : issue with e# ?? :(
+                               this.name = value.Trim ();
+                               this.taskComp.Summary = value.Trim ();
+
+                               this.Commit ();
+                       }
+               }
+
+               public override DateTime DueDate
+               {
+                       get { return taskComp.Due; }
+                       set {
+                               Logger.Debug ("Setting new task due date");
+                               taskComp.Due = value;
+                               this.Commit ();
+                       }
+               }
+
+               public override DateTime CompletionDate
+               {
+                       get { return taskComp.Completed; }
+                       set {
+                               Logger.Debug ("Setting new task completion date");
+                               taskComp.Completed = value;
+
+                               this.Commit ();
+                       }
+               }
+
+               public override bool IsComplete
+               {
+                       get {
+                               if (completionDate == DateTime.MinValue)
+                                       return false;
+
+                               return true;
+                       }
+               }
+
+               public override TaskPriority Priority
+               {
+                       get {
+                               switch (taskComp.Priority) {
+                                       default:
+                                       case CalPriority.Undefined:
+                                               return TaskPriority.None;
+                                       case CalPriority.High:
+                                               return TaskPriority.High;
+                                       case CalPriority.Normal:
+                                               return TaskPriority.Medium;
+                                       case CalPriority.Low:
+                                               return TaskPriority.Low;
+                               }
+                       }
+                       set {
+                               Console.WriteLine ("Setting Priority : {0}", value);
+                               switch (value) {
+                                       default:
+                                       case TaskPriority.None:
+                                               taskComp.Priority = CalPriority.Undefined;
+                                               break;
+                                       case TaskPriority.High:
+                                               taskComp.Priority = CalPriority.High;
+                                               break;
+                                       case TaskPriority.Medium:
+                                               taskComp.Priority = CalPriority.Normal;
+                                               break;
+                                       case TaskPriority.Low:
+                                               taskComp.Priority = CalPriority.Low;
+                                               break;
+                               }
+                               Console.WriteLine ("taskComp : priority : {0}", taskComp.Priority);
+                               this.Commit ();
+                       }
+               }
+
+               public override bool HasNotes
+               {
+		       get { return (notes.Count > 0); }
+               }
+
+               public override bool SupportsMultipleNotes
+               {
+                       get { return true; }
+               }
+
+               public override TaskState State
+               {
+                       get { return state; }
+               }
+
+               public override ICategory Category
+               {
+                       get { return category; }
+                       set {
+                               category = value as EDSCategory;
+                       }
+               }
+
+               public override List<INote> Notes
+               {
+                       get { return notes; }
+               }
+
+               #endregion // Public Properties
+
+	       private void UpdateNotes ()
+	       {
+		       string[] descriptions = new string [this.notes.Count];
+		       // Too much 'c' influence here ? 
+		       int i = 0;
+		       foreach (EDSNote note in this.notes ) {
+			       descriptions [i] = string.Copy (note.Text);
+			       i++;
+		       }
+
+		       this.taskComp.Descriptions = descriptions;
+		       this.Commit ();
+	       }
+
+               #region Public Methods
+               public override void Activate ()
+               {
+                       Logger.Debug ("EDSTask.Activate ()");
+                       state = TaskState.Active;
+                       this.taskComp.Status = CalStatus.InProcess;
+                       CompletionDate = DateTime.MinValue;
+               }
+
+               public override void Inactivate ()
+               {
+                       Logger.Debug ("EDSTask.Inactivate ()");
+                       state = TaskState.Inactive;
+                       this.taskComp.Status = CalStatus.None;
+                       CompletionDate = DateTime.Now;
+               }
+
+               public override void Complete ()
+               {
+                       Logger.Debug ("EDSTask.Complete ()");
+                       this.taskComp.Status = CalStatus.Completed;
+                       CompletionDate = DateTime.Now;
+                       state = TaskState.Completed;
+               }
+
+               public override void Delete ()
+               {
+                       Logger.Debug ("EDSTask.Delete ()");
+                       state = TaskState.Deleted;
+               }
+
+               public override INote CreateNote(string text)
+               {
+		       EDSNote edsNote;
+			
+		       edsNote = new EDSNote (text);
+		       notes.Add(edsNote);
+		       this.UpdateNotes ();
+
+		       return edsNote;
+               }
+
+               public override void DeleteNote(INote note)
+               {
+		       foreach(EDSNote edsNote in notes) {
+			       if(string.Equals (edsNote.Text, note.Text)) {
+				       notes.Remove(edsNote);
+				       break;
+			       }
+		       }
+		       this.UpdateNotes ();
+               }
+
+               public override void SaveNote(INote note)
+               {
+		       this.UpdateNotes ();
+               }
+
+               #endregion // Public Methods
+       }
+}

Added: trunk/src/Backends/IceCore/IceBackend.cs
==============================================================================
--- (empty file)
+++ trunk/src/Backends/IceCore/IceBackend.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,441 @@
+// IceBackend.cs created with MonoDevelop
+// User: boyd at 8:32 AMÂ2/14/2008
+
+using System;
+using System.Collections.Generic;
+using NDesk.DBus;
+using org.freedesktop.DBus;
+using Novell.IceDesktop;
+using Tasquer;
+using Tasquer.Backends;
+
+namespace Tasquer.Backends.IceCore
+{
+	public class IceBackend : IBackend
+	{
+		static ObjectPath DaemonPath =
+			new ObjectPath ("/Novell/ICEDesktop/Daemon");
+		static string DaemonNamespace = "Novell.ICEDesktop.Daemon";
+		
+		private org.freedesktop.DBus.IBus sessionBus;
+		Novell.IceDesktop.IDaemon deskIceDaemon = null;
+		
+		private Gtk.ListStore tasks;
+		private Gtk.TreeModelSort sortedTasks;
+		private Dictionary<string, Gtk.TreeIter> taskIters;
+		
+		private Gtk.ListStore categories;
+		private Gtk.TreeModelSort sortedCategories;
+		private Dictionary<string, Gtk.TreeIter> categoryIters;
+		
+		private bool initialized;
+		
+		public IceBackend ()
+		{
+			initialized = false;
+			
+			//
+			// Set up the Tasks ListStore
+			//
+			tasks = new Gtk.ListStore (typeof (ITask));
+			sortedTasks = new Gtk.TreeModelSort (tasks);
+			sortedTasks.SetSortFunc (0,
+				new Gtk.TreeIterCompareFunc (CompareTasksSortFunc));
+			sortedTasks.SetSortColumnId (0, Gtk.SortType.Ascending);
+			taskIters = new Dictionary<string, Gtk.TreeIter> ();
+			
+			categories = new Gtk.ListStore (typeof (ICategory));
+			sortedCategories = new Gtk.TreeModelSort (categories);
+			sortedCategories.SetSortFunc (0,
+				new Gtk.TreeIterCompareFunc (CompareCategoriesSortFunc));
+			sortedCategories.SetSortColumnId (0, Gtk.SortType.Ascending);
+			categoryIters = new Dictionary<string, Gtk.TreeIter> ();
+			
+			DaemonService.Initialize ();
+		}
+		
+		#region Events
+		public event BackendInitializedHandler BackendInitialized;
+		public event BackendSyncStartedHandler BackendSyncStarted;
+		public event BackendSyncFinishedHandler BackendSyncFinished;
+		#endregion // Events
+
+		#region Properties
+		public string Name
+		{
+			get { return "Novell Teaming and Conferencing"; }
+		}
+		
+		/// <value>
+		/// All the tasks provided by the backend.
+		/// </value>
+		public Gtk.TreeModel Tasks
+		{
+			get { return sortedTasks; }
+		}
+		
+		/// <value>
+		/// This returns all the ICategory items from the backend.
+		/// </value>
+		public Gtk.TreeModel Categories
+		{
+			get { return sortedCategories; }
+		}
+		
+		/// <value>
+		/// Indication that the backend has enough information
+		/// (credentials/etc.) to run.  If false, the properties dialog will
+		/// be shown so the user can configure the backend.
+		/// </value>
+		public bool Configured
+		{
+			// TODO: Implement IceBackend.Configured:bool
+			get { return true; }
+		}
+		
+		/// <value>
+		/// Inidication that the backend is initialized
+		/// </value>
+		public bool Initialized
+		{
+			get { return initialized; }
+		}
+		#endregion // Properties
+		
+		#region Methods
+		/// <summary>
+		/// Create a new task.
+		/// </summary>
+		public ITask CreateTask (string taskName, ICategory category)
+		{
+			IceTask task = null;
+			IceCategory iceCategory = category as IceCategory;
+			
+			if (taskName == null || taskName.Trim () == string.Empty
+					|| category == null) {
+				Logger.Warn ("Cannot call IceBackend.CreateTask () with null/empty arguments.");
+				return null;
+			}
+			
+			string taskId = null;
+			try {
+				taskId =
+				deskIceDaemon.CreateTask (iceCategory.Folder.ID,
+										  taskName,
+										  string.Empty,	// description
+										  5,			// Lowest Priority/None
+										  TaskStatus.NeedsAction,
+										  string.Empty,	// start date
+										  string.Empty);	// due date
+			} catch (Exception e) {
+				Logger.Error ("Exception calling deskIceDaemon.CreateTask (): {0}", e.Message);
+				return null;
+			}
+			
+			TaskEntry taskEntry = new TaskEntry (taskId, taskName);
+			task = new IceTask (this, iceCategory, taskEntry);
+			
+			UpdateTask (task);
+			
+			return task;
+		}
+		
+		/// <summary>
+		/// Deletes the specified task.
+		/// </summary>
+		/// <param name="task">
+		/// A <see cref="ITask"/>
+		/// </param>
+		public void DeleteTask (ITask task)
+		{
+			Logger.Debug ("TODO: Implement IceBackend.DeleteTask()");
+		}
+
+
+		/// <summary>
+		/// Refreshes the backend.
+		/// </summary>
+		public void Refresh()
+		{
+			// TODO: Eventually don't clear out existing entries, but match up
+			// the updates
+//			categories.Clear ();
+			
+			// TODO: Eventually don't clear out existing tasks, but match them
+			// up with the updates
+//			tasks.Clear ();
+			
+			Teamspace [] teams = null;
+			try {
+				teams = deskIceDaemon.GetTeamList ();
+			} catch {
+				Logger.Warn ("Exception thrown getting team list");
+				return;
+			}
+			
+			foreach (Teamspace team in teams) {
+				Logger.Debug ("Team Found: {0} ({1})", team.Name, team.ID);
+				
+				// Check to see if the team has tasks enabled
+				TaskFolder [] taskFolders = null;
+				try {
+					taskFolders = deskIceDaemon.GetTaskFolders (team.ID);
+				} catch {
+					Logger.Warn ("Exception thrown getting task folders");
+					return;
+				}
+				
+				foreach (TaskFolder taskFolder in taskFolders) {
+					// Create an IceCategory for each folder
+					IceCategory category = new IceCategory (team, taskFolder);
+					Gtk.TreeIter iter;
+					if (categoryIters.ContainsKey (category.Id))
+						iter = categoryIters [category.Id];
+					else {
+						iter = categories.Append ();
+						categoryIters [category.Id] = iter;
+					}
+					
+					categories.SetValue (iter, 0, category);
+					
+					LoadTasksFromCategory (category);
+				}
+			}
+			
+			// TODO: Wrap this in a try/catch
+			if (BackendSyncFinished != null) {
+				try {
+					BackendSyncFinished ();
+				} catch (Exception e) {
+					Logger.Warn ("Error calling BackendSyncFinished handler: {0}", e.Message);
+				}
+			}
+		}
+
+		/// <summary>
+		/// Initializes the backend
+		/// </summary>
+		public void Initialize()
+		{
+			BusG.Init ();
+			
+			// Watch the session bus for when ICEcore Daemon comes or goes.
+			// When it comes, attempt to connect to it.
+			sessionBus =
+				Bus.Session.GetObject<org.freedesktop.DBus.IBus> (
+					"org.freedesktop.DBus",
+					new ObjectPath ("/org/freedesktop/DBus"));
+			sessionBus.NameOwnerChanged += OnDBusNameOwnerChanged;
+			
+			// Force the daemon to start up if it's not already running
+			if (Bus.Session.NameHasOwner (DaemonNamespace) == false) {
+				Bus.Session.StartServiceByName (DaemonNamespace);
+			}
+			
+			// Register for ICEcore Daemon's events
+			ConnectToICEcoreDaemon ();
+			
+			//
+			// Add in the AllCategory
+			//
+			AllCategory allCategory = new AllCategory ();
+			Gtk.TreeIter iter = categories.Append ();
+			categories.SetValue (iter, 0, allCategory);				
+			
+			// Populate the models
+			Refresh ();
+			
+			initialized = true;
+			
+			if (BackendInitialized != null) {
+				try {
+					BackendInitialized ();
+				} catch (Exception e) {
+					Logger.Debug ("Exception in IceBackend.BackendInitialized handler: {0}", e.Message);
+				}
+			}
+		}
+
+		/// <summary>
+		/// Cleanup the backend before quitting
+		/// </summary>
+		public void Cleanup()
+		{
+			Logger.Debug ("IceBackend.Cleanup ()");
+			
+			// TODO: Figure out whether we need to do anything in IceBackend.Cleanup ()");
+		}
+		
+		public Gtk.Widget GetPreferencesWidget ()
+		{
+			return null;
+		}
+		
+		public void UpdateTask (IceTask task)
+		{
+			// Set the task in the store so the model will update the UI.
+			Gtk.TreeIter iter;
+			
+			if (taskIters.ContainsKey (task.Id) == false) {
+				// This must be a new task that should be added in.
+				iter = tasks.Append ();
+				taskIters [task.Id] = iter;
+			} else {
+				iter = taskIters [task.Id];
+			}
+			
+			if (task.State == TaskState.Deleted) {
+				taskIters.Remove (task.Id);
+				if (tasks.Remove (ref iter) == false) {
+					Logger.Debug ("Successfully deleted from taskStore: {0}",
+						task.Name);
+				} else {
+					Logger.Debug ("Problem removing from taskStore: {0}",
+						task.Name);
+				}
+			} else {
+				tasks.SetValue (iter, 0, task);
+			}
+		}
+		
+		public void SaveAndUpdateTask (IceTask task)
+		{
+			// Send new values to the server and then update the task in the
+			// TreeModel.
+			
+			try {
+				IceCategory iceCategory = task.Category as IceCategory;
+				deskIceDaemon.UpdateTask (iceCategory.Folder.ID,
+										  task.Entry.ID,
+										  task.Name,
+										  task.Entry.Description,
+										  task.IceDesktopStatus,
+										  int.Parse (task.Entry.Priority),
+										  int.Parse (task.Entry.PercentComplete),
+										  task.DueDateString);
+			} catch (Exception e) {
+				Logger.Warn ("Error calling deskIceDaemon.UpdateTask: {0}",
+							 e.Message);
+				return;
+			}
+			
+			UpdateTask (task);
+		}
+		#endregion // Public Methods
+		
+		#region Private Methods
+		/// <summary>
+		/// Connect with the ICEcore Daemon and register event handlers.
+		/// </summary>
+		void ConnectToICEcoreDaemon ()
+		{
+			Console.WriteLine ("Connecting the to the ICEcore Daemon");
+			deskIceDaemon = DaemonService.DaemonInstance;
+			if (deskIceDaemon != null) {
+				// Set up the daemon event handlers
+				deskIceDaemon.Authenticated += OnAuthenticated;
+				deskIceDaemon.Disconnected += OnDisconnected;
+			}
+			
+			// TODO: Do we need to call a refresh here?
+		}
+		
+		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 CompareCategoriesSortFunc (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 Tasquer.AllCategory)
+				return -1;
+			else if (categoryB is Tasquer.AllCategory)
+				return 1;
+			
+			return (categoryA.Name.CompareTo (categoryB.Name));
+		}
+		
+		private void LoadTasksFromCategory (IceCategory category)
+		{
+			TaskEntry [] taskEntries = null;
+			try {
+				taskEntries = deskIceDaemon.GetTaskEntries (category.Folder.ID);
+			} catch (Exception e) {
+				Logger.Warn ("Exception loading tasks from category: {0}", e.Message);
+				return;
+			}
+			
+			foreach (TaskEntry entry in taskEntries) {
+				IceTask task = new IceTask (this, category, entry);
+				Gtk.TreeIter iter;
+				if (taskIters.ContainsKey (task.Id))
+					iter = taskIters [task.Id];
+				else {
+					iter = tasks.Append ();
+					taskIters [task.Id] = iter;
+				}
+				
+				tasks.SetValue (iter, 0, task);
+			}
+		}
+		#endregion // Private Methods
+		
+		#region Event Handlers
+		void OnDBusNameOwnerChanged (string serviceName,
+											string oldOwner,
+											string newOwner)
+		{
+			if (serviceName == null)
+				return;
+			
+			if (serviceName.CompareTo (DaemonNamespace) != 0)
+				return;
+			
+			if (oldOwner != null && oldOwner.Length > 0) {
+				// The daemon just went away
+				Console.WriteLine ("The ICEcore Daemon just quit.");
+				
+				// TODO: Determine whether we should force the daemon to start up again
+			} else {
+				// This is a new daemon
+				ConnectToICEcoreDaemon ();
+				
+				// Populate the models
+				Refresh ();
+			}
+		}
+		
+		void OnAuthenticated (string server, string username)
+		{
+			Logger.Debug ("Received authenticated message from ICEcore Daemon");
+			
+			// Do a refresh
+			Refresh ();
+		}
+		
+		void OnDisconnected (string server, string username)
+		{
+			// TODO: Figure out what to do when the ICEcore Daemon disconnects
+			Logger.Debug ("Received disconnect message from ICEcore Daemon");
+			
+			Refresh (); // ... this will clear out the lists (yuck!)
+		}
+		#endregion // Event Handlers
+	}
+}

Added: trunk/src/Backends/IceCore/IceCategory.cs
==============================================================================
--- (empty file)
+++ trunk/src/Backends/IceCore/IceCategory.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,62 @@
+// IceCategory.cs created with MonoDevelop
+// User: boyd at 8:32 AMÂ2/14/2008
+
+using System;
+using Novell.IceDesktop;
+
+namespace Tasquer.Backends.IceCore
+{
+	public class IceCategory : ICategory
+	{
+		private Teamspace team;
+		private TaskFolder folder;
+		private string id;
+		
+		public IceCategory(Teamspace iceTeam, TaskFolder iceFolder)
+		{
+			team = iceTeam;
+			folder = iceFolder;
+			
+			// Construct a unique ID in the format:
+			// <team-id>-<task-folder-id>
+			id = string.Format ("{0}-{1}",
+								team.ID,
+								folder.ID);
+		}
+		
+		public string Name
+		{
+			get { return team.Name; }
+		}
+		
+		public bool ContainsTask(ITask task)
+		{
+			if (task == null)
+				return false;
+			
+			IceCategory iceCategory = task.Category as IceCategory;
+			if (iceCategory == null)
+				return false;
+			
+			if (Id.CompareTo (iceCategory.Id) == 0)
+				return true;
+			
+			return false;
+		}
+		
+		public string Id
+		{
+			get { return id; }
+		}
+		
+		public Teamspace Team
+		{
+			get { return team; }
+		}
+		
+		public TaskFolder Folder
+		{
+			get { return folder; }
+		}
+	}
+}

Added: trunk/src/Backends/IceCore/IceNote.cs
==============================================================================
--- (empty file)
+++ trunk/src/Backends/IceCore/IceNote.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,32 @@
+// IceNote.cs created with MonoDevelop
+// User: boyd at 8:32 AMÂ2/14/2008
+
+using System;
+
+namespace Tasquer.Backends.IceCore
+{
+	public class IceNote : INote
+	{
+		private IceBackend backend;
+		private IceTask task;
+		
+		public IceNote (IceBackend iceBackend,
+						IceTask iceTask)
+		{
+			backend = iceBackend;
+			task = iceTask;
+		}
+		
+		public string Name
+		{
+			get { return task.Entry.Title; }
+			set {}
+		}
+    
+		public string Text
+		{
+			get { return task.Entry.Description; }
+			set {}
+		}
+	}
+}

Added: trunk/src/Backends/IceCore/IceTask.cs
==============================================================================
--- (empty file)
+++ trunk/src/Backends/IceCore/IceTask.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,316 @@
+// IceTask.cs created with MonoDevelop
+// User: boyd at 8:33 AMÂ2/14/2008
+
+using System;
+using System.Collections.Generic;
+using Novell.IceDesktop;
+
+namespace Tasquer.Backends.IceCore
+{
+	public class IceTask : AbstractTask
+	{
+		private IceBackend backend;
+		private IceCategory category;
+		private TaskEntry entry;
+		private TaskState state;
+		private string id;
+		
+		private static string UtcDateFormat = "yyyy-MM-ddTHH:mm:ssZ";
+		
+		public IceTask (IceBackend iceBackend,
+						IceCategory iceCategory,
+						TaskEntry taskEntry)
+		{
+			this.backend = iceBackend;
+			this.category = iceCategory;
+			this.entry = taskEntry;
+			if (entry.Status == TaskStatus.Completed)
+				this.state = TaskState.Completed;
+			else if (entry.Status == TaskStatus.Cancelled)
+				this.state = TaskState.Inactive; // TODO: Is this the right thing to do?
+			else
+				this.state = TaskState.Active;
+			
+			// Construct a unique id in the format of
+			// <team-id>-<team-folder-id>-<task-entry-id>
+			id = string.Format ("{0}-{1}-{2}",
+								iceCategory.Team.ID,
+								iceCategory.Folder.ID,
+								taskEntry.ID);
+		}
+
+		#region Properties
+		public TaskEntry Entry
+		{
+			get { return entry; }
+		}
+		
+		/// <value>
+		/// A unique ID in the format of
+		/// <team-id>-<team-folder-id>-<task-entry-id>
+		/// </value>
+		public string Id
+		{
+			get { return id; }
+		}
+		
+		/// <value>
+		/// A Task's Name will be used to show the task in the main list window.
+		/// </value>
+		public override string Name
+		{
+			get { return entry.Title; }
+			set {
+				if (value == null)
+					entry.Title = string.Empty;
+				else
+					entry.Title = value.Trim ();
+				
+				backend.SaveAndUpdateTask (this);
+			}
+		}
+		
+		/// <value>
+		/// A DueDate of DateTime.MinValue indicates that a due date is not set.
+		/// </value>
+		public override DateTime DueDate
+		{
+			get {
+				string dateString = entry.DueDate;
+				if (dateString == null || dateString == string.Empty)
+					return DateTime.MinValue;
+				else
+					return DateTime.Parse (dateString);
+			}
+			set {
+				// Set the date using UTC format
+				entry.DueDate = value.ToUniversalTime ().ToString (UtcDateFormat);
+				backend.SaveAndUpdateTask (this);
+			}
+		}
+		
+		public string DueDateString
+		{
+			get { return DueDate.ToUniversalTime ().ToString (UtcDateFormat); }
+		}
+		
+		/// <value>
+		/// If set to CompletionDate.MinValue, the task has not been completed.
+		/// </value>
+		public override DateTime CompletionDate
+		{
+			get {
+				string dateString = entry.CompletionDate;
+				if (dateString == null || dateString == string.Empty)
+					return DateTime.MinValue;
+				else
+					return DateTime.Parse (dateString);
+			}
+			set {
+				// Set the date using UTC format
+				entry.CompletionDate = value.ToUniversalTime ().ToString (UtcDateFormat);
+				backend.SaveAndUpdateTask (this);
+			}
+		}
+		
+		/// <value>
+		/// This is a convenience property which should use the CompletionDate
+		/// to determine whether a task is completed.
+		/// </value>
+		public override bool IsComplete 
+		{
+			get { return state == TaskState.Completed; }
+		}
+		
+		/// <value>
+		/// Backends should, by default, set the priority of a task to
+		/// TaskPriority.None.
+		///
+		/// ICEcore uses 1 = critical, 2 = high, 3 = medium, 4 = low, 5 = not
+		/// important.  We'll map 1 -> 2, 2 = TaskPriority.High,
+		/// 3 = TaskPriority.Medium, 4 = TaskPriority.Low, and
+		/// 5 = TaskPriority.None.
+		/// </value>
+		public override TaskPriority Priority
+		{
+			get {
+				string priorityString = entry.Priority;
+				if (priorityString == null || priorityString == string.Empty)
+					return TaskPriority.None;
+				
+				switch (priorityString) {
+				case "1":
+				case "2":
+					return TaskPriority.High;
+				case "3":
+					return TaskPriority.Medium;
+				case "4":
+					return TaskPriority.Low;
+				}
+				
+				return TaskPriority.None;
+			}
+			set {
+				switch (value) {
+				case TaskPriority.High:
+					entry.Priority = "2";
+					break;
+				case TaskPriority.Medium:
+					entry.Priority = "3";
+					break;
+				case TaskPriority.Low:
+					entry.Priority = "4";
+					break;
+				default:
+					entry.Priority = "5";
+					break;
+				}
+				
+				backend.SaveAndUpdateTask (this);
+			}
+		}
+		
+		/// <value>
+		/// Indicates whether any notes exist in this task.  If a backend does
+		/// not support notes, it should always return false.
+		///
+		/// ICEcore task descriptions will be exposed in Tasquer as a Task Note.
+		/// </value>
+		public override bool HasNotes
+		{
+			get {
+				string description = entry.Description;
+				if (description == null || description.Trim () == string.Empty)
+					return false;
+				
+				return true;
+			}
+		}
+		
+		/// <value>
+		/// Should be true if the task supports having multiple notes.
+		/// </value>
+		public override bool SupportsMultipleNotes
+		{
+			get { return false; }
+		}
+		
+		/// <value>
+		/// The state of the task.  Note: sreeves' LOVES source code comments
+		/// like these.
+		/// </value>
+		public override TaskState State
+		{
+			get { return state; }
+		}
+		
+		public Novell.IceDesktop.TaskStatus IceDesktopStatus
+		{
+			get {
+				switch (state) {
+				case TaskState.Deleted:
+					return TaskStatus.Cancelled;
+				case TaskState.Completed:
+					return TaskStatus.Completed;
+				}
+				
+				return TaskStatus.InProcess;
+			}
+		}
+		
+		/// <value>
+		/// The category to which this task belongs
+		/// </value>
+		public override ICategory Category
+		{
+			get { return category; } 
+			set {}
+		}
+		
+		/// <value>
+		/// The notes associated with this task
+		/// </value>
+		public override List<INote> Notes
+		{
+			get {
+				List<INote> notes = new List<INote> ();
+				notes.Add (new IceNote (backend, this));
+				return notes;
+			}
+		}
+		
+		#endregion // Properties
+		
+		#region Methods
+		
+		/// <summary>
+		/// Activate (Reopen) a task that's Inactivated or Completed.
+		/// </summary>
+		public override void Activate ()
+		{
+			if (entry.Status != TaskStatus.Cancelled
+					&& entry.Status != TaskStatus.Completed)
+				return;
+			
+			entry.Status = TaskStatus.NeedsAction;
+			CompletionDate = DateTime.MinValue;
+			state = TaskState.Active;
+			
+			backend.SaveAndUpdateTask (this);
+		}
+		
+		/// <summary>
+		/// Inactivate a task (this is the "limbo" mode).
+		/// </summary>
+		public override void Inactivate ()
+		{
+			state = TaskState.Inactive;
+			backend.UpdateTask (this);
+		}
+		
+		/// <summary>
+		/// Mark a task as completed.
+		/// </summary>
+		public override void Complete ()
+		{
+			entry.Status = TaskStatus.Completed;
+			CompletionDate = DateTime.Now;
+			state = TaskState.Completed;
+			backend.SaveAndUpdateTask (this);
+		}
+		
+		/// <summary>
+		/// Delete a task from the backend.
+		/// </summary>
+		public override void Delete ()
+		{
+			// TODO: Implement IceTask.Delete ()
+		}
+		
+		/// <summary>
+		/// Creates a new note on this task
+		/// </summary>
+		public override INote CreateNote(string text)
+		{
+			// TODO: Implement IceTask.CreateNote()
+			return null;
+		}
+		
+		/// <summary>
+		/// Removes a note from this task
+		/// </summary>
+		public override void DeleteNote(INote note)
+		{
+			// TODO: Implement IceTask.DeleteNote ()
+		}
+		
+		/// <summary>
+		/// Updates an exising note on the task
+		/// </summary>
+		public override void SaveNote(INote note)
+		{
+			// TODO: Implement IceTask.SaveNote ()
+		}
+		#endregion // Methods
+	}
+}

Added: trunk/src/Backends/Rtm/RtmBackend.cs
==============================================================================
--- (empty file)
+++ trunk/src/Backends/Rtm/RtmBackend.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,661 @@
+// 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
+//
+
+using System;
+using Mono.Unix;
+using Tasquer.Backends;
+using RtmNet;
+using System.Threading;
+using System.Collections.Generic;
+
+namespace Tasquer.Backends.RtmBackend
+{
+	public class RtmBackend : IBackend
+	{
+		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);
+
+			categoryListStore = new Gtk.ListStore (typeof (ICategory));
+
+			sortedCategoriesModel = new Gtk.TreeModelSort (categoryListStore);
+			sortedCategoriesModel.SetSortFunc (0, new Gtk.TreeIterCompareFunc (CompareCategorySortFunc));
+			sortedCategoriesModel.SetSortColumnId (0, Gtk.SortType.Ascending);
+
+			// make sure we have the all Category in our list
+			Gtk.Application.Invoke ( delegate {
+				AllCategory allCategory = new Tasquer.AllCategory ();
+				Gtk.TreeIter iter = categoryListStore.Append ();
+				categoryListStore.SetValue (iter, 0, allCategory);				
+			});
+
+			runRefreshEvent = new AutoResetEvent(false);
+			
+			runningRefreshThread = false;
+			refreshThread  = new Thread(RefreshThreadLoop);
+		}
+
+		#region Public Properties
+		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;
+			}
+		}
+		
+		/// <value>
+		/// Indication that the rtm backend is configured
+		/// </value>
+		public bool Configured
+		{
+			get { return configured; }
+		}
+		
+		/// <value>
+		/// Inidication that the backend is initialized
+		/// </value>
+		public bool Initialized
+		{
+			get { return initialized; }
+		}
+		
+#endregion // Public Properties
+
+#region Public Methods
+		public ITask CreateTask (string taskName, ICategory category)
+		{
+			string categoryID;
+			RtmTask rtmTask = null;
+			
+			if(category is Tasquer.AllCategory)
+				categoryID = null;
+			else
+				categoryID = (category as RtmCategory).ID;	
+
+			if(rtm != null) {
+				try {
+					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());
+				}
+			}
+			else
+				throw new Exception("Unable to communicate with Remember The Milk");
+				
+			return rtmTask;
+		}
+		
+		public void DeleteTask(ITask task)
+		{
+			RtmTask 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());
+				}
+			}
+			else
+				throw new Exception("Unable to communicate with Remember The Milk");
+		}
+		
+		public void Refresh()
+		{
+			Logger.Debug("Refreshing data...");
+
+			runRefreshEvent.Set();
+			
+			Logger.Debug("Done refreshing data!");
+		}
+
+		public void Initialize()
+		{
+			// *************************************
+			// AUTHENTICATION to Remember The Milk
+			// *************************************
+			string authToken =
+				Application.Preferences.Get (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;
+				} catch (Exception e) {
+					Application.Preferences.Set (Preferences.AuthTokenKey, null);
+					Application.Preferences.Set (Preferences.UserIdKey, null);
+					Application.Preferences.Set (Preferences.UserNameKey, null);
+					rtm = null;
+					rtmAuth = null;
+					Logger.Error("Exception authenticating, reverting" + e.Message);
+				}
+			}
+
+			if(rtm == null)
+				rtm = new Rtm(apiKey, sharedSecret);
+			
+			runningRefreshThread = true;
+			if (refreshThread.ThreadState == ThreadState.Running) {
+				Logger.Debug ("RtmBackend refreshThread already running");
+			} else {
+				refreshThread.Start();
+			}
+			runRefreshEvent.Set();		
+		}
+
+		public void Cleanup()
+		{
+			runningRefreshThread = false;
+			runRefreshEvent.Set();
+			refreshThread.Abort ();
+		}
+
+		public Gtk.Widget GetPreferencesWidget ()
+		{
+			return new RtmPreferencesWidget ();
+		}
+
+		public string GetAuthUrl()
+		{
+			frob = rtm.AuthGetFrob();
+			string url = rtm.AuthCalcUrl(frob, AuthLevel.Delete);
+			return url;
+		}
+
+		public void FinishedAuth()
+		{
+			rtmAuth = rtm.AuthGetToken(frob);
+			if (rtmAuth != null) {
+				Preferences prefs = Application.Preferences;
+				prefs.Set (Preferences.AuthTokenKey, rtmAuth.Token);
+				if (rtmAuth.User != null) {
+					prefs.Set (Preferences.UserNameKey, rtmAuth.User.Username);
+					prefs.Set (Preferences.UserIdKey, rtmAuth.User.UserId);
+				}
+			}
+			
+			string authToken =
+				Application.Preferences.Get (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();
+				} catch (Exception e) {
+					rtm = null;
+					rtmAuth = null;				
+					Logger.Error("Exception authenticating, reverting" + e.Message);
+				}	
+			}
+		}
+
+		public void UpdateTaskName(RtmTask task)
+		{
+			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());
+				}
+			}
+		}
+		
+		public void UpdateTaskDueDate(RtmTask task)
+		{
+			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());
+				}
+			}
+		}
+		
+		public void UpdateTaskCompleteDate(RtmTask task)
+		{
+			UpdateTask(task);
+		}
+		
+		public void UpdateTaskPriority(RtmTask task)
+		{
+			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());
+				}
+			}
+		}
+		
+		public void UpdateTaskActive(RtmTask task)
+		{
+			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());
+					}
+				}
+			}
+			else
+				UpdateTask(task);
+		}
+		
+		public void UpdateTaskInactive(RtmTask task)
+		{
+			UpdateTask(task);
+		}	
+
+		public void UpdateTaskCompleted(RtmTask task)
+		{
+			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());
+				}
+			}
+		}	
+
+		public void UpdateTaskDeleted(RtmTask task)
+		{
+			UpdateTask(task);
+		}
+		
+
+		public void MoveTaskCategory(RtmTask task, string id)
+		{
+			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());
+				}
+			}					
+		}
+		
+		
+		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 = new RtmTask(ts, 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);
+						}
+					});
+				}
+				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)
+		{
+			RtmNet.Note note = null;
+			RtmNote rtmNote = 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());
+				}
+			}
+			else
+				throw new Exception("Unable to communicate with Remember The Milk");
+				
+			return rtmNote;
+		}
+
+
+		public void DeleteNote (RtmTask rtmTask, RtmNote note)
+		{
+			if(rtm != null) {
+				try {
+					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");
+		}
+
+		public void SaveNote (RtmTask rtmTask, RtmNote note)
+		{
+			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());
+				}
+			}
+			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 Tasquer.AllCategory)
+				return -1;
+			else if (categoryB is Tasquer.AllCategory)
+				return 1;
+			
+			return (categoryA.Name.CompareTo (categoryB.Name));
+		}
+
+		/// <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()
+		{
+			Logger.Debug("RtmBackend.UpdateCategories was called");
+			
+			try {
+				Lists lists = rtm.ListsGetList();
+				foreach(List list in lists.listCollection)
+				{
+					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);
+							}
+						});
+					}
+				}
+			} catch (Exception e) {
+				Logger.Debug("Exception in fetch " + e.Message);
+			}
+			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()
+		{
+			Logger.Debug("RtmBackend.UpdateTasks was called");
+			
+			try {
+				Lists lists = rtm.ListsGetList();
+				foreach(List list in lists.listCollection)
+				{
+					Tasks tasks = null;
+					try {
+						tasks = rtm.TasksGetList(list.ID);
+					} catch (Exception tglex) {
+						Logger.Debug("Exception calling TasksGetList(list.ListID) " + tglex.Message);
+					}
+
+					if(tasks != null) {
+						foreach(List tList in tasks.ListCollection)
+						{
+							foreach(TaskSeries ts in tList.TaskSeriesCollection)
+							{
+								RtmTask rtmTask = new RtmTask(ts, this, list.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);
+									});
+								}
+							}
+						}
+					}
+				}
+			} catch (Exception e) {
+				Logger.Debug("Exception in fetch " + e.Message);
+				Logger.Debug(e.ToString());
+			}
+			Logger.Debug("RtmBackend.UpdateTasks is done");			
+		}
+		
+		
+		
+		private void RefreshThreadLoop()
+		{
+			while(runningRefreshThread) {
+				runRefreshEvent.WaitOne();
+
+				if(!runningRefreshThread)
+					return;
+
+				// Fire the event on the main thread
+				Gtk.Application.Invoke ( delegate {
+					if(BackendSyncStarted != null)
+						BackendSyncStarted();
+				});
+
+				runRefreshEvent.Reset();
+
+				if(rtmAuth != null) {
+					UpdateCategories();			
+					UpdateTasks();
+				}
+				if(!initialized) {
+					initialized = true;
+
+					// Fire the event on the main thread
+					Gtk.Application.Invoke ( delegate {
+						if(BackendInitialized != null)
+							BackendInitialized();
+					});
+				}
+
+				// Fire the event on the main thread
+				Gtk.Application.Invoke ( delegate {
+					if(BackendSyncFinished != null)
+						BackendSyncFinished();
+				});
+			}
+		}
+		
+#endregion // Private Methods
+
+#region Event Handlers
+#endregion // Event Handlers
+	}
+}

Added: trunk/src/Backends/Rtm/RtmCategory.cs
==============================================================================
--- (empty file)
+++ trunk/src/Backends/Rtm/RtmCategory.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,73 @@
+// RtmCategory.cs created with MonoDevelop
+// User: boyd at 9:06 AMÂ2/11/2008
+//
+// To change standard headers go to Edit->Preferences->Coding->Standard Headers
+//
+
+using System;
+using Tasquer;
+using RtmNet;
+
+namespace Tasquer.Backends.RtmBackend
+{
+	public class RtmCategory : ICategory
+	{
+		private List list;
+		private Gtk.TreeIter iter;
+
+		public RtmCategory(List list)
+		{
+			this.list = list;
+		}
+		
+		public string Name
+		{
+			get { return list.Name; }
+		}
+
+		public string ID
+		{
+			get { return list.ID; }
+		}
+    
+		public int Deleted
+		{
+			get { return list.Deleted; }
+		}
+
+		public int Locked
+		{
+			get { return list.Locked; }
+		}
+    
+		public int Archived
+		{
+			get { return list.Archived; }
+		}
+
+		public int Position
+		{
+			get { return list.Position; }
+		}
+
+		public int Smart
+		{
+			get { return list.Smart; }
+		}
+		
+		public Gtk.TreeIter Iter
+		{
+			get { return iter; }
+			set { iter = value; }
+		}
+
+		public bool ContainsTask(ITask task)
+		{
+			if(task.Category is RtmCategory)
+				return ((task.Category as RtmCategory).ID.CompareTo(ID) == 0);
+			else
+				return false;
+		}
+
+	}
+}

Added: trunk/src/Backends/Rtm/RtmNote.cs
==============================================================================
--- (empty file)
+++ trunk/src/Backends/Rtm/RtmNote.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,37 @@
+// RtmNote.cs created with MonoDevelop
+// User: calvin at 11:05 AMÂ2/12/2008
+//
+// To change standard headers go to Edit->Preferences->Coding->Standard Headers
+//
+
+using System;
+using Tasquer;
+using RtmNet;
+
+namespace Tasquer.Backends.RtmBackend
+{
+	public class RtmNote : INote
+	{
+		Note note;
+		
+		public RtmNote(Note note)
+		{
+			this.note = note;
+			if( (note.Title != null) && (note.Title.Length > 0) ) {
+				note.Text = note.Title + note.Text;
+			}
+			note.Title = String.Empty;
+		}
+		
+		public string ID
+		{
+			get { return note.ID; }
+		}
+    
+		public string Text
+		{
+			get { return note.Text; }
+			set { note.Text = value; }
+		}
+	}
+}

Added: trunk/src/Backends/Rtm/RtmPreferencesWidget.cs
==============================================================================
--- (empty file)
+++ trunk/src/Backends/Rtm/RtmPreferencesWidget.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,136 @@
+// RtmPreferencesWidget.cs created with MonoDevelop
+// User: boyd at 11:29 PMÂ2/18/2008
+
+using System;
+using Gtk;
+
+namespace Tasquer.Backends.RtmBackend
+{
+	public class RtmPreferencesWidget : Gtk.EventBox
+	{
+		#if GTK_2_10
+ 		private LinkButton		authButton;
+		#else
+		private Button			authButton;
+		#endif
+		
+		private Label			statusLabel;
+		private Gtk.Image		image;
+		private bool			authRequested;
+		private bool			isAuthorized;
+		
+		private static Gdk.Pixbuf normalPixbuf;
+		
+		static RtmPreferencesWidget ()
+		{
+			normalPixbuf = Utilities.GetIcon ("rtmLogo", 122);
+		}
+		
+		public RtmPreferencesWidget () : base ()
+		{
+			LoadPreferences ();
+			
+			// We're using an event box so we can paint the background white
+			BorderWidth = 0;
+			ModifyBg(StateType.Normal, new Gdk.Color(255,255,255));
+			ModifyBase(StateType.Normal, new Gdk.Color(255,255,255));
+
+			VBox mainVBox = new VBox(false, 0);
+			mainVBox.BorderWidth = 10;
+			mainVBox.Show();
+			Add(mainVBox);
+
+			// Add the rtm logo
+			image = new Gtk.Image (normalPixbuf);
+			image.Show();
+			//make the dialog box look pretty without hard coding total size and
+			//therefore clipping displays with large fonts.
+			Alignment spacer = new Alignment((float)0.5, 0, 0, 0);
+			spacer.SetPadding(0, 0, 125, 125);
+			spacer.Add(image);
+			spacer.Show();
+			mainVBox.PackStart(spacer, true, true, 0);
+
+			// Status message label
+			statusLabel = new Label();
+			statusLabel.Justify = Gtk.Justification.Center;
+			statusLabel.Wrap = true;
+			statusLabel.LineWrap = true;
+			statusLabel.Show();
+			statusLabel.UseMarkup = true;
+			statusLabel.UseUnderline = false;
+
+			#if GTK_2_10
+			authButton = new LinkButton("Click Here to Connect");
+			#else
+			authButton = new Button("Click Here to Connect");
+			#endif
+			
+			authButton.Clicked += OnAuthButtonClicked;
+			
+			if ( isAuthorized ) {
+				statusLabel.Text = "\n\nYou are currently connected";
+				string userName = Application.Preferences.Get (Preferences.UserNameKey);
+				if (userName != null && userName.Trim () != string.Empty)
+					statusLabel.Text += " as\n" + userName.Trim ();
+			} else {
+				statusLabel.Text = "\n\nYou are not connected";
+				authButton.Show();
+			}
+			mainVBox.PackStart(statusLabel, false, false, 0);
+			mainVBox.PackStart(authButton, false, false, 0);
+
+			Label blankLabel = new Label("\n");
+			blankLabel.Show();
+			mainVBox.PackStart(blankLabel, false, false, 0);
+		}
+		
+		private void LoadPreferences ()
+		{
+			string authToken = Tasquer.Application.Preferences.Get(Preferences.AuthTokenKey);
+			if (authToken == null || authToken.Trim() == "") {
+				Logger.Debug("Rtm: Not authorized");
+				isAuthorized = false;
+			} else {
+				Logger.Debug("Rtm: Authorized");
+				isAuthorized = true;
+			}
+		}
+		
+		private void OnAuthButtonClicked (object sender, EventArgs args)
+		{
+			RtmBackend rtmBackend = Application.Backend as RtmBackend;
+			if (rtmBackend != null) {
+				if (!isAuthorized && !authRequested) {
+					string url = rtmBackend.GetAuthUrl();
+					Logger.Debug("Launching browser to authorize with Remember the Milk");
+					Gnome.Url.Show(url);
+					authRequested = true;
+					authButton.Label = "Click Here After Authorizing Tasquer";
+				} else if (!isAuthorized && authRequested) {
+					authButton.Label = "Processing...";
+					try {
+						rtmBackend.FinishedAuth();
+						Logger.Debug("Successfully authorized with Remember the Milk");
+						isAuthorized = true;
+						authRequested = false;
+					} catch (RtmNet.RtmApiException) {
+						Logger.Debug("Failed to authorize with Remember the Milk");
+						isAuthorized = false;
+						authRequested = true;
+						authButton.Label = "Failed, Try Again";
+					}
+				}
+				if (isAuthorized) {
+					authButton.Label = "Thank You";
+					authButton.Sensitive = false;
+					statusLabel.Text = "\n\nYou are currently connected";
+					string userName =
+						Application.Preferences.Get(Preferences.UserNameKey);
+					if (userName != null && userName.Trim() != string.Empty)
+						statusLabel.Text += " as\n" + userName.Trim();
+				}
+			}
+		}
+	}
+}

Added: trunk/src/Backends/Rtm/RtmTask.cs
==============================================================================
--- (empty file)
+++ trunk/src/Backends/Rtm/RtmTask.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,315 @@
+// Task.cs created with MonoDevelop
+// User: boyd at 8:50 PMÂ2/10/2008
+
+using System;
+using RtmNet;
+using System.Collections.Generic;
+
+namespace Tasquer.Backends.RtmBackend
+{
+	public class RtmTask : AbstractTask
+	{
+		private RtmBackend rtmBackend;
+		private TaskState state;
+		private RtmCategory category;
+		private List<INote> notes;		
+		
+		TaskSeries taskSeries;
+		
+		/// <summary>
+		/// Constructor that is created from an RTM Task Series
+		/// </summary>
+		/// <param name="taskSeries">
+		/// A <see cref="TaskSeries"/>
+		/// </param>
+		public RtmTask(TaskSeries taskSeries, RtmBackend be, string listID)
+		{
+			this.taskSeries = taskSeries;
+			this.rtmBackend = be;
+			this.category = be.GetCategory(listID);
+			
+			if(CompletionDate == DateTime.MinValue )
+				state = TaskState.Active;
+			else
+				state = TaskState.Completed;
+			notes = new List<INote>();
+			
+			foreach(Note note in taskSeries.Notes.NoteCollection) {
+				RtmNote rtmNote = new RtmNote(note);
+				notes.Add(rtmNote);
+			}
+		}
+		
+		#region Public Properties
+
+		/// <value>
+		/// Holds the name of the task
+		/// </value>		
+		public override string Name
+		{
+			get { return taskSeries.Name; }
+			set {
+				if (value != null) {
+					taskSeries.Name = value.Trim ();
+					rtmBackend.UpdateTaskName(this);
+				}
+			}
+		}
+		
+		/// <value>
+		/// Due Date for the task
+		/// </value>
+		public override DateTime DueDate
+		{
+			get { return taskSeries.Task.Due; }
+			set { 
+				taskSeries.Task.Due = value;
+				rtmBackend.UpdateTaskDueDate(this);			
+			}
+		}
+		
+
+		/// <value>
+		/// Due Date for the task
+		/// </value>
+		public string DueDateString
+		{
+			get {
+				// Return the due date in UTC format
+				string format = "yyyy-MM-ddTHH:mm:ssZ";
+				string dateString = taskSeries.Task.Due.ToUniversalTime ().ToString (format);
+				return dateString;
+			}
+		}
+
+		
+		/// <value>
+		/// Completion Date for the task
+		/// </value>
+		public override DateTime CompletionDate
+		{
+			get { return taskSeries.Task.Completed; }
+			set { 
+				taskSeries.Task.Completed = value;
+				rtmBackend.UpdateTaskCompleteDate(this);
+			}
+		}
+		
+		/// <value>
+		/// Returns if the task is complete
+		/// </value>
+		public override bool IsComplete
+		{
+			get { return state == TaskState.Completed; }
+		}
+		
+		/// <value>
+		/// Holds the priority of the task
+		/// </value>
+		public override TaskPriority Priority
+		{
+			get { 
+				switch (taskSeries.Task.Priority) {
+					default:
+					case "N":
+						return TaskPriority.None;
+					case "1":
+						return TaskPriority.High;
+					case "2":
+						return TaskPriority.Medium;
+					case "3":
+						return TaskPriority.Low;
+				}
+			}
+			set {
+				switch (value) {
+					default:
+					case TaskPriority.None:
+						taskSeries.Task.Priority = "N";
+						break;
+					case TaskPriority.High:
+						taskSeries.Task.Priority = "1";
+						break;
+					case TaskPriority.Medium:
+						taskSeries.Task.Priority = "2";
+						break;
+					case TaskPriority.Low:
+						taskSeries.Task.Priority = "3";
+						break;
+				}
+				rtmBackend.UpdateTaskPriority(this);				
+			}
+		}
+		
+		public string PriorityString
+		{
+			get { return taskSeries.Task.Priority; }
+		}		
+		
+		
+		/// <value>
+		/// Returns if the task has any notes
+		/// </value>
+		public override bool HasNotes
+		{
+			get { return (notes.Count > 0); }
+		}
+		
+		/// <value>
+		/// Returns if the task supports multiple notes
+		/// </value>
+		public override bool SupportsMultipleNotes
+		{
+			get { return true; }
+		}
+		
+		/// <value>
+		/// Holds the current state of the task
+		/// </value>
+		public override TaskState State
+		{
+			get { return state; }
+		}
+		
+		/// <value>
+		/// Returns the category object for this task
+		/// </value>
+		public override ICategory Category
+		{
+			get { return category; } 
+			set {
+				RtmCategory rtmCategory = value as RtmCategory;
+				rtmBackend.MoveTaskCategory(this, rtmCategory.ID);				
+			}
+		}
+		
+		/// <value>
+		/// Returns the notes associates with this task
+		/// </value>
+		public override List<INote> Notes
+		{
+			get { return notes; }
+		}
+		
+		/// <value>
+		/// Holds the current RtmBackend for this task
+		/// </value>
+		public RtmBackend RtmBackend
+		{
+			get { return this.rtmBackend; }
+		}
+		
+		public string ID
+		{
+			get {return taskSeries.TaskID; }
+		}
+		
+		public string SeriesTaskID
+		{
+			get { return taskSeries.TaskID; }
+		}
+		
+		public string TaskTaskID
+		{
+			get { return taskSeries.Task.TaskID; }
+		}
+		
+		public string ListID
+		{
+			get { return category.ID; }
+		}
+		#endregion // Public Properties
+		
+		#region Public Methods
+		/// <summary>
+		/// Activates the task
+		/// </summary>
+		public override void Activate ()
+		{
+			Logger.Debug("Activating Task: " + Name);
+			state = TaskState.Active;
+			taskSeries.Task.Completed = DateTime.MinValue;
+			rtmBackend.UpdateTaskActive(this);
+		}
+		
+		/// <summary>
+		/// Sets the task to be inactive
+		/// </summary>
+		public override void Inactivate ()
+		{
+			Logger.Debug("Inactivating Task: " + Name);		
+			state = TaskState.Inactive;
+			taskSeries.Task.Completed = DateTime.Now;
+			rtmBackend.UpdateTaskInactive(this);
+		}
+		
+		/// <summary>
+		/// Completes the task
+		/// </summary>
+		public override void Complete ()
+		{
+			Logger.Debug("Completing Task: " + Name);			
+			state = TaskState.Completed;
+			if(taskSeries.Task.Completed == DateTime.MinValue)
+				taskSeries.Task.Completed = DateTime.Now;
+			rtmBackend.UpdateTaskCompleted(this);
+		}
+		
+		/// <summary>
+		/// Deletes the task
+		/// </summary>
+		public override void Delete ()
+		{
+			state = TaskState.Deleted;
+			rtmBackend.UpdateTaskDeleted(this);
+		}
+		
+		/// <summary>
+		/// Adds a note to a task
+		/// </summary>
+		/// <param name="note">
+		/// A <see cref="INote"/>
+		/// </param>
+		public override INote CreateNote(string text)
+		{
+			RtmNote rtmNote;
+			
+			rtmNote = rtmBackend.CreateNote(this, text);
+			notes.Add(rtmNote);
+			
+			return rtmNote;
+		}
+		
+		/// <summary>
+		/// Deletes a note from a task
+		/// </summary>
+		/// <param name="note">
+		/// A <see cref="INote"/>
+		/// </param>
+		public override void DeleteNote(INote note)
+		{
+			RtmNote rtmNote = (note as RtmNote);
+			
+			foreach(RtmNote lRtmNote in notes) {
+				if(lRtmNote.ID == rtmNote.ID) {
+					notes.Remove(lRtmNote);
+					break;
+				}
+			}
+			rtmBackend.DeleteNote(this, rtmNote);
+		}		
+
+		/// <summary>
+		/// Deletes a note from a task
+		/// </summary>
+		/// <param name="note">
+		/// A <see cref="INote"/>
+		/// </param>
+		public override void SaveNote(INote note)
+		{		
+			rtmBackend.SaveNote(this, (note as RtmNote));
+		}		
+
+		#endregion // Public Methods
+	}
+}

Added: trunk/src/Backends/Sqlite/Database.cs
==============================================================================
--- (empty file)
+++ trunk/src/Backends/Sqlite/Database.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,209 @@
+// Database.cs created with MonoDevelop
+// User: calvin at 11:27 AMÂ2/19/2008
+//
+// To change standard headers go to Edit->Preferences->Coding->Standard Headers
+//
+
+using System;
+using Mono.Data.Sqlite;
+using System.IO;
+using Tasquer;
+
+namespace Tasquer.Backends.Sqlite
+{
+	public class Database
+	{
+		private SqliteConnection connection;
+        public static readonly DateTime LocalUnixEpoch = new DateTime(1970, 1, 1).ToLocalTime();
+		
+		public SqliteConnection Connection
+		{
+			get { return connection; }
+		}
+		
+		public Database()
+		{
+		}
+		
+		
+		public void Open()
+		{
+			string dbLocation = "URI=file:" + Path.Combine(
+						Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), 
+						"tasquer/sqlitebackend.db");
+
+			connection = new SqliteConnection(dbLocation);
+			connection.Open();
+			
+			CreateTables();
+		}
+		
+		public void Close()
+		{
+			connection.Close();
+			connection = null;			
+		}
+		
+		public void CreateTables()
+		{
+			if(!TableExists("Categories")) {
+				Console.WriteLine("Creating Categories table");
+				ExecuteScalar(@"CREATE TABLE Categories (
+					ID INTEGER PRIMARY KEY,
+					Name TEXT,
+					ExternalID TEXT
+				)");
+			}
+
+			if(!TableExists("Tasks")) {
+				Console.WriteLine("Creating Tasks table");
+				ExecuteScalar(@"CREATE TABLE Tasks (
+					ID INTEGER PRIMARY KEY,
+					Category INTEGER,
+					Name TEXT,
+					DueDate INTEGER,
+					CompletionDate INTEGER,
+					Priority INTEGER,
+					State INTEGER,
+					ExternalID TEXT
+				)");
+			}
+
+			if(!TableExists("Notes")) {
+				Console.WriteLine("Creating Notes table");
+				ExecuteScalar(@"CREATE TABLE Notes (
+					ID INTEGER PRIMARY KEY,
+					Task INTEGER KEY,
+					Name TEXT,
+					Text TEXT,
+					ExternalID TEXT
+				)");
+			}
+		}
+		
+
+		public object ExecuteScalar(string command)
+        {
+        	object resultset;
+        	
+        	SqliteCommand cmd = connection.CreateCommand();
+        	cmd.CommandText = command;
+        	resultset = cmd.ExecuteScalar();
+        	return resultset;
+        }
+        
+        public int ExecuteNonQuery(string command)		
+        {
+        	int resultCode;
+        	SqliteCommand cmd = connection.CreateCommand();
+        	cmd.CommandText = command;
+        	resultCode = cmd.ExecuteNonQuery();
+        	cmd.Dispose();
+        	return resultCode;
+        }
+        
+        public string GetSingleString(string command)
+        {
+        	string readString = String.Empty;
+        	try {
+	        	SqliteCommand cmd = connection.CreateCommand();
+	        	cmd.CommandText = command;
+	        	SqliteDataReader dataReader = cmd.ExecuteReader();
+	        	if(dataReader.Read())
+	        		readString = dataReader.GetString(0);
+	        	else
+	        		readString = string.Empty;
+	        	dataReader.Close();
+	        	cmd.Dispose();
+			} catch (Exception e) {
+				Logger.Debug("Exception Thrown {0}", e);
+			}
+        	return readString;
+        }
+        
+        public DateTime GetDateTime(string command)
+        {
+        	long longValue;
+        	DateTime dtValue;
+	       	try{
+	        	longValue = GetSingleLong(command);
+	        	if(longValue == 0)
+	        		dtValue = DateTime.MinValue;
+	        	else
+	        		dtValue = Database.ToDateTime(longValue);
+			} catch (Exception e) {
+				Logger.Debug("Exception Thrown {0}", e);
+				dtValue = DateTime.MinValue;
+			}
+        	return dtValue;
+        }        
+        
+        public int GetSingleInt(string command)
+        {
+        	int dtVal = 0;
+        	try {        	
+	        	SqliteCommand cmd = connection.CreateCommand();
+	        	cmd.CommandText = command;
+	        	SqliteDataReader dataReader = cmd.ExecuteReader();
+	        	if(dataReader.Read())
+	        		dtVal = dataReader.GetInt32(0);
+	        	else
+	        		dtVal = 0;
+	        	dataReader.Close();
+	        	cmd.Dispose();
+			} catch (Exception e) {
+				Logger.Debug("Exception Thrown {0}", e);
+			}        	
+        	return dtVal;
+        }  
+
+        public long GetSingleLong(string command)
+        {
+        	long dtVal = 0;
+         	try {       	
+	        	SqliteCommand cmd = connection.CreateCommand();
+	        	cmd.CommandText = command;
+	        	SqliteDataReader dataReader = cmd.ExecuteReader();
+	        	if(dataReader.Read())
+	        		dtVal = dataReader.GetInt64(0);
+	        	else
+	        		dtVal = 0;
+	        	dataReader.Close();
+	        	cmd.Dispose();
+			} catch (Exception e) {
+				Logger.Debug("Exception Thrown {0}", e);
+			} 
+        	return dtVal;
+        }  
+
+        
+		public bool TableExists(string table)
+		{
+			return Convert.ToInt32(ExecuteScalar(String.Format(@"
+				SELECT COUNT(*)
+				FROM sqlite_master
+				WHERE Type='table' AND Name='{0}'", 
+				table))) > 0;
+		}
+
+		public static DateTime ToDateTime(long time)
+		{
+			return FromTimeT(time);
+		}
+
+		public static long FromDateTime(DateTime time)
+		{
+			return ToTimeT(time);
+		}
+
+		public static DateTime FromTimeT(long time)
+		{
+			return LocalUnixEpoch.AddSeconds(time);
+		}
+
+		public static long ToTimeT(DateTime time)
+		{
+			return (long)time.Subtract(LocalUnixEpoch).TotalSeconds;
+		}
+	}
+}

Added: trunk/src/Backends/Sqlite/SqliteBackend.cs
==============================================================================
--- (empty file)
+++ trunk/src/Backends/Sqlite/SqliteBackend.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,306 @@
+// SqliteBackend.cs created with MonoDevelop
+// User: boyd at 7:10 AMÂ2/11/2008
+
+using System;
+using System.Collections.Generic;
+using Mono.Unix;
+using Tasquer.Backends;
+using Mono.Data.Sqlite;
+
+namespace Tasquer.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;
+		
+		private Database db;
+		
+		private Gtk.ListStore categoryListStore;
+		private Gtk.TreeModelSort sortedCategoriesModel;
+
+		public event BackendInitializedHandler BackendInitialized;
+		public event BackendSyncStartedHandler BackendSyncStarted;
+		public event BackendSyncFinishedHandler BackendSyncFinished;
+		
+		SqliteCategory defaultCategory;
+		//SqliteCategory workCategory;
+		//SqliteCategory projectsCategory;
+		
+		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);
+		}
+		
+		#region Public Properties
+		public string Name
+		{
+			get { return "Local File"; } // TODO: Return something more usable to the user like, "Built-in" or whatever
+		}
+		
+		/// <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; }
+		}
+		
+		/// <value>
+		/// Indication that the Sqlite backend is configured
+		/// </value>
+		public bool Configured 
+		{
+			get { return configured; }
+		}
+		
+		
+		/// <value>
+		/// Inidication that the backend is initialized
+		/// </value>
+		public bool Initialized
+		{
+			get { return initialized; }
+		}		
+		
+		public Database Database
+		{
+			get { return db; }
+		}
+		#endregion // Public Properties
+		
+		#region Public Methods
+		public ITask CreateTask (string taskName, ICategory category)		
+		{
+			// not sure what to do here with the category
+			SqliteTask task = new SqliteTask (this, taskName);
+			
+			// Determine and set the task category
+			if (category == null || category is Tasquer.AllCategory)
+				task.Category = defaultCategory; // Default to work
+			else
+				task.Category = category;
+			
+			Gtk.TreeIter iter = taskStore.AppendNode ();
+			taskStore.SetValue (iter, 0, task);
+			taskIters [task.Id] = iter;
+			
+			return task;
+		}
+		
+		public void DeleteTask(ITask task)
+		{}
+		
+		public void Refresh()
+		{}
+		
+		public void Initialize()
+		{
+			if(db == null)
+				db = new Database();
+				
+			db.Open();
+			
+			//
+			// Add in the "All" Category
+			//
+			AllCategory allCategory = new Tasquer.AllCategory ();
+			Gtk.TreeIter iter = categoryListStore.Append ();
+			categoryListStore.SetValue (iter, 0, allCategory);
+			
+			
+			RefreshCategories();
+			RefreshTasks();		
+
+		
+			initialized = true;
+			if(BackendInitialized != null) {
+				BackendInitialized();
+			}		
+		}
+
+		public void Cleanup()
+		{
+			this.categoryListStore.Clear();
+			this.taskStore.Clear();
+			this.taskIters.Clear();
+
+			db.Close();
+			db = null;
+			initialized = false;		
+		}
+
+		public Gtk.Widget GetPreferencesWidget ()
+		{
+			// TODO: Replace this with returning null once things are going
+			// so that the Preferences Dialog doesn't waste space.
+			return new Gtk.Label ("Local file requires no configuration.");
+		}
+		#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 Tasquer.AllCategory)
+				return -1;
+			else if (categoryB is Tasquer.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.Id) == false)
+				return;
+				
+			iter = taskIters [task.Id];
+			
+			if (task.State == TaskState.Deleted) {
+				taskIters.Remove (task.Id);
+				if (taskStore.Remove (ref iter) == false) {
+					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;
+			
+			string command = "SELECT id FROM Categories";
+        	SqliteCommand cmd = db.Connection.CreateCommand();
+        	cmd.CommandText = command;
+        	SqliteDataReader dataReader = cmd.ExecuteReader();
+        	while(dataReader.Read()) {
+			    int id = dataReader.GetInt32(0);
+				hasValues = true;
+				
+				newCategory = new SqliteCategory (this, id);
+				if( (defaultCategory == null) || (newCategory.Name.CompareTo("Work") == 0) )
+					defaultCategory = newCategory;
+				iter = categoryListStore.Append ();
+				categoryListStore.SetValue (iter, 0, newCategory);				
+        	}
+
+        	dataReader.Close();
+        	cmd.Dispose();
+
+			if(!hasValues)
+			{
+				defaultCategory = newCategory = new SqliteCategory (this, "Work");
+				iter = categoryListStore.Append ();
+				categoryListStore.SetValue (iter, 0, newCategory);
+
+				newCategory = new SqliteCategory (this, "Personal");
+				iter = categoryListStore.Append ();
+				categoryListStore.SetValue (iter, 0, newCategory);
+				
+				newCategory = new SqliteCategory (this, "Family");
+				iter = categoryListStore.Append ();
+				categoryListStore.SetValue (iter, 0, newCategory);		
+
+				newCategory = new SqliteCategory (this, "Project");
+				iter = categoryListStore.Append ();
+				categoryListStore.SetValue (iter, 0, newCategory);		
+			}
+		}
+		
+
+		public void RefreshTasks()
+		{
+			Gtk.TreeIter iter;
+			SqliteTask newTask;
+			bool hasValues = false;
+			
+			string command = "SELECT id FROM Tasks";
+        	SqliteCommand cmd = db.Connection.CreateCommand();
+        	cmd.CommandText = command;
+        	SqliteDataReader dataReader = cmd.ExecuteReader();
+        	while(dataReader.Read()) {
+			    int id = dataReader.GetInt32(0);
+				hasValues = true;
+				
+				newTask = new SqliteTask(this, id);
+				iter = taskStore.AppendNode();
+				taskStore.SetValue (iter, 0, newTask);				
+        	}
+
+        	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.Id] = iter;
+			}
+		}
+
+		#endregion // Private Methods
+		
+		#region Event Handlers
+		#endregion // Event Handlers
+	}
+}

Added: trunk/src/Backends/Sqlite/SqliteCategory.cs
==============================================================================
--- (empty file)
+++ trunk/src/Backends/Sqlite/SqliteCategory.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,70 @@
+// SqliteCategory.cs created with MonoDevelop
+// User: boyd at 9:06 AMÂ2/11/2008
+//
+// To change standard headers go to Edit->Preferences->Coding->Standard Headers
+//
+
+using System;
+using Tasquer;
+
+namespace Tasquer.Backends.Sqlite
+{
+	public class SqliteCategory : ICategory
+	{
+		private int id;
+		SqliteBackend backend;
+		
+		public int ID
+		{
+			get { return id; }
+		}
+		
+		public string Name
+		{
+			get {
+				string command = String.Format("SELECT Name FROM Categories where ID='{0}'", id);
+				return backend.Database.GetSingleString(command);
+			}
+			set {
+				string command = String.Format("UPDATE Categories set Name='{0}' where ID='{0}'", value, id);
+				backend.Database.ExecuteScalar(command);
+			}
+		}
+		
+		public string ExternalID
+		{
+			get {
+				string command = String.Format("SELECT ExternalID FROM Categories where ID='{0}'", id);
+				return backend.Database.GetSingleString(command);
+			}
+			set {
+				string command = String.Format("UPDATE Categories set ExternalID='{0}' where ID='{0}'", value, id);
+				backend.Database.ExecuteScalar(command);
+			}
+		}
+		
+		public SqliteCategory (SqliteBackend backend, string name)
+		{
+			this.backend = backend;
+			string command = String.Format("INSERT INTO Categories (Name, ExternalID) values ('{0}', '{1}')", name, string.Empty);
+			backend.Database.ExecuteScalar(command);
+			this.id = backend.Database.Connection.LastInsertRowId;
+			//Logger.Debug("Inserted category named: {0} with id {1}", name, id);
+		}
+		
+		public SqliteCategory (SqliteBackend backend, int id)
+		{
+			this.backend = backend;
+			this.id = id;
+		}
+
+		public bool ContainsTask(ITask task)
+		{
+			if(task.Category is SqliteCategory)
+				return ((task.Category as SqliteCategory).ID == id);
+
+			return false;
+		}
+		
+	}
+}

Added: trunk/src/Backends/Sqlite/SqliteNote.cs
==============================================================================
--- (empty file)
+++ trunk/src/Backends/Sqlite/SqliteNote.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,29 @@
+// SqliteNote.cs created with MonoDevelop
+// User: calvin at 10:56 AMÂ2/12/2008
+//
+// To change standard headers go to Edit->Preferences->Coding->Standard Headers
+//
+
+using System;
+using Tasquer;
+
+namespace Tasquer.Backends.Sqlite
+{
+	public class SqliteNote : INote
+	{
+		public string Name
+		{
+			get { return ""; }
+			set { // TODO: Implement something 
+			}
+		}
+   
+		public string Text
+		{
+			get { return ""; }
+			set { // TODO: Implement something 
+			}
+		}
+
+	}
+}

Added: trunk/src/Backends/Sqlite/SqliteTask.cs
==============================================================================
--- (empty file)
+++ trunk/src/Backends/Sqlite/SqliteTask.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,201 @@
+// SqliteTask.cs created with MonoDevelop
+// User: boyd at 8:50 PMÂ2/10/2008
+
+using System;
+using Tasquer;
+using System.Collections.Generic;
+
+namespace Tasquer.Backends.Sqlite
+{
+	public class SqliteTask : AbstractTask
+	{
+		private SqliteBackend backend;
+		private int id;
+		
+		public SqliteTask(SqliteBackend backend, string name)
+		{
+			this.backend = backend;
+			string command = String.Format("INSERT INTO Tasks (Name, DueDate, CompletionDate, Priority, State, Category, ExternalID) values ('{0}','{1}', '{2}','{3}', '{4}', '{5}', '{6}')", 
+								name, Database.FromDateTime(DateTime.MinValue), Database.FromDateTime(DateTime.MinValue), 
+								((int)(TaskPriority.None)), ((int)TaskState.Active), 0, string.Empty );
+			backend.Database.ExecuteScalar(command);
+			this.id = backend.Database.Connection.LastInsertRowId;
+			//Logger.Debug("Inserted task named: {0} with id {1}", name, id);			
+		}
+		
+		public SqliteTask (SqliteBackend backend, int id)
+		{
+			this.backend = backend;
+			this.id = id;
+		}		
+		
+		#region Public Properties
+		
+		public int Id
+		{
+			get { return id; }
+			set { id = value; }
+		}
+		
+		public override string Name
+		{
+			get {
+				string command = String.Format("SELECT Name FROM Tasks where ID='{0}'", id);
+				return backend.Database.GetSingleString(command);
+			}
+			set {
+				string command = String.Format("UPDATE Tasks set Name='{0}' where ID='{1}'", value, id);
+				backend.Database.ExecuteScalar(command);
+				backend.UpdateTask(this);
+			}
+		}
+		
+		public override DateTime DueDate
+		{
+			get {
+				string command = String.Format("SELECT DueDate FROM Tasks where ID='{0}'", id);
+				return backend.Database.GetDateTime(command);
+			}
+			set {
+				string command = String.Format("UPDATE Tasks set DueDate='{0}' where ID='{1}'", Database.FromDateTime(value), id);
+				backend.Database.ExecuteScalar(command);
+				backend.UpdateTask(this);				
+			}
+		}
+		
+		
+		public override DateTime CompletionDate
+		{
+			get {
+				string command = String.Format("SELECT CompletionDate FROM Tasks where ID='{0}'", id);
+				return backend.Database.GetDateTime(command);
+			}
+			set {
+				string command = String.Format("UPDATE Tasks set CompletionDate='{0}' where ID='{1}'", Database.FromDateTime(value), id);
+				backend.Database.ExecuteScalar(command);
+				backend.UpdateTask(this);				
+			}
+		}		
+		
+		
+		public override bool IsComplete
+		{
+			get {
+				if (CompletionDate == DateTime.MinValue)
+					return false;
+				
+				return true;
+			}
+		}
+		
+		public override TaskPriority Priority
+		{
+			get {
+				string command = String.Format("SELECT Priority FROM Tasks where ID='{0}'", id);
+				return (TaskPriority)backend.Database.GetSingleInt(command);
+			}
+			set {
+				string command = String.Format("UPDATE Tasks set Priority='{0}' where ID='{1}'", ((int)value), id);
+				backend.Database.ExecuteScalar(command);
+				backend.UpdateTask(this);				
+			}
+		}
+
+		public override bool HasNotes
+		{
+			get { return false; }
+		}
+		
+		public override bool SupportsMultipleNotes
+		{
+			get { return false; }
+		}
+		
+		public override TaskState State
+		{
+			get { return LocalState; }
+		}
+		
+		public TaskState LocalState
+		{
+			get {
+				string command = String.Format("SELECT State FROM Tasks where ID='{0}'", id);
+				return (TaskState)backend.Database.GetSingleInt(command);
+			}
+			set {
+				string command = String.Format("UPDATE Tasks set State='{0}' where ID='{1}'", ((int)value), id);
+				backend.Database.ExecuteScalar(command);
+				backend.UpdateTask(this);				
+			}
+		}
+
+		public override ICategory Category
+		{
+			get {
+				string command = String.Format("SELECT Category FROM Tasks where ID='{0}'", id);
+				int catID = backend.Database.GetSingleInt(command);
+				SqliteCategory sqCat = new SqliteCategory(backend, catID);
+				return sqCat;
+			}
+			set {
+				string command = String.Format("UPDATE Tasks set Category='{0}' where ID='{1}'", ((int)(value as SqliteCategory).ID), id);
+				backend.Database.ExecuteScalar(command);
+				backend.UpdateTask(this);
+			}
+		}
+		
+		public override List<INote> Notes
+		{
+			get { return null; }
+		}		
+		
+		#endregion // Public Properties
+		
+		#region Public Methods
+		public override void Activate ()
+		{
+			// Logger.Debug ("SqliteTask.Activate ()");
+			CompletionDate = DateTime.MinValue;
+			LocalState = TaskState.Active;
+			backend.UpdateTask (this);
+		}
+		
+		public override void Inactivate ()
+		{
+			// Logger.Debug ("SqliteTask.Inactivate ()");
+			CompletionDate = DateTime.Now;
+			LocalState = TaskState.Inactive;
+			backend.UpdateTask (this);
+		}
+		
+		public override void Complete ()
+		{
+			//Logger.Debug ("SqliteTask.Complete ()");
+			CompletionDate = DateTime.Now;
+			LocalState = TaskState.Completed;
+			backend.UpdateTask (this);
+		}
+		
+		public override void Delete ()
+		{
+			//Logger.Debug ("SqliteTask.Delete ()");
+			LocalState = TaskState.Deleted;
+			backend.UpdateTask (this);
+		}
+		
+		public override INote CreateNote(string text)
+		{
+			return null;
+		}
+		
+		public override void DeleteNote(INote note)
+		{
+		}
+
+		public override void SaveNote(INote note)
+		{
+		}
+
+		#endregion // Public Methods
+	}
+}

Added: trunk/src/CellRendererDate.cs
==============================================================================
--- (empty file)
+++ trunk/src/CellRendererDate.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,313 @@
+
+using System;
+using Mono.Unix;
+using Gtk;
+using Tasquer;
+
+namespace Gtk.Extras
+{
+	public delegate void DateEditedHandler (CellRendererDate renderer, string path);
+
+	public class CellRendererDate : Gtk.CellRenderer
+	{
+		DateTime date;
+		bool editable;
+		bool show_time;
+
+		Window popup;
+		Calendar cal;
+		string path;
+
+		// The following variable is set during StartEditing ()
+		// and this is kind of a hack, but it "works".  The widget
+		// that is passed-in to StartEditing() is the TreeView.
+		// This is used to position the calendar popup.
+		Gtk.TreeView tree;
+
+		private const uint CURRENT_TIME = 0;
+
+		#region Constructors
+		/// <summary>
+		/// <param name="parent">The parent window where this CellRendererDate
+		/// will be used from.  This is needed to access the Gdk.Screen so the
+		/// Calendar will popup in the proper location.</param>
+		/// </summary>
+		public CellRendererDate()
+		{
+			date = DateTime.MinValue;
+			editable = false;
+			popup = null;
+			show_time = true;
+		}
+
+protected CellRendererDate (System.IntPtr ptr) : base (ptr)
+		{
+		}
+		#endregion // Constructors
+
+		#region Public Properties
+		public DateTime Date
+		{
+			get {
+				return date;
+			}
+			set {
+				date = value;
+			}
+		}
+
+		/// <summary>
+		/// If the renderer is editable, a date picker widget will appear when
+		/// the user attempts to edit the cell.
+		/// </summary>
+		public bool Editable
+		{
+			get {
+				return editable;
+			}
+			set {
+				editable = value;
+
+				if (editable)
+					Mode = CellRendererMode.Editable;
+				else
+					Mode = CellRendererMode.Inert;
+			}
+		}
+
+		/// <summary>
+		/// If true, both the date and time will be shown.  If false, the time
+		/// will be omitted.  The default is true.
+		/// </summary>
+		public bool ShowTime
+		{
+			get {
+				return show_time;
+			}
+			set {
+				show_time = value;
+			}
+		}
+		#endregion // Public Properties
+
+		#region Public Methods
+		public override void GetSize (Widget widget, ref Gdk.Rectangle cell_area,
+		                              out int x_offset, out int y_offset, out int width, out int height)
+		{
+			Pango.Layout layout = GetLayout (widget);
+
+			// FIXME: If this code is ever built into its own library,
+			// the call to Tomboy will definitely have to change
+			layout.SetText (Utilities.GetPrettyPrintDate (date, show_time));
+
+			CalculateSize (layout, out x_offset, out y_offset, out width, out height);
+		}
+
+		public override CellEditable StartEditing (Gdk.Event evnt,
+		                Widget widget, string path, Gdk.Rectangle background_area,
+		                Gdk.Rectangle cell_area, CellRendererState flags)
+		{
+			this.path = path;
+			this.tree = widget as Gtk.TreeView;
+			ShowCalendar();
+
+			return null;
+		}
+
+		#endregion
+
+		#region Public Events
+
+		public event DateEditedHandler Edited;
+
+		#endregion // Public Events
+
+		#region Private Methods
+		protected override void Render (Gdk.Drawable drawable, Widget widget,
+		                                Gdk.Rectangle background_area, Gdk.Rectangle cell_area,
+		                                Gdk.Rectangle expose_area, CellRendererState flags)
+		{
+			Pango.Layout layout = GetLayout (widget);
+
+			// FIXME: If this code is ever built into its own library,
+			// the call to Tomboy will definitely have to change
+			layout.SetText (Utilities.GetPrettyPrintDate (date, show_time));
+
+			int x, y, w, h;
+			CalculateSize (layout, out x, out y, out w, out h);
+
+			StateType state = RendererStateToWidgetState(flags);
+
+			Gdk.GC gc;
+			if (state.Equals(StateType.Selected)) {
+				// Use the proper Gtk.StateType so text appears properly when selected
+				gc = new Gdk.GC(drawable);
+				gc.Copy(widget.Style.TextGC(state));
+				gc.RgbFgColor = widget.Style.Foreground(state);
+			} else
+				gc = widget.Style.TextGC(Gtk.StateType.Normal);
+
+			drawable.DrawLayout (
+			        gc,
+			        cell_area.X + (int)Xalign + (int)Xpad,
+			        cell_area.Y + ((cell_area.Height - h) / 2),
+			        layout);
+		}
+
+		Pango.Layout GetLayout (Gtk.Widget widget)
+		{
+			return widget.CreatePangoLayout (string.Empty);
+		}
+
+		void CalculateSize (Pango.Layout layout, out int x, out int y,
+		                    out int width, out int height)
+		{
+			int w, h;
+
+			layout.GetPixelSize (out w, out h);
+
+			x = 0;
+			y = 0;
+			width = w + ((int) Xpad) * 2;
+			height = h + ((int) Ypad) * 2;
+		}
+
+		private void ShowCalendar()
+		{
+			popup = new Window(WindowType.Popup);
+			popup.Screen = tree.Screen;
+
+			Frame frame = new Frame();
+			frame.Shadow = ShadowType.Out;
+			frame.Show();
+
+			popup.Add(frame);
+
+			VBox box = new VBox(false, 0);
+			box.Show();
+			frame.Add(box);
+
+			cal = new Calendar();
+			cal.DisplayOptions = CalendarDisplayOptions.ShowHeading
+			                     | CalendarDisplayOptions.ShowDayNames
+			                     | CalendarDisplayOptions.ShowWeekNumbers;
+
+			cal.KeyPressEvent += OnCalendarKeyPressed;
+			popup.ButtonPressEvent += OnButtonPressed;
+
+			cal.Show();
+
+			Alignment calAlignment = new Alignment(0.0f, 0.0f, 1.0f, 1.0f);
+			calAlignment.Show();
+			calAlignment.SetPadding(4, 4, 4, 4);
+			calAlignment.Add(cal);
+
+			box.PackStart(calAlignment, false, false, 0);
+
+			// FIXME: Make the popup appear directly below the date
+			Gdk.Rectangle allocation = tree.Allocation;
+//   Gtk.Requisition req = tree.SizeRequest ();
+			int x = 0, y = 0;
+			tree.GdkWindow.GetOrigin(out x, out y);
+//   popup.Move(x + allocation.X, y + allocation.Y + req.Height + 3);
+			popup.Move(x + allocation.X, y + allocation.Y);
+			popup.Show();
+			popup.GrabFocus();
+
+			Grab.Add(popup);
+
+			Gdk.GrabStatus grabbed = Gdk.Pointer.Grab(popup.GdkWindow, true,
+			                         Gdk.EventMask.ButtonPressMask
+			                         | Gdk.EventMask.ButtonReleaseMask
+			                         | Gdk.EventMask.PointerMotionMask, null, null, CURRENT_TIME);
+
+			if (grabbed == Gdk.GrabStatus.Success) {
+				grabbed = Gdk.Keyboard.Grab(popup.GdkWindow,
+				                            true, CURRENT_TIME);
+
+				if (grabbed != Gdk.GrabStatus.Success) {
+					Grab.Remove(popup);
+					popup.Destroy();
+					popup = null;
+				}
+			} else {
+				Grab.Remove(popup);
+				popup.Destroy();
+				popup = null;
+			}
+
+			cal.DaySelectedDoubleClick += OnCalendarDaySelected;
+			cal.ButtonPressEvent += OnCalendarButtonPressed;
+
+			cal.Date = date == DateTime.MinValue ? DateTime.Now : date;
+		}
+
+		public void HideCalendar(bool update)
+		{
+			if (popup != null) {
+				Grab.Remove(popup);
+				Gdk.Pointer.Ungrab(CURRENT_TIME);
+				Gdk.Keyboard.Ungrab(CURRENT_TIME);
+
+				popup.Destroy();
+				popup = null;
+			}
+
+			if (update) {
+				date = cal.GetDate();
+
+				if (Edited != null)
+					Edited (this, path);
+			}
+		}
+
+		private StateType RendererStateToWidgetState(CellRendererState flags)
+		{
+			StateType state = StateType.Normal;
+			if ((CellRendererState.Selected & flags).Equals(
+			                        CellRendererState.Selected))
+				state = StateType.Selected;
+			return state;
+		}
+		#endregion
+
+		#region Event Handlers
+		private void OnButtonPressed(object o, ButtonPressEventArgs args)
+		{
+			if (popup != null)
+				HideCalendar(false);
+		}
+
+		private void OnCalendarDaySelected(object o, EventArgs args)
+		{
+			HideCalendar(true);
+		}
+
+		private void OnCalendarButtonPressed(object o,
+		                                     ButtonPressEventArgs args)
+		{
+			args.RetVal = true;
+		}
+
+		private void OnCalendarKeyPressed(object o, KeyPressEventArgs args)
+		{
+			switch (args.Event.Key) {
+			case Gdk.Key.Escape:
+				HideCalendar(false);
+				break;
+			case Gdk.Key.KP_Enter:
+			case Gdk.Key.ISO_Enter:
+			case Gdk.Key.Key_3270_Enter:
+			case Gdk.Key.Return:
+			case Gdk.Key.space:
+			case Gdk.Key.KP_Space:
+				HideCalendar(true);
+				break;
+			default:
+				break;
+			}
+		}
+
+		#endregion // Event Handlers
+	}
+}

Added: trunk/src/CompletedTaskGroup.cs
==============================================================================
--- (empty file)
+++ trunk/src/CompletedTaskGroup.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,279 @@
+// CompletedTaskGroup.cs created with MonoDevelop
+// User: boyd at 12:34 AMÂ3/1/2008
+//
+// To change standard headers go to Edit->Preferences->Coding->Standard Headers
+//
+
+using System;
+using Gtk;
+using Mono.Unix;
+
+namespace Tasquer
+{
+	public enum ShowCompletedRange : uint
+	{
+		Yesterday = 0,
+		Last7Days,
+		LastMonth,
+		LastYear,
+		All
+	}
+	
+	public class CompletedTaskGroup : TaskGroup
+	{
+		ICategory selectedCategory;
+		HScale rangeSlider;
+		ShowCompletedRange currentRange;
+		
+		public CompletedTaskGroup (string groupName, DateTime rangeStart,
+								   DateTime rangeEnd, Gtk.TreeModel tasks)
+			: base (groupName, rangeStart, rangeEnd,
+					new CompletedTasksSortModel(tasks))
+		{
+			// 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
+			// range.
+			this.HideWhenEmpty = false;
+			
+			selectedCategory = GetSelectedCategory ();
+			Application.Preferences.SettingChanged += OnSelectedCategorySettingChanged;
+			
+			CreateRangeSlider ();
+			UpdateDateRanges ();
+		}
+		
+		/// <summary>
+		/// Create and show a slider (HScale) that will allow the user to
+		/// customize how far in the past to show completed items.
+		/// </summary>
+		private void CreateRangeSlider ()
+		{
+			// There are five (5) different values allowed here:
+			// "Yesterday", "Last7Days", "LastMonth", "LastYear", or "All"
+			// Create the slider with 5 distinct "stops"
+			rangeSlider = new HScale (0, 4, 1);
+			rangeSlider.SetIncrements (1, 1);
+			rangeSlider.WidthRequest = 100;
+			rangeSlider.DrawValue = true;
+			
+			// TODO: Set the initial value and range
+			string rangeStr =
+				Application.Preferences.Get (Preferences.CompletedTasksRange);
+			if (rangeStr == null) {
+				// Set a default value of All
+				rangeStr = ShowCompletedRange.All.ToString ();
+				Application.Preferences.Set (Preferences.CompletedTasksRange,
+											 rangeStr);
+			}
+			
+			currentRange = ParseRange (rangeStr);
+			rangeSlider.Value = (double)currentRange;
+			rangeSlider.FormatValue += OnFormatRangeSliderValue;
+			rangeSlider.ValueChanged += OnRangeSliderChanged;
+			rangeSlider.Show ();
+			
+			this.ExtraWidget = rangeSlider;
+		}
+		
+		/// <summary>
+		/// Override the default filter mechanism so that we show only
+		/// completed tasks in this group.
+		/// </summary>
+		/// <param name="model">
+		/// A <see cref="TreeModel"/>
+		/// </param>
+		/// <param name="iter">
+		/// A <see cref="TreeIter"/>
+		/// </param>
+		/// <returns>
+		/// A <see cref="System.Boolean"/>
+		/// </returns>
+		protected override bool FilterTasks (TreeModel model, TreeIter iter)
+		{
+			// Don't show any task here if showCompletedTasks is false
+			if (showCompletedTasks == false)
+				return false;
+			
+			ITask task = model.GetValue (iter, 0) as ITask;
+			if (task == null || task.State != TaskState.Completed)
+				return false;
+			
+			if (selectedCategory != null
+					&& selectedCategory.ContainsTask (task) == false)
+				return false;
+			
+			// Make sure that the task fits into the specified range depending
+			// on what the user has set the range slider to be.
+			if (task.CompletionDate < this.TimeRangeStart)
+				return false;
+			
+			if (task.CompletionDate == DateTime.MinValue)
+				return true; // Just in case
+			
+			// Don't show tasks in the completed group that were completed
+			// today.  Tasks completed today should still appear under their
+			// original group until tomorrow.
+			DateTime today = DateTime.Now;
+			
+			if (today.Year == task.CompletionDate.Year
+					&& today.DayOfYear == task.CompletionDate.DayOfYear)
+				return false;
+			
+			return true;
+		}
+		
+		protected void OnSelectedCategorySettingChanged (
+								Preferences preferences,
+								string settingKey)
+		{
+			if (settingKey.CompareTo (Preferences.SelectedCategoryKey) != 0)
+				return;
+			
+			selectedCategory = GetSelectedCategory ();
+			Refilter (selectedCategory);
+		}
+		
+		protected ICategory GetSelectedCategory ()
+		{
+			ICategory foundCategory = null;
+			
+			string cat = Application.Preferences.Get (
+							Preferences.SelectedCategoryKey);
+			if (cat != null) {
+				TreeIter iter;
+				TreeModel model = Application.Backend.Categories;
+				
+				if (model.GetIterFirst (out iter) == true) {
+					do {
+						ICategory category = model.GetValue (iter, 0) as ICategory;
+						if (category.Name.CompareTo (cat) == 0) {
+							foundCategory = category;
+							break;
+						}
+					} while (model.IterNext (ref iter) == true);
+				}
+			}
+			
+			return foundCategory;
+		}
+		
+		private void OnRangeSliderChanged (object sender, EventArgs args)
+		{
+			ShowCompletedRange range = (ShowCompletedRange)(uint)rangeSlider.Value;
+			
+			// If the value is different than what we already have, adjust it in
+			// the UI and set the preference.
+			if (range == this.currentRange)
+				return;
+			
+			this.currentRange = range;
+			Application.Preferences.Set (Preferences.CompletedTasksRange,
+										 range.ToString ());
+			
+			UpdateDateRanges ();
+		}
+		
+		private void OnFormatRangeSliderValue (object sender,
+											   FormatValueArgs args)
+		{
+			ShowCompletedRange range = (ShowCompletedRange)args.Value;
+			args.RetVal = GetTranslatedRangeValue (range);
+		}
+		
+		private ShowCompletedRange ParseRange (string rangeStr)
+		{
+			switch (rangeStr) {
+			case "Yesterday":
+				return ShowCompletedRange.Yesterday;
+			case "Last7Days":
+				return ShowCompletedRange.Last7Days;
+			case "LastMonth":
+				return ShowCompletedRange.LastMonth;
+			case "LastYear":
+				return ShowCompletedRange.LastYear;
+			}
+			
+			// If the string doesn't match for some reason just return the
+			// default, which is All.
+			return ShowCompletedRange.All;
+		}
+		
+		private string GetTranslatedRangeValue (ShowCompletedRange range)
+		{
+			switch (range) {
+			case ShowCompletedRange.Yesterday:
+				return Catalog.GetString ("Yesterday");
+			case ShowCompletedRange.Last7Days:
+				return Catalog.GetString ("Last 7 Days");
+			case ShowCompletedRange.LastMonth:
+				return Catalog.GetString ("Last Month");
+			case ShowCompletedRange.LastYear:
+				return Catalog.GetString ("Last Year");
+			}
+			
+			return Catalog.GetString ("All");
+		}
+		
+		private void UpdateDateRanges ()
+		{
+			DateTime date = DateTime.MinValue;
+			DateTime today = DateTime.Now;
+			
+			switch (currentRange) {
+			case ShowCompletedRange.Yesterday:
+				date = today.AddDays (-1);
+				date = new DateTime (date.Year, date.Month, date.Day,
+									 0, 0, 0);
+				break;
+			case ShowCompletedRange.Last7Days:
+				date = today.AddDays (-7);
+				date = new DateTime (date.Year, date.Month, date.Day,
+									 0, 0, 0);
+				break;
+			case ShowCompletedRange.LastMonth:
+				date = today.AddMonths (-1);
+				date = new DateTime (date.Year, date.Month, date.Day,
+									 0, 0, 0);
+				break;
+			case ShowCompletedRange.LastYear:
+				date = today.AddYears (-1);
+				date = new DateTime (date.Year, date.Month, date.Day,
+									 0, 0, 0);
+				break;
+			}
+			
+			this.TimeRangeStart = date;
+		}
+	}
+	
+	/// <summary>
+	/// The purpose of this class is to allow the CompletedTaskGroup to show
+	/// completed tasks in reverse order (i.e., most recently completed tasks
+	/// at the top of the list).
+	/// </summary>
+	class CompletedTasksSortModel : Gtk.TreeModelSort
+	{
+		public CompletedTasksSortModel (Gtk.TreeModel childModel)
+			: base (childModel)
+		{
+			SetSortFunc (0, new Gtk.TreeIterCompareFunc (CompareTasksSortFunc));
+			SetSortColumnId (0, Gtk.SortType.Descending);
+		}
+		
+		#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;
+			
+			// Reverse the logic with the '!' so it's in re
+			return (taskA.CompareToByCompletionDate (taskB));
+		}
+		#endregion // Private Methods
+	}
+}

Added: trunk/src/DateButton.cs
==============================================================================
--- (empty file)
+++ trunk/src/DateButton.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,216 @@
+/***************************************************************************
+ *  DateButton.cs
+ *
+ *  Copyright (C) 2005 Novell
+ *  Written by Aaron Bockover (aaron aaronbock net)
+ ****************************************************************************/
+
+/*  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 GLib;
+using Gtk;
+using Tasquer;
+
+namespace Gtk.Extras
+{
+	public class DateButton : ToggleButton
+	{
+		private Window popup;
+		private DateTime date;
+		private Calendar cal;
+		private bool show_time;
+
+		private const uint CURRENT_TIME = 0;
+
+		// FIXME: If this is ever moved to its own library
+		// this reference to Tomboy will obviously have to
+		// be removed.
+		public DateButton(DateTime date_time, bool show_time)
+: base(Utilities.GetPrettyPrintDate (date_time, show_time))
+		{
+			Toggled += OnToggled;
+			popup = null;
+			date = date_time;
+			this.show_time = show_time;
+		}
+
+		private void ShowCalendar()
+		{
+			popup = new Window(WindowType.Popup);
+			popup.Screen = this.Screen;
+
+			Frame frame = new Frame();
+			frame.Shadow = ShadowType.Out;
+			frame.Show();
+
+			popup.Add(frame);
+
+			VBox box = new VBox(false, 0);
+			box.Show();
+			frame.Add(box);
+
+			cal = new Calendar();
+			cal.DisplayOptions = CalendarDisplayOptions.ShowHeading
+			                     | CalendarDisplayOptions.ShowDayNames
+			                     | CalendarDisplayOptions.ShowWeekNumbers;
+
+			cal.KeyPressEvent += OnCalendarKeyPressed;
+			popup.ButtonPressEvent += OnButtonPressed;
+
+			cal.Show();
+
+			Alignment calAlignment = new Alignment(0.0f, 0.0f, 1.0f, 1.0f);
+			calAlignment.Show();
+			calAlignment.SetPadding(4, 4, 4, 4);
+			calAlignment.Add(cal);
+
+			box.PackStart(calAlignment, false, false, 0);
+
+			Requisition req = SizeRequest();
+			int x = 0, y = 0;
+			GdkWindow.GetOrigin(out x, out y);
+			popup.Move(x + Allocation.X, y + Allocation.Y + req.Height + 3);
+			popup.Show();
+			popup.GrabFocus();
+
+			Grab.Add(popup);
+
+			Gdk.GrabStatus grabbed = Gdk.Pointer.Grab(popup.GdkWindow, true,
+			                         Gdk.EventMask.ButtonPressMask
+			                         | Gdk.EventMask.ButtonReleaseMask
+			                         | Gdk.EventMask.PointerMotionMask, null, null, CURRENT_TIME);
+
+			if (grabbed == Gdk.GrabStatus.Success) {
+				grabbed = Gdk.Keyboard.Grab(popup.GdkWindow,
+				                            true, CURRENT_TIME);
+
+				if (grabbed != Gdk.GrabStatus.Success) {
+					Grab.Remove(popup);
+					popup.Destroy();
+					popup = null;
+				}
+			} else {
+				Grab.Remove(popup);
+				popup.Destroy();
+				popup = null;
+			}
+
+			cal.DaySelectedDoubleClick += OnCalendarDaySelected;
+			cal.ButtonPressEvent += OnCalendarButtonPressed;
+
+			cal.Date = date;
+		}
+
+		public void HideCalendar(bool update)
+		{
+			if (popup != null) {
+				Grab.Remove(popup);
+				Gdk.Pointer.Ungrab(CURRENT_TIME);
+				Gdk.Keyboard.Ungrab(CURRENT_TIME);
+
+				popup.Destroy();
+				popup = null;
+			}
+
+			if (update) {
+				date = cal.GetDate();
+				// FIXME: If this is ever moved to its own library
+				// this reference to Tomboy will obviously have to
+				// be removed.
+				Label = Utilities.GetPrettyPrintDate (date, show_time);
+			}
+
+			Active = false;
+		}
+
+		private void OnToggled(object o, EventArgs args)
+		{
+			if (Active) {
+				ShowCalendar();
+			} else {
+				HideCalendar(false);
+			}
+		}
+
+		private void OnButtonPressed(object o, ButtonPressEventArgs args)
+		{
+			if (popup != null)
+				HideCalendar(false);
+		}
+
+		private void OnCalendarDaySelected(object o, EventArgs args)
+		{
+			HideCalendar(true);
+		}
+
+		private void OnCalendarButtonPressed(object o,
+		                                     ButtonPressEventArgs args)
+		{
+			args.RetVal = true;
+		}
+
+		private void OnCalendarKeyPressed(object o, KeyPressEventArgs args)
+		{
+			switch (args.Event.Key) {
+			case Gdk.Key.Escape:
+				HideCalendar(false);
+				break;
+			case Gdk.Key.KP_Enter:
+			case Gdk.Key.ISO_Enter:
+			case Gdk.Key.Key_3270_Enter:
+			case Gdk.Key.Return:
+			case Gdk.Key.space:
+			case Gdk.Key.KP_Space:
+				HideCalendar(true);
+				break;
+			default:
+				break;
+			}
+		}
+
+		public DateTime Date
+		{
+			get {
+				return date;
+			}
+			set {
+				date = value;
+				Label = Utilities.GetPrettyPrintDate (date, show_time);
+			}
+		}
+
+		/// <summary>
+		/// If true, both the date and time will be shown.  If false, the time
+		/// will be omitted.
+		/// </summary>
+		public bool ShowTime
+		{
+			get {
+				return show_time;
+			}
+			set {
+				show_time = value;
+			}
+		}
+	}
+}

Added: trunk/src/Defines.cs.in
==============================================================================
--- (empty file)
+++ trunk/src/Defines.cs.in	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,40 @@
+/***************************************************************************
+ *  Defines.cs.in
+ *
+ *  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;
+
+namespace Tasquer
+{
+	public class Defines
+	{
+		public const string Version		= "@version@";
+		public const string DataDir		= "@datadir@";
+		public const string GnomeLocaleDir	= "@datadir@/locale";
+		public const string SoundDir		= "@datadir@/tasquer/sounds";
+	}
+}

Added: trunk/src/IBackend.cs
==============================================================================
--- (empty file)
+++ trunk/src/IBackend.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,108 @@
+// ITaskBackend.cs created with MonoDevelop
+// User: boyd at 7:02 AMÂ2/11/2008
+
+using System;
+
+namespace Tasquer.Backends
+{
+	public delegate void BackendInitializedHandler ();
+	public delegate void BackendSyncStartedHandler ();
+	public delegate void BackendSyncFinishedHandler ();
+	
+	/// <summary>
+	/// This is the main integration interface for different backends that
+	/// Tasquer can use.
+	/// </summary>
+	public interface IBackend
+	{
+		event BackendInitializedHandler BackendInitialized;
+		event BackendSyncStartedHandler BackendSyncStarted;
+		event BackendSyncFinishedHandler BackendSyncFinished;
+
+		#region Properties
+		/// <value>
+		/// A human-readable name for the backend that will be displayed in the
+		/// preferences dialog to allow the user to select which backend Tasquer
+		/// should use.
+		/// </value>
+		string Name
+		{
+			get;
+		}
+		
+		/// <value>
+		/// All the tasks provided by the backend.
+		/// </value>
+		Gtk.TreeModel Tasks
+		{
+			get;
+		}
+		
+		/// <value>
+		/// This returns all the ICategory items from the backend.
+		/// </value>
+		Gtk.TreeModel Categories
+		{
+			get;
+		}
+		
+		/// <value>
+		/// Indication that the backend has enough information
+		/// (credentials/etc.) to run.  If false, the properties dialog will
+		/// be shown so the user can configure the backend.
+		/// </value>
+		bool Configured
+		{
+			get;
+		}
+		
+		/// <value>
+		/// Inidication that the backend is initialized
+		/// </value>
+		bool Initialized
+		{
+			get;
+		}
+		#endregion // Properties
+		
+		#region Methods
+		/// <summary>
+		/// Create a new task.
+		/// </summary>
+		ITask CreateTask (string taskName, ICategory category);
+
+		/// <summary>
+		/// Deletes the specified task.
+		/// </summary>
+		/// <param name="task">
+		/// A <see cref="ITask"/>
+		/// </param>
+		void DeleteTask (ITask task);
+		
+		/// <summary>
+		/// Refreshes the backend.
+		/// </summary>
+		void Refresh();
+		
+		/// <summary>
+		/// Initializes the backend
+		/// </summary>
+		void Initialize();
+
+		/// <summary>
+		/// Cleanup the backend before quitting
+		/// </summary>
+		void Cleanup();
+		
+		/// <summary>
+		/// A widget that will be placed into the Preferences Dialog when the
+		/// backend is the one being used.
+		/// </summary>
+		/// <returns>
+		/// A <see cref="Gtk.Widget"/>
+		/// </returns>
+		Gtk.Widget GetPreferencesWidget ();
+
+#endregion // Methods
+	}
+}

Added: trunk/src/ICategory.cs
==============================================================================
--- (empty file)
+++ trunk/src/ICategory.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,20 @@
+// ICategory.cs created with MonoDevelop
+// User: boyd at 9:04 AMÂ2/11/2008
+//
+// To change standard headers go to Edit->Preferences->Coding->Standard Headers
+//
+
+using System;
+
+namespace Tasquer
+{
+	public interface ICategory
+	{
+		string Name
+		{
+			get;
+		}
+		
+		bool ContainsTask(ITask task);
+	}
+}

Added: trunk/src/INote.cs
==============================================================================
--- (empty file)
+++ trunk/src/INote.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,19 @@
+// ICategory.cs created with MonoDevelop
+// User: boyd at 9:04 AMÂ2/11/2008
+//
+// To change standard headers go to Edit->Preferences->Coding->Standard Headers
+//
+
+using System;
+
+namespace Tasquer
+{
+	public interface INote
+	{
+		string Text
+		{
+			get;
+			set;
+		}
+	}
+}

Added: trunk/src/ITask.cs
==============================================================================
--- (empty file)
+++ trunk/src/ITask.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,176 @@
+// ITask.cs created with MonoDevelop
+// User: boyd at 8:50 PMÂ2/10/2008
+
+using System;
+using System.Collections.Generic;
+
+namespace Tasquer
+{
+	public interface ITask
+	{
+		#region Properties
+		/// <value>
+		/// A Task's Name will be used to show the task in the main list window.
+		/// </value>
+		string Name
+		{
+			get;
+			set;
+		}
+		
+		/// <value>
+		/// A DueDate of DateTime.MinValue indicates that a due date is not set.
+		/// </value>
+		DateTime DueDate
+		{
+			get;
+			set;
+		}
+		
+		/// <value>
+		/// If set to CompletionDate.MinValue, the task has not been completed.
+		/// </value>
+		DateTime CompletionDate
+		{
+			get;
+			set;
+		}
+		
+		/// <value>
+		/// This is a convenience property which should use the CompletionDate
+		/// to determine whether a task is completed.
+		/// </value>
+		bool IsComplete 
+		{
+			get;
+		}
+		
+		/// <value>
+		/// Backends should, by default, set the priority of a task to
+		/// TaskPriority.None.
+		/// </value>
+		TaskPriority Priority
+		{
+			get;
+			set;
+		}
+		
+		/// <value>
+		/// Indicates whether any notes exist in this task.  If a backend does
+		/// not support notes, it should always return false.
+		/// </value>
+		bool HasNotes
+		{
+			get;
+		}
+		
+		/// <value>
+		/// Should be true if the task supports having multiple notes.
+		/// </value>
+		bool SupportsMultipleNotes
+		{
+			get;
+		}
+		
+		/// <value>
+		/// The state of the task.  Note: sreeves' LOVES source code comments
+		/// like these.
+		/// </value>
+		TaskState State
+		{
+			get;
+		}
+		
+		/// <value>
+		/// The category to which this task belongs
+		/// </value>
+		ICategory Category
+		{
+			get; 
+			set;
+		}
+		
+		/// <value>
+		/// The notes associated with this task
+		/// </value>
+		List<INote> Notes
+		{
+			get;
+		}
+		
+		/// <value>
+		/// The ID of the timer used to complete a task after being marked
+		/// inactive.
+		/// </value>
+		uint TimerID
+		{
+			get;
+			set;
+		}	
+
+		#endregion // Properties
+		
+		#region Methods
+		
+		/// <summary>
+		/// Activate (Reopen) a task that's Inactivated or Completed.
+		/// </summary>
+		void Activate ();
+		
+		/// <summary>
+		/// Inactivate a task (this is the "limbo" mode).
+		/// </summary>
+		void Inactivate ();
+		
+		/// <summary>
+		/// Mark a task as completed.
+		/// </summary>
+		void Complete ();
+		
+		/// <summary>
+		/// Delete a task from the backend.
+		/// </summary>
+		void Delete ();
+		
+		/// <summary>
+		/// Creates a new note on this task
+		/// </summary>
+		INote CreateNote(string text);
+		
+		/// <summary>
+		/// Removes a note from this task
+		/// </summary>
+		void DeleteNote(INote note);
+		
+		/// <summary>
+		/// Updates an exising note on the task
+		/// </summary>
+		void SaveNote(INote note);
+		
+		/// <summary>
+		/// This is used for sorting tasks in the TaskWindow and should compare
+		/// based on due date.
+		/// </summary>
+		/// <param name="task">
+		/// A <see cref="ITask"/>
+		/// </param>
+		/// <returns>
+		/// A <see cref="System.Int32"/>
+		/// </returns>
+		int CompareTo (ITask task);
+		
+		/// <summary>
+		/// This is the same as CompareTo above but should use completion date
+		/// instead of due date.  This is used to sort items in the
+		/// CompletedTaskGroup.
+		/// </summary>
+		/// <param name="task">
+		/// A <see cref="ITask"/>
+		/// </param>
+		/// <returns>
+		/// A <see cref="System.Int32"/>
+		/// </returns>
+		int CompareToByCompletionDate (ITask task);
+		#endregion // Methods
+	}
+}

Added: trunk/src/Logger.cs
==============================================================================
--- (empty file)
+++ trunk/src/Logger.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,163 @@
+/***************************************************************************
+ *  Logger.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.IO;
+
+namespace Tasquer
+{
+	public enum LogLevel { Debug, Info, Warn, Error, Fatal };
+	
+	public interface ILogger
+	{
+		void Log (LogLevel lvl, string message, params object[] args);
+	}
+	
+	class NullLogger : ILogger
+	{
+		public void Log (LogLevel lvl, string msg, params object[] args)
+		{
+		}
+	}
+
+	class ConsoleLogger : ILogger
+	{
+		public void Log (LogLevel lvl, string msg, params object[] args)
+		{
+			msg = string.Format ("[{0}]: {1}", Enum.GetName (typeof (LogLevel), lvl), msg);
+			Console.WriteLine (msg, args);
+		}
+	}
+
+	class FileLogger : ILogger
+	{
+		StreamWriter log;
+		ConsoleLogger console;
+
+		public FileLogger ()
+		{
+			try {
+				log = File.CreateText (Path.Combine (
+					Environment.GetEnvironmentVariable ("HOME"), 
+					".banter.log"));
+				log.Flush ();
+			} catch (IOException) {
+				// FIXME: Use temp file
+			}
+
+			console = new ConsoleLogger ();
+		}
+
+		~FileLogger ()
+		{
+			if (log != null)
+				log.Flush ();
+		}
+
+		public void Log (LogLevel lvl, string msg, params object[] args)
+		{
+			console.Log (lvl, msg, args);
+
+			if (log != null) {
+				msg = string.Format ("{0} [{1}]: {2}", 
+						     DateTime.Now.ToString(), 
+						     Enum.GetName (typeof (LogLevel), lvl), 
+						     msg);
+				log.WriteLine (msg, args);
+				log.Flush();
+			}
+		}
+	}
+
+	// <summary>
+	// This class provides a generic logging facility. By default all
+	// information is written to standard out and a log file, but other 
+	// loggers are pluggable.
+	// </summary>
+	public static class Logger
+	{
+		private static LogLevel logLevel = LogLevel.Debug;
+
+		static ILogger logDev = new FileLogger ();
+
+		static bool muted = false;
+
+		public static LogLevel LogLevel
+		{
+			get { return logLevel; }
+			set { logLevel = value; }
+		}
+
+		public static ILogger LogDevice
+		{
+			get { return logDev; }
+			set { logDev = value; }
+		}
+
+		public static void Debug (string msg, params object[] args)
+		{
+			Log (LogLevel.Debug, msg, args);
+		}
+
+		public static void Info (string msg, params object[] args)
+		{
+			Log (LogLevel.Info, msg, args);
+		}
+
+		public static void Warn (string msg, params object[] args)
+		{
+			Log (LogLevel.Warn, msg, args);
+		}
+
+		public static void Error (string msg, params object[] args)
+		{
+			Log (LogLevel.Error, msg, args);
+		}
+
+		public static void Fatal (string msg, params object[] args)
+		{
+			Log (LogLevel.Fatal, msg, args);
+		}
+
+		public static void Log (LogLevel lvl, string msg, params object[] args)
+		{
+			if (!muted && lvl >= logLevel)
+				logDev.Log (lvl, msg, args);
+		}
+
+		public static void Mute ()
+		{
+			muted = true;
+		}
+
+		public static void Unmute ()
+		{
+			muted = false;
+		}
+	}
+}

Added: trunk/src/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/src/Makefile.am	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,178 @@
+CSC = gmcs
+
+TARGET = Tasquer.exe
+WRAPPER = tasquer
+
+if ENABLE_DEBUG
+CSFLAGS =  -noconfig -codepage:utf8 -warn:4 -debug -d:DEBUG
+endif
+if ENABLE_RELEASE
+CSFLAGS =  -noconfig -codepage:utf8 -warn:4
+endif
+
+if ENABLE_NOTIFY_SHARP
+NOTIFY_SHARP_CSFLAGS = -define:ENABLE_NOTIFY_SHARP
+endif
+
+if ENABLE_BACKEND_DUMMY
+DUMMY_CSFILES = \
+	$(srcdir)/Backends/Dummy/*.cs
+else
+DUMMY_CSFILES =
+endif
+
+if ENABLE_BACKEND_RTM
+RTM_CSFILES = \
+	$(srcdir)/Backends/Rtm/*.cs 
+else
+RTM_CSFILES =
+endif
+
+if ENABLE_BACKEND_ICECORE
+ICECORE_CSFILES = \
+	$(srcdir)/Backends/IceCore/*.cs
+else
+ICECORE_CSFILES =
+endif
+
+
+if ENABLE_BACKEND_SQLITE
+SQLITE_CSFILES = \
+	$(srcdir)/Backends/Sqlite/*.cs
+SQLITE_LIBS = -r:Mono.Data.Sqlite
+else
+SQLITE_CSFILES =
+SQLITE_LIBS = 
+endif
+
+if ENABLE_BACKEND_EDS
+EDS_CSFILES = \
+       $(srcdir)/Backends/EDS/*.cs
+else
+EDS_CSFILES =
+endif
+
+CSFILES = \
+	$(srcdir)/AbstractTask.cs \
+	$(srcdir)/AllCategory.cs \
+	$(srcdir)/Application.cs \
+	$(srcdir)/CellRendererDate.cs \
+	$(srcdir)/CompletedTaskGroup.cs \
+	$(srcdir)/DateButton.cs \
+	$(srcdir)/IBackend.cs \
+	$(srcdir)/ICategory.cs \
+	$(srcdir)/ITask.cs \
+	$(srcdir)/INote.cs \
+	$(srcdir)/Logger.cs \
+	$(srcdir)/NoteDialog.cs \
+	$(srcdir)/NoteWidget.cs \
+	$(srcdir)/Preferences.cs \
+	$(srcdir)/PreferencesDialog.cs \
+	$(srcdir)/RemoteControl.cs \
+	$(srcdir)/RemoteControlProxy.cs \
+	$(srcdir)/TaskCalendar.cs \
+	$(srcdir)/TaskGroup.cs \
+	$(srcdir)/TaskPriority.cs \
+	$(srcdir)/TaskState.cs \
+	$(srcdir)/TaskWindow.cs \
+	$(srcdir)/TaskTreeView.cs \
+	$(srcdir)/TrayLib.cs \
+	$(srcdir)/Utilities.cs \
+	\
+	$(DUMMY_CSFILES) \
+	\
+	$(RTM_CSFILES) \
+	\
+	$(ICECORE_CSFILES) \
+	\
+	$(SQLITE_CSFILES) \
+	\
+	$(EDS_CSFILES)
+
+RESOURCES = \
+	-resource:$(top_srcdir)/data/images/tasquer-16.png \
+	-resource:$(top_srcdir)/data/images/tasquer-22.png \
+	-resource:$(top_srcdir)/data/images/tasquer-24.png \
+	-resource:$(top_srcdir)/data/images/tasquer-32.png \
+	-resource:$(top_srcdir)/data/images/tasquer-48.png \
+	-resource:$(top_srcdir)/data/images/note-16.png,note.png \
+	-resource:$(top_srcdir)/data/images/rtmLogo.png \
+	-resource:$(top_srcdir)/data/images/clock-16-0.png \
+	-resource:$(top_srcdir)/data/images/clock-16-1.png \
+	-resource:$(top_srcdir)/data/images/clock-16-2.png \
+	-resource:$(top_srcdir)/data/images/clock-16-3.png \
+	-resource:$(top_srcdir)/data/images/clock-16-4.png \
+	-resource:$(top_srcdir)/data/images/clock-16-5.png \
+	-resource:$(top_srcdir)/data/images/clock-16-6.png \
+	-resource:$(top_srcdir)/data/images/clock-16-7.png \
+	-resource:$(top_srcdir)/data/images/clock-16-8.png \
+	-resource:$(top_srcdir)/data/images/clock-16-9.png \
+	-resource:$(top_srcdir)/data/images/clock-16-10.png \
+	-resource:$(top_srcdir)/data/images/clock-16-11.png
+
+ASSEMBLIES =  \
+	-r:System \
+	-r:Mono.Posix \
+	-r:System.Xml \
+	-r:$(top_builddir)/RtmNet/RtmNet \
+	$(GLIB_SHARP_20_LIBS) \
+	$(GNOME_SHARP_20_LIBS) \
+	$(GTK_DOTNET_20_LIBS) \
+	$(NOTIFY_SHARP_LIBS) \
+	$(NDESK_DBUS_10_LIBS) \
+	$(NDESK_DBUS_GLIB_10_LIBS) \
+	$(ICE_DESKTOP_LIBS) \
+	$(SQLITE_LIBS) \
+	$(EVOLUTION_SHARP_LIBS)
+
+$(TARGET): $(CSFILES) Defines.cs
+	$(CSC) -unsafe -out:$@ $(CSFLAGS) $(NOTIFY_SHARP_CSFLAGS) $(CSC_DEFINES) $^ $(ASSEMBLIES) $(RESOURCES)
+
+tasquerlibdir = $(prefix)/lib/tasquer
+tasquerlib_DATA = $(TARGET)	
+
+bin_SCRIPTS = $(WRAPPER)
+
+$(WRAPPER): $(srcdir)/$(WRAPPER).in Makefile
+	sed -e "s|\ prefix\@|$(prefix)|g"               \
+		-e "s|\ exec_prefix\@|$(bindir)|g"			\
+		-e "s|\ libdir\@|$(libdir)|g"				\
+		-e "s|\ pkglibdir\@|$(pkglibdir)|g"         \
+		-e "s|\ bindir\@|$(bindir)|g"               \
+		-e "s|\ target\@|$(TARGET)|g"               \
+		-e "s|\ wrapper\@|$(WRAPPER)|g"             \
+		-e "s|\ srcdir\@|$(PWD)|g"  \
+		< $< > $@
+	chmod +x $(WRAPPER)
+
+Defines.cs: $(srcdir)/Defines.cs.in Makefile
+	sed -e "s|\ version\@|$(VERSION)|"     \
+		-e "s|\ datadir\@|$(datadir)|"     \
+		-e "s|\ pkglibdir\@|$(pkglibdir)|" \
+		< $< > $@
+
+EXTRA_DIST = \
+	$(CSFILES)					\
+	$(WRAPPER).in				\
+	$(srcdir)/Defines.cs.in		\
+	$(srcdir)/tasquer.pc.in		\
+	$(srcdir)/Backends/Dummy/*.cs	\
+	$(srcdir)/Backends/EDS/*.cs	\
+	$(srcdir)/Backends/IceCore/*.cs \
+	$(srcdir)/Backends/Rtm/*.cs	\
+	$(srcdir)/Backends/Sqlite/*.cs
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = tasquer.pc
+
+CLEANFILES = \
+	$(TARGET)					\
+	$(TARGET).mdb				\
+	$(WRAPPER)					\
+	Defines.cs
+
+DISTCLEANFILES =                        \
+        $(WRAPPER)			\
+		$(TARGET)			\
+		$(TARGET).mdb		\
+		Defines.cs			

Added: trunk/src/NoteDialog.cs
==============================================================================
--- (empty file)
+++ trunk/src/NoteDialog.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,141 @@
+// NoteDialog.cs created with MonoDevelop
+// User: boyd at 5:24 PMÂ2/13/2008
+
+using System;
+using System.Collections.Generic;
+using Mono.Unix;
+
+namespace Tasquer
+{
+	public class NoteDialog : Gtk.Dialog
+	{
+		private ITask task;
+		
+		Gtk.VBox targetVBox;
+		
+		#region Constructors
+		public NoteDialog (Gtk.Window parentWindow, ITask task)
+			: base ()
+		{
+			this.ParentWindow = parentWindow.GdkWindow;
+			this.task = task;
+			this.Title = String.Format(Catalog.GetString("Notes for: {0:s}"), task.Name);
+			this.HasSeparator = false;
+			this.SetSizeRequest(350,320);
+			this.Icon = Utilities.GetIcon ("tasquer-48", 48);
+			//this.Flags = Gtk.DialogFlags.DestroyWithParent;
+			
+			Gtk.ScrolledWindow sw = new Gtk.ScrolledWindow ();
+			sw.VscrollbarPolicy = Gtk.PolicyType.Automatic;
+			sw.HscrollbarPolicy = Gtk.PolicyType.Never;
+
+			sw.BorderWidth = 0;
+			sw.CanFocus = true;
+			sw.Show ();
+			
+			Gtk.EventBox innerEb = new Gtk.EventBox();
+			innerEb.BorderWidth = 0;
+			innerEb.ModifyBg (Gtk.StateType.Normal, 
+						new Gdk.Color(255,255,255));
+			innerEb.ModifyBase (Gtk.StateType.Normal, 
+						new Gdk.Color(255,255,255));
+
+			targetVBox = new Gtk.VBox();
+			targetVBox.BorderWidth = 5;
+			targetVBox.Show ();
+			innerEb.Add(targetVBox);
+			innerEb.Show ();
+			
+			if(task.Notes != null) {
+				foreach (INote note in task.Notes) {
+					NoteWidget noteWidget = new NoteWidget (note);
+					noteWidget.TextChanged += OnNoteTextChanged;
+					noteWidget.DeleteButtonClicked += OnDeleteButtonClicked;
+					noteWidget.Show ();
+					targetVBox.PackStart (noteWidget, false, false, 0);
+				}
+			}
+			
+			sw.AddWithViewport(innerEb);
+			sw.Show ();
+			
+			VBox.PackStart (sw, true, true, 0);
+
+			if(task.SupportsMultipleNotes) {
+				Gtk.Button button = new Gtk.Button(Gtk.Stock.Add);
+				button.Show();
+				this.ActionArea.PackStart(button);
+				button.Clicked += OnAddButtonClicked;
+			}
+			
+			AddButton (Gtk.Stock.Close, Gtk.ResponseType.Close);
+					
+			Response += delegate (object sender, Gtk.ResponseArgs args) {
+				// Hide the window.  The TaskWindow watches for when the
+				// dialog is hidden and will take care of the rest.
+				Hide ();
+			};
+		}
+		#endregion // Constructors
+		
+		#region Properties
+		public ITask Task
+		{
+			get { return task; }
+		}
+		#endregion // Properties
+		
+		#region Private Methods
+		#endregion // PrivateMethods
+		
+		#region Event Handlers
+		void OnAddButtonClicked (object sender, EventArgs args)
+		{
+			Logger.Debug("Add button clicked in dialog");
+			NoteWidget noteWidget = new NoteWidget (null);
+			noteWidget.TextChanged += OnNoteTextChanged;
+			noteWidget.DeleteButtonClicked += OnDeleteButtonClicked;
+			noteWidget.Show ();
+			targetVBox.PackStart (noteWidget, false, false, 0);
+			
+			// TODO: Implement NoteDialog.OnAddButtonClicked
+		}
+
+		
+		void OnDeleteButtonClicked (object sender, EventArgs args)
+		{
+			NoteWidget nWidget = sender as NoteWidget;
+			try {
+				task.DeleteNote(nWidget.Note);
+				targetVBox.Remove (nWidget);
+			} catch(Exception e) {
+				Logger.Debug("Unable to delete the note");
+				Logger.Debug(e.ToString());
+			}
+		}
+		
+		void OnNoteTextChanged (object sender, EventArgs args)
+		{
+			NoteWidget nWidget = sender as NoteWidget;
+
+			// if null, add a note, else, modify it
+			if(nWidget.Note == null) {
+				try {
+					INote note = task.CreateNote(nWidget.Text);
+					nWidget.Note = note;
+				} catch(Exception e) {
+					Logger.Debug("Unable to create a note");
+					Logger.Debug(e.ToString());
+				}
+			} else {
+				try {
+					task.SaveNote(nWidget.Note);
+				} catch(Exception e) {
+					Logger.Debug("Unable to save note");
+					Logger.Debug(e.ToString());
+				}
+			}
+		}
+		#endregion // Event Handlers
+	}
+}

Added: trunk/src/NoteWidget.cs
==============================================================================
--- (empty file)
+++ trunk/src/NoteWidget.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,288 @@
+// NoteWidget.cs created with MonoDevelop
+// User: boyd at 5:28 PMÂ2/13/2008
+
+using System;
+using Mono.Unix;
+
+namespace Tasquer
+{
+	public class NoteWidget : Gtk.Notebook
+	{
+		private INote note;
+		private string text;
+		
+		private Gtk.Widget viewPage;
+		private Gtk.Widget editPage;
+		
+		private int viewPageId;
+		private int editPageId;
+		
+		//
+		// View Page Items
+		//
+		private Gtk.Label textLabel;
+		private Gtk.Button deleteButton;
+		private Gtk.Button editButton;
+		
+		//
+		// Edit Page Items
+		//
+		private Gtk.TextView textView;
+		private Gtk.Button cancelButton;
+		private Gtk.Button saveButton;
+				
+		#region Constructors
+		public NoteWidget (INote note)
+		{
+			this.note = note;
+			this.text = ( (note == null) || (note.Text == null) ) ? string.Empty : note.Text.Trim ();
+			
+			this.ShowTabs = false;
+			
+			viewPage = MakeViewPage ();
+			editPage = MakeEditPage ();
+			
+			// The label below does not need to be translated because it's
+			// for debugging purposes only.
+			viewPageId = AppendPage (viewPage, new Gtk.Label ("View"));
+			
+			// The label below does not need to be translated because it's
+			// for debugging purposes only.
+			editPageId = AppendPage (editPage, new Gtk.Label ("Edit"));
+			
+			if (text == null || text == string.Empty) {
+				// Go into edit mode (switch to the edit page)
+				ShowPage (editPageId);
+			} else {
+				// Go to view mode (switch to the view page)
+				ShowPage (viewPageId);
+			}
+		}
+		#endregion // Constructors
+		
+		#region Events
+		public event EventHandler TextChanged;
+		public event EventHandler DeleteButtonClicked;
+		#endregion // Events
+		
+		#region Properties
+		public INote Note
+		{
+			get { return note; }
+			set { 
+				note = value;
+				Text = value.Text;
+			}
+		}
+		
+		public string Text
+		{
+			get {
+				return text;
+			}
+			set {
+				text = value == null ? string.Empty : value.Trim ();
+				
+				if (Page == this.viewPageId) {
+					textLabel.Text = GLib.Markup.EscapeText (text);
+				} else {
+					textView.Buffer.Text = text;
+				}
+			}
+		}
+		#endregion // Properties
+		
+		#region Public Methods
+		#endregion // Public Methods
+		
+		#region Private Methods
+		private Gtk.Widget MakeViewPage ()
+		{
+			Gtk.VBox vbox = new Gtk.VBox (false, 0);
+			vbox.BorderWidth = 6;
+			
+			textLabel = new Gtk.Label ();
+			textLabel.Xalign = 0;
+			textLabel.UseUnderline = false;
+			textLabel.Justify = Gtk.Justification.Left;
+			textLabel.Wrap = true;
+			textLabel.Text = GLib.Markup.EscapeText (text);
+			textLabel.Show ();
+			vbox.PackStart (textLabel, true, true, 0);
+			
+			Gtk.HButtonBox hButtonBox = new Gtk.HButtonBox ();
+			hButtonBox.Layout = Gtk.ButtonBoxStyle.End;
+			
+			deleteButton = new Gtk.Button (Gtk.Stock.Delete);
+			deleteButton.Clicked += OnDeleteButtonClicked;
+			deleteButton.Show ();
+			hButtonBox.PackStart (deleteButton, false, false, 0);
+			
+			editButton = new Gtk.Button (Gtk.Stock.Edit);
+			editButton.Clicked += OnEditButtonClicked;
+			editButton.Show ();
+			hButtonBox.PackStart (editButton, false, false, 0);
+			
+			hButtonBox.Show ();
+			vbox.PackStart (hButtonBox, false, false, 0);
+			
+			vbox.Show ();
+			return vbox;
+		}
+		
+		private Gtk.Widget MakeEditPage ()
+		{
+			Gtk.VBox vbox = new Gtk.VBox (false, 0);
+			vbox.BorderWidth = 6;
+			
+			Gtk.ScrolledWindow sw = new Gtk.ScrolledWindow ();
+			sw.ShadowType = Gtk.ShadowType.EtchedIn;
+			sw.HscrollbarPolicy = Gtk.PolicyType.Automatic;
+			sw.VscrollbarPolicy = Gtk.PolicyType.Automatic;
+			
+			textView = new Gtk.TextView ();
+			textView.WrapMode = Gtk.WrapMode.Word;
+			textView.Editable = true;
+			textView.Buffer.Text = text;
+			textView.CanFocus = true;
+			textView.NoShowAll = true;
+			sw.Add (textView);
+			sw.Show ();
+			vbox.PackStart (sw, true, true, 0);
+			
+			Gtk.HButtonBox hButtonBox = new Gtk.HButtonBox ();
+			hButtonBox.Layout = Gtk.ButtonBoxStyle.End;
+			
+			cancelButton = new Gtk.Button (Gtk.Stock.Cancel);
+			cancelButton.Clicked += OnCancelButtonClicked;
+			cancelButton.NoShowAll = true;
+			hButtonBox.PackStart (cancelButton, false, false, 0);
+			
+			saveButton = new Gtk.Button (Gtk.Stock.Save);
+			saveButton.Clicked += OnSaveButtonClicked;
+			saveButton.NoShowAll = true;
+			hButtonBox.PackStart (saveButton, false, false, 0);
+			
+			hButtonBox.Show ();
+			vbox.PackStart (hButtonBox, false, false, 6);
+			
+			vbox.Show ();
+			return vbox;
+		}
+		
+		/// <summary>
+		/// This is a custom method to show and hide the different notebook
+		/// pages.  The reason for this is to specifically be able to hide/show
+		/// each notebook's widgets properly so that the notebook can shrink
+		/// and grow without taking up too much space in the window.
+		/// </summary>
+		/// <param name="pageNum">
+		/// A <see cref="System.Int32"/>
+		/// </param>
+		void ShowPage (int pageNum)
+		{
+			if (Page == pageNum) {
+				// We're already on that page, so do nothing
+				return;
+			}
+			
+			if (pageNum == viewPageId) {
+				ShowViewPage ();
+			} else {
+				ShowEditPage ();
+			}
+		}
+		
+		void ShowViewPage ()
+		{
+			// Hide all the widgets on the edit page
+			textView.Hide ();
+			cancelButton.Hide ();
+			saveButton.Hide ();
+
+			// Show all the widgets on the view page
+			textLabel.Show ();
+			deleteButton.Show ();
+			editButton.Show ();
+			
+			// Switch back to the View Page
+			Page = viewPageId;
+		}
+		
+		void ShowEditPage ()
+		{
+			// Set the initial text
+			textView.Buffer.Text = text;
+			
+			// Hide all the widgets on the view page
+			textLabel.Hide ();
+			deleteButton.Hide ();
+			editButton.Hide ();
+
+			// Show all the widgets on the edit page
+			textView.Show ();
+			cancelButton.Show ();
+			saveButton.Show ();
+			
+			// TODO: Grab the keyboard focus so the cursor is in the textView.
+//			Gtk.Widget aParent = this.Parent;
+//			while (aParent != null) {
+//				// Get our parent Gtk.Window
+//				if (aParent is Gtk.Dialog) {
+//					(aParent as Gtk.Dialog).Focus = textView;
+//					break;
+//				}
+//				
+//				aParent = aParent.Parent;
+//			}
+			
+			// Switch to the Edit Page
+			Page = editPageId;
+		}
+		#endregion // Private Methods
+		
+		#region Event Handlers
+		private void OnDeleteButtonClicked (object sender, EventArgs args)
+		{
+			if (this.DeleteButtonClicked == null)
+				return;
+			
+			try {
+				DeleteButtonClicked (this, EventArgs.Empty);
+			} catch (Exception e) {
+				Logger.Warn ("Exception in NoteWidget.DeleteButtonClicked handler: {0}", e.Message);
+			}
+		}
+		
+		private void OnEditButtonClicked (object sender, EventArgs args)
+		{
+			ShowPage (editPageId);
+		}
+		
+		private void OnCancelButtonClicked (object sender, EventArgs args)
+		{
+			ShowPage (viewPageId);
+		}
+		
+		private void OnSaveButtonClicked (object sender, EventArgs args)
+		{
+			// Update the text
+			text = textView.Buffer.Text.Trim ();
+			textLabel.Text = GLib.Markup.EscapeText (text);
+			if(note != null)
+				note.Text = text;
+			
+			ShowPage (viewPageId);
+			
+			// Let the event handlers know the note's been changed
+			if (TextChanged != null) {
+				try {
+					TextChanged (this, EventArgs.Empty);
+				} catch (Exception e) {
+					Logger.Debug ("Exception in NoteWidget.TextChanged handler: {0}", e.Message);
+				}
+			}
+		}
+		#endregion // Event Handlers
+	}
+}

Added: trunk/src/Preferences.cs
==============================================================================
--- (empty file)
+++ trunk/src/Preferences.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,290 @@
+/***************************************************************************
+ *  Preferences.cs
+ *
+ *  Copyright (C) 2008 Novell, Inc.
+ *  Written by:
+ *      Calvin Gaisford <calvinrg gmail com>
+ *      Boyd Timothy <btimothy 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.Xml;
+using System.IO;
+
+namespace Tasquer 
+{
+	// <summary>
+	// Class used to store Tasquer preferences
+	// </summary>
+	public class Preferences
+	{
+		private System.Xml.XmlDocument document;
+		private string location;
+		
+		public static string AuthTokenKey = "AuthToken";
+		public static string CurrentBackend = "CurrentBackend";
+		public static string InactivateTimeoutKey = "InactivateTimeout";
+		public static string SelectedCategoryKey = "SelectedCategory";
+		
+		/// <summary>
+		/// A list of category names to show in the TaskWindow when the "All"
+		/// category is selected.
+		/// </summary>
+		public static string ShowInAllCategory = "ShowInAllCategory";
+		public static string ShowCompletedTasksKey = "ShowCompletedTasks";
+		public static string UserNameKey = "UserName";
+		public static string UserIdKey = "UserID";
+		
+		/// <summary>
+		/// This setting allows a user to specify how many completed tasks to
+		/// show in the Completed Tasks Category.  The setting should be one of:
+		/// "Yesterday", "Last7Days", "LastMonth", "LastYear", or "All".
+		/// </summary>
+		/// <param name="settingKey">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <returns>
+		/// A <see cref="System.String"/>
+		/// </returns>
+		public static string CompletedTasksRange = "CompletedTasksRange";
+		
+		public delegate void SettingChangedHandler (Preferences preferences,
+													string settingKey);
+		public event SettingChangedHandler SettingChanged;
+		
+		public string Get (string settingKey)
+		{
+			if (settingKey == null || settingKey.Trim () == string.Empty)
+				throw new ArgumentNullException ("settingKey", "Preferences.Get() called with a null/empty settingKey");
+			
+			string xPath = string.Format ("//{0}", settingKey.Trim ());
+			XmlNode node = document.SelectSingleNode (xPath);
+			if (node == null || node is XmlElement == false)
+				return null;
+			
+			XmlElement element = node as XmlElement;
+			if( (element == null) || (element.InnerText.Length < 1) )
+				return null;
+			else
+				return element.InnerText;
+		}
+		
+		public void Set (string settingKey, string settingValue)
+		{
+			if (settingKey == null || settingKey.Trim () == string.Empty)
+				throw new ArgumentNullException ("settingKey", "Preferences.Set() called with a null/empty settingKey");
+			
+			string xPath = string.Format ("//{0}", settingKey.Trim ());
+			XmlNode node = document.SelectSingleNode (xPath);
+			XmlElement element = null;
+			if (node != null && node is XmlElement)
+				element = node as XmlElement;
+			
+			if (element == null) {
+				element = document.CreateElement(settingKey);
+				document.DocumentElement.AppendChild (element);
+			}
+			
+			if (settingValue == null)
+				element.InnerText = string.Empty;
+			else
+				element.InnerText = settingValue;
+			
+			SavePrefs();
+			
+			NotifyHandlersOfSettingChange (settingKey.Trim ());
+		}
+		
+		public int GetInt (string settingKey)
+		{
+			string val = Get (settingKey);
+			if (val == null)
+				return -1;
+			
+			return int.Parse (val);
+		}
+		
+		public void SetInt (string settingKey, int settingValue)
+		{
+			Set (settingKey, string.Format ("{0}", settingValue));
+		}
+		
+		public bool GetBool (string settingKey)
+		{
+			string val = Get (settingKey);
+			if (val == null)
+				return false;
+			
+			return bool.Parse (val);
+		}
+		
+		public void SetBool (string settingKey, bool settingValue)
+		{
+			Set (settingKey, settingValue.ToString ());
+		}
+		
+		public List<string> GetStringList (string settingKey)
+		{
+			if (settingKey == null || settingKey.Trim () == string.Empty)
+				throw new ArgumentNullException ("settingKey", "Preferences.GetStringList() called with a null/empty settingKey");
+			
+			List<string> stringList = new List<string> ();
+			
+			// Select all nodes whose parent is the settingKey
+			string xPath = string.Format ("//{0}/*", settingKey.Trim ());
+			XmlNodeList list = document.SelectNodes (xPath);
+			if (list == null)
+				return stringList;
+			
+			foreach (XmlNode node in list) {
+				if (node.InnerText != null && node.InnerText.Length > 0)
+					stringList.Add (node.InnerText);
+			}
+			
+			return stringList;
+		}
+		
+		public void SetStringList (string settingKey, List<string> stringList)
+		{
+			if (settingKey == null || settingKey.Trim () == string.Empty)
+				throw new ArgumentNullException ("settingKey", "Preferences.SetStringList() called with null/empty settingKey");
+			
+			// Assume that the caller meant to null out an existing list
+			if (stringList == null)
+				stringList = new List<string> ();
+			
+			// Select the specific node
+			string xPath = string.Format ("//{0}", settingKey.Trim ());
+			XmlNode node = document.SelectSingleNode (xPath);
+			XmlElement element = null;
+			if (node != null && node is XmlElement) {
+				element = node as XmlElement;
+				// Clear out any old children
+				if (element.HasChildNodes) {
+					element.RemoveAll ();
+				}
+			}
+			
+			if (element == null) {
+				element = document.CreateElement(settingKey);
+				document.DocumentElement.AppendChild (element);
+			}
+			
+			foreach (string listItem in stringList) {
+				XmlElement child = document.CreateElement ("list-item");
+				child.InnerText = listItem;
+				element.AppendChild (child);
+			}
+			
+			SavePrefs();
+			
+			NotifyHandlersOfSettingChange (settingKey.Trim ());
+		}
+
+		public Preferences ()
+		{
+			document = new XmlDocument();
+			location = Path.Combine(Environment.GetFolderPath(
+			Environment.SpecialFolder.ApplicationData), "tasquer/preferences");
+			if(!File.Exists(location)) {
+				CreateDefaultPrefs();
+			} else {
+				document.Load(location);
+			}
+			
+			ValidatePrefs ();
+		}
+		
+		/// <summary>
+		/// Validate existing preferences just in case we're running on a
+		/// machine that already has an existing file without having the
+		/// settings specified here.
+		/// </summary>
+		private void ValidatePrefs ()
+		{
+			if (GetInt (Preferences.InactivateTimeoutKey) <= 0)
+				SetInt (Preferences.InactivateTimeoutKey, 5);
+		}
+
+
+		private void SavePrefs()
+		{
+			XmlTextWriter writer = new XmlTextWriter(location, System.Text.Encoding.UTF8);
+			writer.Formatting = Formatting.Indented;
+			document.WriteTo( writer );
+			writer.Flush();
+			writer.Close();
+		}
+
+
+		private void CreateDefaultPrefs()
+		{
+			try {
+				Directory.CreateDirectory(Path.GetDirectoryName(location));
+
+       			document.LoadXml(
+       				"<tasquerprefs></tasquerprefs>");
+				SavePrefs();
+/* 
+		       // Create a new element node.
+		       XmlNode newElem = doc.CreateNode("element", "pages", "");  
+		       newElem.InnerText = "290";
+		     
+		       Console.WriteLine("Add the new element to the document...");
+		       XmlElement root = doc.DocumentElement;
+		       root.AppendChild(newElem);
+		     
+		       Console.WriteLine("Display the modified XML document...");
+		       Console.WriteLine(doc.OuterXml);
+*/
+			} catch (Exception e) {
+				Logger.Debug("Exception thrown in Preferences {0}", e);
+				return;
+			}
+
+		}
+		
+		/// <summary>
+		/// Notify all SettingChanged event handlers that the specified
+		/// setting has changed.
+		/// </summary>
+		/// <param name="settingKey">
+		/// A <see cref="System.String"/>.  The setting that changed.
+		/// </param>
+		private void NotifyHandlersOfSettingChange (string settingKey)
+		{
+			// Notify SettingChanged handlers of the change
+			if (SettingChanged != null) {
+				try {
+					SettingChanged (this, settingKey);
+				} catch (Exception e) {
+					Logger.Warn ("Exception calling SettingChangedHandlers for setting '{0}': {1}",
+								 settingKey,
+								 e.Message);
+				}
+			}
+		}
+	}
+}

Added: trunk/src/PreferencesDialog.cs
==============================================================================
--- (empty file)
+++ trunk/src/PreferencesDialog.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,530 @@
+/***************************************************************************
+ *  PreferencesDialog.cs
+ *
+ *  Copyright (C) 2008 Novell, Inc.
+ *  Written by Scott Reeves <sreeves 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 Gtk;
+using Mono.Unix;
+using Tasquer.Backends;
+
+namespace Tasquer
+{
+
+	public class PreferencesDialog : Gtk.Dialog
+	{
+//		private CheckButton		showCompletedTasksCheck;
+		
+		Gtk.Notebook			notebook;
+		
+		//
+		// General Page Widgets
+		//
+		Gtk.Widget				generalPage;
+		int						generalPageId;
+		Gtk.ComboBox			backendComboBox;
+		Dictionary<int, IBackend> backendComboMap; // track backends
+		int 					selectedBackend;
+		Gtk.CheckButton			showCompletedTasksCheckButton;
+		Gtk.TreeModelFilter		filteredCategories;
+		List<string>			categoriesToShow;
+		Gtk.TreeView			categoriesTree;
+		
+		//
+		// Backend Page Widgets
+		//
+		Gtk.Widget				backendPage;
+		int						backendPageId;
+
+		public PreferencesDialog() : base ()
+		{
+			LoadPreferences();
+			Init();
+			ConnectEvents();
+			
+			Shown += OnShown;
+			
+			this.WidthRequest = 400;
+			this.HeightRequest = 350;
+		}
+		
+		protected override void OnResponse (ResponseType response_id)
+		{
+			base.OnResponse (response_id);
+			
+			Hide ();
+		}
+
+
+		private void Init()
+		{
+			Logger.Debug("Called Preferences Init");
+			this.Icon = Utilities.GetIcon ("tasquer-48", 48);
+			// Update the window title
+			this.Title = string.Format ("Tasquer Preferences");	
+			
+			this.VBox.Spacing = 0;
+			this.VBox.BorderWidth = 0;
+			this.Resizable = false;
+		
+			this.AddButton(Stock.Close, Gtk.ResponseType.Ok);
+			this.DefaultResponse = ResponseType.Ok;
+			
+			notebook = new Gtk.Notebook ();
+			notebook.ShowTabs = true;
+			
+			//
+			// General Page
+			//
+			generalPage = MakeGeneralPage ();
+			generalPage.Show ();
+			generalPageId =
+				notebook.AppendPage (generalPage,
+									 new Label (Catalog.GetString ("General")));
+			
+			//
+			// Backend Page
+			//
+			backendPage = null;
+			backendPageId = -1;
+			
+			if (Application.Backend != null) {
+				backendPage = Application.Backend.GetPreferencesWidget ();
+				if (backendPage != null) {
+					backendPage.Show ();
+					Label l =
+						new Label (GLib.Markup.EscapeText (Application.Backend.Name));
+					l.UseMarkup = false;
+					l.UseUnderline = false;
+					l.Show ();
+					backendPageId =
+						notebook.AppendPage (backendPage, l);
+				}
+			}
+			
+			notebook.Show ();
+			this.VBox.PackStart (notebook, true, true, 0);
+
+			/* Available preferences
+			// Preferences
+			// Show completed tasks
+			showCompletedTasksCheck = new CheckButton("Show Completed Tasks");
+			showCompletedTasksCheck.Show();
+			mainVBox.PackStart(showCompletedTasksCheck, false, false, 0);
+			*/
+
+			/*  Available backends
+			label = new Label();
+			label.Show();
+			label.Justify = Gtk.Justification.Left;
+			label.SetAlignment (0.0f, 0.5f);
+			label.LineWrap = false;
+			label.UseMarkup = true;
+			label.UseUnderline = false;
+			label.Markup = "<span weight=\"bold\" size=\"large\">Backend</span>";
+			mainVBox.PackStart(label, false, false, 0);
+
+			// List of available backends
+			string [] backends = new string[] { "Remember the Milk" };
+			ComboBox backendComboBox = new ComboBox(backends);
+			backendComboBox.Active = 0;
+			backendComboBox.Show();
+			mainVBox.PackStart(backendComboBox, false, false, 0);
+			*/
+
+			DeleteEvent += WindowDeleted;
+		}
+		
+		private Gtk.Widget MakeGeneralPage ()
+		{
+			VBox vbox = new VBox (false, 6);
+			vbox.BorderWidth = 10;
+			
+			//
+			// Task Management System
+			//
+			VBox sectionVBox = new VBox (false, 4);
+			Label l = new Label ();
+			l.Markup = string.Format ("<span size=\"large\" weight=\"bold\">{0}</span>",
+									  Catalog.GetString ("Task Management System"));
+			l.UseUnderline = false;
+			l.UseMarkup = true;
+			l.Wrap = false;
+			l.Xalign = 0;
+			
+			l.Show ();
+			sectionVBox.PackStart (l, false, false, 0);
+			
+			backendComboBox = ComboBox.NewText ();
+			backendComboMap = new Dictionary<int,IBackend> ();
+			// Fill out the ComboBox
+			int i = 0;
+			selectedBackend = -1;
+			foreach (IBackend backend in Application.AvailableBackends) {
+				backendComboBox.AppendText (backend.Name);
+				backendComboMap [i] = backend;
+				if (backend == Application.Backend)
+					selectedBackend = i;
+				i++;
+			}
+			if (selectedBackend >= 0)
+				backendComboBox.Active = selectedBackend;
+			backendComboBox.Changed += OnBackendComboBoxChanged;
+			backendComboBox.Show ();
+			
+			HBox hbox = new HBox (false, 6);
+			l = new Label (string.Empty); // spacer
+			l.Show ();
+			hbox.PackStart (l, false, false, 0);
+			hbox.PackStart (backendComboBox, false, false, 0);
+			hbox.Show ();
+			sectionVBox.PackStart (hbox, false, false, 0);
+			sectionVBox.Show ();
+			vbox.PackStart (sectionVBox, false, false, 0);
+			
+			//
+			// Task Filtering
+			//
+			sectionVBox = new VBox (false, 4);
+			l = new Label ();
+			l.Markup = string.Format ("<span size=\"large\" weight=\"bold\">{0}</span>",
+									  Catalog.GetString ("Task Filtering"));
+			l.UseUnderline = false;
+			l.UseMarkup = true;
+			l.Wrap = false;
+			l.Xalign = 0;
+			
+			l.Show ();
+			sectionVBox.PackStart (l, false, false, 0);
+			
+			HBox sectionHBox = new HBox (false, 6);
+			l = new Label (string.Empty); // spacer
+			l.Show ();
+			sectionHBox.PackStart (l, false, false, 0);
+			VBox innerSectionVBox = new VBox (false, 6);
+			hbox = new HBox (false, 6);
+			
+			bool showCompletedTasks = Application.Preferences.GetBool (
+											Preferences.ShowCompletedTasksKey);
+			showCompletedTasksCheckButton =
+				new CheckButton (Catalog.GetString ("Show _completed tasks"));
+			showCompletedTasksCheckButton.UseUnderline = true;
+			showCompletedTasksCheckButton.Active = showCompletedTasks;
+			showCompletedTasksCheckButton.Show ();
+			hbox.PackStart (showCompletedTasksCheckButton, true, true, 0);
+			hbox.Show ();
+			innerSectionVBox.PackStart (hbox, false, false, 0);
+			
+			// Categories TreeView
+			l = new Label (Catalog.GetString ("Only _show these categories when \"All\" is selected:"));
+			l.UseUnderline = true;
+			l.Xalign = 0;
+			l.Show ();
+			innerSectionVBox.PackStart (l, false, false, 0);
+			
+			ScrolledWindow sw = new ScrolledWindow ();
+			sw.HscrollbarPolicy = PolicyType.Automatic;
+			sw.VscrollbarPolicy = PolicyType.Automatic;
+			sw.ShadowType = ShadowType.EtchedIn;
+			
+			categoriesTree = new TreeView ();
+			categoriesTree.Selection.Mode = SelectionMode.None;
+			categoriesTree.RulesHint = false;
+			categoriesTree.HeadersVisible = false;
+			l.MnemonicWidget = categoriesTree;
+			
+			Gtk.TreeViewColumn column = new Gtk.TreeViewColumn ();
+			column.Title = Catalog.GetString ("Category");
+			column.Sizing = Gtk.TreeViewColumnSizing.Autosize;
+			column.Resizable = false;
+			
+			Gtk.CellRendererToggle toggleCr = new CellRendererToggle ();
+			toggleCr.Toggled += OnCategoryToggled;
+			column.PackStart (toggleCr, false);
+			column.SetCellDataFunc (toggleCr,
+						new Gtk.TreeCellDataFunc (ToggleCellDataFunc));
+			
+			Gtk.CellRendererText textCr = new CellRendererText ();
+			column.PackStart (textCr, true);
+			column.SetCellDataFunc (textCr,
+						new Gtk.TreeCellDataFunc (TextCellDataFunc));
+			
+			categoriesTree.AppendColumn (column);
+			
+			categoriesTree.Show ();
+			sw.Add (categoriesTree);
+			sw.Show ();
+			innerSectionVBox.PackStart (sw, false, false, 0);
+			innerSectionVBox.Show ();
+			
+			sectionHBox.PackStart (innerSectionVBox, true, true, 0);
+			sectionHBox.Show ();
+			sectionVBox.PackStart (sectionHBox, true, true, 0);
+			sectionVBox.Show ();
+			vbox.PackStart (sectionVBox, false, false, 0);
+			
+			return vbox;
+		}
+
+		///<summary>
+		///	WindowDeleted
+		/// Cleans up the conversation object with the ConversationManager
+		///</summary>	
+		private void WindowDeleted (object sender, DeleteEventArgs args)
+		{
+			// Save preferences
+
+		}
+
+
+		private void LoadPreferences()
+		{
+			Logger.Debug("Loading preferences");
+			categoriesToShow =
+				Application.Preferences.GetStringList (Preferences.ShowInAllCategory);
+			if (categoriesToShow == null || categoriesToShow.Count == 0)
+				categoriesToShow = BuildNewCategoryList ();
+		}
+
+		private void ConnectEvents()
+		{
+			// showCompletedTasksCheckbox delegate
+			showCompletedTasksCheckButton.Toggled += delegate {
+				Application.Preferences.SetBool (
+					Preferences.ShowCompletedTasksKey,
+					showCompletedTasksCheckButton.Active);
+			};
+		}
+		
+		private void OnBackendComboBoxChanged (object sender, EventArgs args)
+		{
+			if (selectedBackend >= 0) {
+				// TODO: Prompt the user and make sure they really want to change
+				// which backend they are using.
+				
+				// Remove the existing backend's preference page
+				if (backendPageId >= 0) {
+					notebook.RemovePage (backendPageId);
+					backendPageId = -1;
+					backendPage = null;
+				}
+				
+				// if yes (replace backend)
+				if (backendComboMap.ContainsKey (selectedBackend) == true) {
+					// Cleanup old backend
+					IBackend oldBackend = backendComboMap [selectedBackend];
+					Logger.Info ("Cleaning up '{0}'...", oldBackend.Name);
+					try {
+						oldBackend.Cleanup ();
+					} catch (Exception e) {
+						Logger.Warn ("Exception cleaning up '{0}': {2}",
+									 oldBackend.Name,
+									 e.Message);
+						
+					}
+					
+					selectedBackend = -1;
+				}
+			}
+			
+			IBackend newBackend = null;
+			if (backendComboMap.ContainsKey (backendComboBox.Active) == true) {
+				newBackend = backendComboMap [backendComboBox.Active];
+			}
+			
+			// TODO: Set the new backend
+			Application.Backend = newBackend;
+			
+			if (newBackend == null)
+				return;
+			
+			selectedBackend = backendComboBox.Active;
+			
+			// Add a backend prefs page if one exists
+			backendPage = newBackend.GetPreferencesWidget ();
+			if (backendPage != null) {
+				backendPage.Show ();
+				Label l = new Label (GLib.Markup.EscapeText (newBackend.Name));
+				l.UseMarkup = false;
+				l.UseUnderline = false;
+				l.Show ();
+				backendPageId =
+					notebook.AppendPage (backendPage, l);
+				
+				// If the new backend is not configured, automatically switch
+				// to the backend's preferences page
+				if (newBackend.Configured == false)
+					notebook.Page = backendPageId;
+			}
+			
+			// Save the user preference
+			Application.Preferences.Set (Preferences.CurrentBackend,
+										 newBackend.GetType ().ToString ());
+			
+			categoriesToShow = BuildNewCategoryList ();
+			Application.Preferences.SetStringList (Preferences.ShowInAllCategory,
+												   categoriesToShow);
+			RebuildCategoryTree ();
+		}
+		
+		private void ToggleCellDataFunc (Gtk.TreeViewColumn column,
+											 Gtk.CellRenderer cell,
+											 Gtk.TreeModel model,
+											 Gtk.TreeIter iter)
+		{
+			Gtk.CellRendererToggle crt = cell as Gtk.CellRendererToggle;
+			ICategory category = model.GetValue (iter, 0) as ICategory;
+			if (category == null) {
+				crt.Active = false;
+				return;
+			}
+			
+			// If the setting is null or empty, show all categories
+			if (categoriesToShow == null || categoriesToShow.Count == 0) {
+				crt.Active = true;
+				return;
+			}
+			
+			// Check to see if the category is specified in the list
+			if (categoriesToShow.Contains (category.Name) == true) {
+				crt.Active = true;
+				return;
+			}
+			
+			crt.Active = false;
+		}
+		
+		private void TextCellDataFunc (Gtk.TreeViewColumn treeColumn,
+				Gtk.CellRenderer renderer, Gtk.TreeModel model,
+				Gtk.TreeIter iter)
+		{
+			Gtk.CellRendererText crt = renderer as Gtk.CellRendererText;
+			crt.Ellipsize = Pango.EllipsizeMode.End;
+			ICategory category = model.GetValue (iter, 0) as ICategory;
+			if (category == null) {
+				crt.Text = string.Empty;
+				return;
+			}
+			
+			crt.Text = GLib.Markup.EscapeText (category.Name);
+		}
+		
+		void OnCategoryToggled (object sender, Gtk.ToggledArgs args)
+		{
+			Logger.Debug ("OnCategoryToggled");
+			Gtk.TreeIter iter;
+			Gtk.TreePath path = new Gtk.TreePath (args.Path);
+			if (categoriesTree.Model.GetIter (out iter, path) == false)
+				return; // Do nothing
+			
+			ICategory category = categoriesTree.Model.GetValue (iter, 0) as ICategory;
+			if (category == null)
+				return;
+			
+			if (categoriesToShow == null)
+				categoriesToShow = BuildNewCategoryList ();
+			
+			if (categoriesToShow.Contains (category.Name))
+				categoriesToShow.Remove (category.Name);
+			else
+				categoriesToShow.Add (category.Name);
+			
+			Application.Preferences.SetStringList (Preferences.ShowInAllCategory,
+												   categoriesToShow);
+		}
+		
+		/// <summary>
+		/// Build a new category list setting from all the categories
+		/// </summary>
+		/// <param name="?">
+		/// A <see cref="System.String"/>
+		/// </param>
+		List<string> BuildNewCategoryList ()
+		{
+			List<string> list = new List<string> ();
+			TreeModel model;
+			IBackend backend = Application.Backend;
+			if (backend == null)
+				return list;
+			
+			model = backend.Categories;
+			Gtk.TreeIter iter;
+			if (model.GetIterFirst (out iter) == false)
+				return list;
+			
+			do {
+				ICategory cat = model.GetValue (iter, 0) as ICategory;
+				if (cat == null)
+					continue;
+				
+				list.Add (cat.Name);
+			} while (model.IterNext (ref iter) == true);
+			
+			return list;
+		}
+		
+		void RebuildCategoryTree ()
+		{
+			if (backendComboMap.ContainsKey (selectedBackend) == false) {
+				categoriesTree.Model = null;
+				return;
+			}
+			
+			IBackend backend = backendComboMap [selectedBackend];
+			filteredCategories = new TreeModelFilter (backend.Categories, null);
+			filteredCategories.VisibleFunc = FilterFunc;
+			categoriesTree.Model = filteredCategories;
+		}
+		
+		void OnShown (object sender, EventArgs args)
+		{
+			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;
+		}
+	}
+}

Added: trunk/src/RemoteControl.cs
==============================================================================
--- (empty file)
+++ trunk/src/RemoteControl.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,152 @@
+// RemoteControl.cs created with MonoDevelop
+// User: sandy at 9:49 AMÂ2/14/2008
+//
+// To change standard headers go to Edit->Preferences->Coding->Standard Headers
+//
+
+using System;
+using System.Collections.Generic;
+
+using Mono.Unix; // for Catalog.GetString ()
+
+#if ENABLE_NOTIFY_SHARP
+using Notifications;
+#endif
+
+using org.freedesktop.DBus;
+using NDesk.DBus;
+
+namespace Tasquer
+{
+	[Interface ("org.gnome.Tasquer.RemoteControl")]
+	public class RemoteControl : MarshalByRefObject
+	{
+		static Gdk.Pixbuf tasquerIcon;
+		static RemoteControl ()
+		{
+			tasquerIcon = Utilities.GetIcon ("tasquer-48", 48);
+		}
+		
+		public RemoteControl()
+		{
+		}
+		
+		/// <summary>
+		/// Create a new task in Tasquer using the given categoryName and name.
+		/// </summary>
+		/// <param name="categoryName">
+		/// A <see cref="System.String"/>.  The name of an existing category.
+		/// Matches are not case-sensitive.
+		/// </param>
+		/// <param name="taskName">
+		/// A <see cref="System.String"/>.  The name of the task to be created.
+		/// </param>
+		/// <param name="enterEditMode">
+		/// A <see cref="System.Boolean"/>.  Specify true if the TaskWindow
+		/// should be shown, the new task scrolled to, and have it be put into
+		/// edit mode immediately.
+		/// </param>
+		/// <returns>
+		/// A unique <see cref="System.String"/> which can be used to reference
+		/// the task later.
+		/// </returns>
+		public string CreateTask (string categoryName, string taskName,
+								  bool enterEditMode)
+		{
+			Gtk.TreeIter iter;
+			Gtk.TreeModel model = Application.Backend.Categories;
+			
+			//
+			// Validate the input parameters.  Don't allow null or empty strings
+			// be passed-in.
+			//
+			if (categoryName == null || categoryName.Trim () == string.Empty
+					|| taskName == null || taskName.Trim () == string.Empty) {
+				return string.Empty;
+			}
+			
+			//
+			// Look for the specified category
+			//
+			if (model.GetIterFirst (out iter) == false) {
+				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) == true);
+			
+			if (category == null) {
+				return string.Empty;
+			}
+			
+			ITask task = null;
+			try {
+				task = Application.Backend.CreateTask (taskName, category);
+			} catch (Exception e) {
+				Logger.Error ("Exception calling Application.Backend.CreateTask from RemoteControl: {0}", e.Message);
+				return string.Empty;
+			}
+			
+			if (task == null) {
+				return string.Empty;
+			}
+			
+			if (enterEditMode == true) {
+				TaskWindow.SelectAndEdit (task);
+			}
+			
+			#if ENABLE_NOTIFY_SHARP
+			// Use notify-sharp to alert the user that a new task has been
+			// created successfully.
+			Notification notify =
+				new Notification (
+					Catalog.GetString ("New task created."), // summary
+					Catalog.GetString (taskName), // body
+					tasquerIcon);
+			Application.ShowAppNotification (notify);
+			#endif
+			
+			// TODO: Add ITask.Id and return the new Id of the task.
+			//return task.Id;
+			return "TaskIdNotImplementedYet";
+		}
+		
+		/// <summary>
+		/// Return an array of Category names.
+		/// </summary>
+		/// <returns>
+		/// A <see cref="System.String"/>
+		/// </returns>
+		public string[] GetCategoryNames ()
+		{
+			List<string> categories = new List<string> ();
+			string[] emptyArray = categories.ToArray ();
+			
+			Gtk.TreeIter iter;
+			Gtk.TreeModel model = Application.Backend.Categories;
+			
+			if (model.GetIterFirst (out iter) == false)
+				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) == true);
+			
+			return categories.ToArray ();
+		}
+		
+		public void ShowTasks ()
+		{
+			TaskWindow.ShowWindow ();
+		}
+	}
+}

Added: trunk/src/RemoteControlProxy.cs
==============================================================================
--- (empty file)
+++ trunk/src/RemoteControlProxy.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,36 @@
+using System;
+using NDesk.DBus;
+using org.freedesktop.DBus;
+
+namespace Tasquer
+{
+	public static class RemoteControlProxy {
+		private const string Path = "/org/gnome/Tasquer/RemoteControl";
+		private const string Namespace = "org.gnome.Tasquer";
+
+		public static RemoteControl GetInstance () {
+			BusG.Init ();
+
+			if (! Bus.Session.NameHasOwner (Namespace))
+				Bus.Session.StartServiceByName (Namespace);
+
+			return Bus.Session.GetObject<RemoteControl> (Namespace,
+			                new ObjectPath (Path));
+		}
+
+		public static RemoteControl Register () {
+			BusG.Init ();
+
+			RemoteControl remote_control = new RemoteControl ();
+			Bus.Session.Register (Namespace,
+			                      new ObjectPath (Path),
+			                      remote_control);
+
+			if (Bus.Session.RequestName (Namespace)
+			                != RequestNameReply.PrimaryOwner)
+				return null;
+
+			return remote_control;
+		}
+	}
+}
\ No newline at end of file

Added: trunk/src/RtmDeveloperKey.txt
==============================================================================
--- (empty file)
+++ trunk/src/RtmDeveloperKey.txt	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,32 @@
+This is the email received from Remember The Milk to do development.
+
+Hi there,
+
+Thank you for requesting a Remember The Milk API key.
+
+Your api_key is: b29f7517b6584035d07df3170b80c430
+Your shared secret is: 93eb5f83628b2066
+
+For more information on how to utilize your API key, please see the API
+documentation at: http://www.rememberthemilk.com/services/api/
+
+If you have any questions about using the API, please see the developer
+mailing list: http://groups.google.com/group/rememberthemilk-api/
+
+If you're writing a web-based application, you'll most likely want to
+set a callback URL for it. Feel free to provide us with a callback URL
+at your convenience. For more information about callback URLs, please
+see: http://www.rememberthemilk.com/services/api/authentication.rtm
+
+You may also provide us with a logo for your service/application. This
+will be shown to users during the authentication process.
+
+Thanks for your interest in Remember The Milk -- happy hacking.
+
+Regards,
+Emily, Omar, & Bob T. Monkey
+
+Remember The Milk
+http://www.rememberthemilk.com/
+
+

Added: trunk/src/TaskCalendar.cs
==============================================================================
--- (empty file)
+++ trunk/src/TaskCalendar.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,206 @@
+// TaskCalendar.cs created with MonoDevelop
+// User: calvin at 7:20 PMÂ2/13/2008
+//
+// To change standard headers go to Edit->Preferences->Coding->Standard Headers
+//
+
+using System;
+using GLib;
+using Gtk;
+using Tasquer;
+
+namespace Tasquer
+{
+	public class TaskCalendar
+	{
+		private Window popup;
+		private DateTime date;
+		private Calendar cal;
+		private bool show_time;
+		int xPos;
+		int yPos;
+		Gtk.Widget parent;
+		int eventCount;
+
+		private ITask task;
+		
+		private const uint CURRENT_TIME = 0;
+
+		public TaskCalendar(ITask task, Gtk.Widget parent)
+		{
+			this.task = task;
+			
+			// If there's no date set (DateTime.MinValue), load the calendar
+			// with today's date.
+			if (task.DueDate == DateTime.MinValue)
+				date = DateTime.Now;
+			else
+				date = task.DueDate;
+			this.parent = parent;
+			eventCount = 0;
+		}
+
+		public void ShowCalendar()
+		{
+			popup = new Window(WindowType.Popup);
+			popup.Screen = parent.Screen;
+
+			Frame frame = new Frame();
+			frame.Shadow = ShadowType.Out;
+			frame.Show();
+
+			popup.Add(frame);
+
+			VBox box = new VBox(false, 0);
+			box.Show();
+			frame.Add(box);
+
+			cal = new Calendar();
+			cal.DisplayOptions = CalendarDisplayOptions.ShowHeading
+			                     | CalendarDisplayOptions.ShowDayNames
+			                     | CalendarDisplayOptions.ShowWeekNumbers;
+			                     
+			cal.KeyPressEvent += OnCalendarKeyPressed;
+			popup.ButtonPressEvent += OnButtonPressed;
+
+			cal.Show();
+
+			Alignment calAlignment = new Alignment(0.0f, 0.0f, 1.0f, 1.0f);
+			calAlignment.Show();
+			calAlignment.SetPadding(4, 4, 4, 4);
+			calAlignment.Add(cal);
+
+			box.PackStart(calAlignment, false, false, 0);
+
+			//Requisition req = SizeRequest();
+
+			parent.GdkWindow.GetOrigin(out xPos, out yPos);
+//			popup.Move(x + Allocation.X, y + Allocation.Y + req.Height + 3);
+			popup.Move(xPos, yPos);
+			popup.Show();
+			popup.GrabFocus();
+
+			Grab.Add(popup);
+
+			Gdk.GrabStatus grabbed = Gdk.Pointer.Grab(popup.GdkWindow, true,
+			                         Gdk.EventMask.ButtonPressMask
+			                         | Gdk.EventMask.ButtonReleaseMask
+			                         | Gdk.EventMask.PointerMotionMask, null, null, CURRENT_TIME);
+
+			if (grabbed == Gdk.GrabStatus.Success) {
+				grabbed = Gdk.Keyboard.Grab(popup.GdkWindow,
+				                            true, CURRENT_TIME);
+
+				if (grabbed != Gdk.GrabStatus.Success) {
+					Grab.Remove(popup);
+					popup.Destroy();
+					popup = null;
+				}
+			} else {
+				Grab.Remove(popup);
+				popup.Destroy();
+				popup = null;
+			}
+
+			cal.DaySelected += OnCalendarDaySelected;
+			cal.MonthChanged += OnCalendarMonthChanged;
+
+			cal.Date = date;
+		}
+
+		public void HideCalendar(bool update)
+		{
+			if (popup != null) {
+				Grab.Remove(popup);
+				Gdk.Pointer.Ungrab(CURRENT_TIME);
+				Gdk.Keyboard.Ungrab(CURRENT_TIME);
+
+				popup.Destroy();
+				popup = null;
+			}
+
+			if (update) {
+				date = cal.GetDate();
+				// FIXME: If this is ever moved to its own library
+				// this reference to Tomboy will obviously have to
+				// be removed.
+				// Label = Utilities.GetPrettyPrintDate (date, show_time);
+			}
+
+			//Active = false;
+		}
+
+
+		private void OnButtonPressed(object o, ButtonPressEventArgs args)
+		{
+			//Logger.Debug("OnButtonPressed");
+			if (popup != null) {
+				HideCalendar(false);
+			}
+		}
+
+		private void OnCalendarDaySelected(object o, EventArgs args)
+		{
+			eventCount++;
+			if(eventCount == 1) {
+				// this is only a day selection, set the date and exit
+				task.DueDate = cal.GetDate();
+				eventCount = 0;
+				HideCalendar(true);
+			}
+			eventCount = 0;
+			//HideCalendar(true);
+		}
+		
+		private void OnCalendarMonthChanged(object o, EventArgs args)
+		{
+			eventCount++;
+		}			
+
+
+		private void OnCalendarKeyPressed(object o, KeyPressEventArgs args)
+		{
+			//Logger.Debug("OnCalendarKeyPressed");
+			switch (args.Event.Key) {
+			case Gdk.Key.Escape:
+				HideCalendar(false);
+				break;
+			//case Gdk.Key.KP_Enter:
+			//case Gdk.Key.ISO_Enter:
+			//case Gdk.Key.Key_3270_Enter:
+			//case Gdk.Key.Return:
+			//case Gdk.Key.space:
+			//case Gdk.Key.KP_Space:
+			//	HideCalendar(true);
+			//	break;
+			default:
+				break;
+			}
+		}
+
+		public DateTime Date
+		{
+			get {
+				return date;
+			}
+			set {
+				date = value;
+				//Label = Utilities.GetPrettyPrintDate (date, show_time);
+			}
+		}
+
+		/// <summary>
+		/// If true, both the date and time will be shown.  If false, the time
+		/// will be omitted.
+		/// </summary>
+		public bool ShowTime
+		{
+			get {
+				return show_time;
+			}
+			set {
+				show_time = value;
+			}
+		}
+	}
+}
\ No newline at end of file

Added: trunk/src/TaskGroup.cs
==============================================================================
--- (empty file)
+++ trunk/src/TaskGroup.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,492 @@
+// TaskGroup.cs created with MonoDevelop
+// User: boyd at 7:50 PMÂ2/11/2008
+
+using System;
+
+namespace Tasquer
+{
+	/// <summary>
+	/// A TaskGroup is a Widget that represents a grouping of tasks that
+	/// are shown in the TaskWindow.  For example, "Overdue", "Today",
+	/// "Tomorrow", etc.
+	/// </summary>
+	public class TaskGroup : Gtk.VBox
+	{
+		DateTime timeRangeStart;
+		DateTime timeRangeEnd;
+		
+		Gtk.Label header;
+		TaskTreeView treeView;
+		Gtk.TreeModelFilter filteredTasks;
+		Gtk.HBox extraWidgetHBox;
+		Gtk.Widget extraWidget;
+		
+		bool hideWhenEmpty;
+		
+		protected bool showCompletedTasks;
+		
+		#region Constructor
+		public TaskGroup (string groupName, DateTime rangeStart,
+						  DateTime rangeEnd, Gtk.TreeModel tasks)
+		{
+			this.timeRangeStart = rangeStart;
+			this.timeRangeEnd = rangeEnd;
+			
+			hideWhenEmpty = true;
+						
+			showCompletedTasks = 
+				Application.Preferences.GetBool (
+					Preferences.ShowCompletedTasksKey);
+			Application.Preferences.SettingChanged += OnSettingChanged;
+			
+			// TODO: Add a date time event watcher so that when we rollover to
+			// a new day, we can update the rangeStart and rangeEnd times.  The
+			// ranges will be used to determine whether tasks fit into certain
+			// groups in the main TaskWindow.  Reference Tomboy's NoteOfTheDay
+			// add-in for code that reacts on day changes.
+			
+			filteredTasks = new Gtk.TreeModelFilter (tasks, null);
+			filteredTasks.VisibleFunc = FilterTasks;
+			
+			// TODO: Add something to watch events so that the group will
+			// automatically refilter and display/hide itself accordingly.
+			
+			//
+			// Build the UI
+			//
+			
+			//
+			// Group Header
+			//
+//			Gtk.EventBox eb = new Gtk.EventBox();
+//			eb.Show();
+//			eb.BorderWidth = 0;
+//			eb.ModifyBg(Gtk.StateType.Normal, new Gdk.Color(211,215,199));
+//			eb.ModifyBase(Gtk.StateType.Normal, new Gdk.Color(211,215,199));
+			Gtk.HBox headerHBox = new Gtk.HBox (false, 0);
+
+			header = new Gtk.Label ();
+			header.UseMarkup = true;
+			header.UseUnderline = false;
+			header.Markup =
+				string.Format ("<span size=\"x-large\" foreground=\"#9eb96e\" weight=\"bold\">{0}</span>",
+							   groupName);
+			header.Xalign = 0;
+			
+			header.Show ();
+			
+//			eb.Add(header);
+//			PackStart (eb, false, false, 0);
+			headerHBox.PackStart (header, false, false, 0);
+			
+			// spacer
+			Gtk.Label spacerLabel = new Gtk.Label (string.Empty);
+			spacerLabel.Show ();
+			headerHBox.PackStart (spacerLabel, true, true, 0);
+			
+			extraWidgetHBox = new Gtk.HBox (false, 0);
+			extraWidgetHBox.Show ();
+			headerHBox.PackStart (extraWidgetHBox, false, false, 0);
+			headerHBox.Show ();
+			PackStart (headerHBox, false, false, 5);
+			
+			//
+			// Group TreeView
+			//
+			treeView = new TaskTreeView (filteredTasks);
+			treeView.Show ();
+			PackStart (treeView, true, true, 0);
+			
+			treeView.NumberOfTasksChanged += OnNumberOfTasksChanged;
+			treeView.RowActivated += OnRowActivated;
+			treeView.ButtonPressEvent += OnButtonPressed;
+		}
+		#endregion // Constructor
+		
+		#region Events
+		public event Gtk.RowActivatedHandler RowActivated;
+		public event Gtk.ButtonPressEventHandler ButtonPressed;
+		#endregion // Events
+		
+		#region Public Properties
+		public string DisplayName
+		{
+			get { return header.Text; }
+		}
+		
+		public int HeaderHeight
+		{
+			get { return header.Requisition.Height; }
+		}
+		
+		/// <value>
+		/// Use this to set an Extra Widget.  The extra widget will be placed
+		/// on the right-hand side of the TaskGroup header.
+		/// </value>
+		public Gtk.Widget ExtraWidget
+		{
+			get { return extraWidget; }
+			set {
+				// Remove and destroy an existing extraWidget
+				if (extraWidget != null) {
+					extraWidgetHBox.Remove (extraWidget);
+					extraWidget.Destroy ();
+					extraWidget = null;
+				}
+				
+				extraWidget = value;
+				
+				if (extraWidget == null)
+					return;
+				
+				extraWidget.Show ();
+				extraWidgetHBox.PackStart (extraWidget, true, true, 0);
+			}
+		}
+		
+		/// <value>
+		/// If true, the entire task group will automatically hide itself when
+		/// there are no tasks to show.  Default value is true.  Special task
+		/// groups like CompletedTaskGroup may want to set this to false so the
+		/// range slider (HScale) widget can control how many completed tasks to
+		/// show.
+		/// </value>
+		public bool HideWhenEmpty
+		{
+			get { return hideWhenEmpty; }
+			set {
+				if (hideWhenEmpty == value)
+					return; // Don't do anything if the values are the same
+				
+				hideWhenEmpty = value;
+			}
+		}
+		
+		/// <value>
+		/// Get and set the minimum date for the group
+		/// </value>
+		public DateTime TimeRangeStart
+		{
+			get { return timeRangeStart; }
+			set {
+				if (value == timeRangeStart)
+					return;
+				
+				timeRangeStart = value;
+				Refilter ();
+			}
+		}
+		
+		/// <value>
+		/// Get and set the maxiumum date for the group
+		/// </value>
+		public DateTime TimeRangeEnd
+		{
+			get { return timeRangeEnd; }
+			set {
+				if (value == timeRangeEnd)
+					return;
+				
+				timeRangeEnd = value;
+				Refilter ();
+			}
+		}
+
+		#endregion // Public Properties
+		
+		#region Public Methods
+		public void Refilter (ICategory selectedCategory)
+		{
+			filteredTasks.Refilter ();
+			treeView.Refilter (selectedCategory);
+		}
+		
+		/// <summary>
+		/// Convenience method to determine whether the specified task is
+		/// currently shown in this TaskGroup.
+		/// </summary>
+		/// <param name="task">
+		/// A <see cref="ITask"/>
+		/// </param>
+		/// <param name="iter">
+		/// A <see cref="Gtk.TreeIter"/>
+		/// </param>
+		/// <returns>
+		/// A <see cref="System.Boolean"/> True if the specified <see
+		/// cref="ITask">task</see> is currently shown inside this TaskGroup.
+		/// Additionally, if true, the <see cref="Gtk.TreeIter">iter</see> will
+		/// point to the specified <see cref="ITask">task</see>.
+		/// </returns>
+		public bool ContainsTask (ITask task, out Gtk.TreeIter iter)
+		{
+			Gtk.TreeIter tempIter;
+			Gtk.TreeModel model = treeView.Model;
+			
+			iter = Gtk.TreeIter.Zero;
+			
+			if (model.GetIterFirst (out tempIter) == false)
+				return false;
+			
+			// Loop through the model looking for a matching task
+			do {
+				ITask tempTask = model.GetValue (tempIter, 0) as ITask;
+				if (tempTask == task) {
+					iter = tempIter;
+					return true;
+				}
+			} while (model.IterNext (ref tempIter) == true);
+			
+			return false;
+		}
+		
+		public int GetNChildren(Gtk.TreeIter iter)
+		{
+			return treeView.Model.IterNChildren();
+		}
+			
+		// Find the index within the tree
+		public int GetIterIndex (Gtk.TreeIter iter)
+		{
+			Gtk.TreePath path = treeView.Model.GetPath (iter);
+			Gdk.Rectangle rect =
+				treeView.GetBackgroundArea (path, treeView.GetColumn (0));
+			
+			int pos = 0;
+			Gtk.TreeIter tempIter;
+			Gtk.TreeModel model = treeView.Model;
+			ITask task = model.GetValue (iter, 0) as ITask;
+			
+			if (model.GetIterFirst (out tempIter) == false)
+				return 0;
+			
+			// This is ugly, but figure out what position the specified iter is
+			// at so we can return a value accordingly.
+			do {
+				ITask tempTask = model.GetValue (tempIter, 0) as ITask;
+				if (tempTask == task)
+					break;
+				
+				pos++;
+			} while (model.IterNext (ref tempIter) == true);
+			
+			return pos;
+		}
+		
+		// Find the height position within the tree
+		public int GetIterPos (Gtk.TreeIter iter)
+		{
+			Gtk.TreePath path = treeView.Model.GetPath (iter);
+			Gdk.Rectangle rect =
+				treeView.GetBackgroundArea (path, treeView.GetColumn (0));
+			int height = rect.Height;
+			
+			int pos = 0;
+			Gtk.TreeIter tempIter;
+			Gtk.TreeModel model = treeView.Model;
+			ITask task = model.GetValue (iter, 0) as ITask;
+			
+			if (model.GetIterFirst (out tempIter) == false)
+				return 0;
+			
+			// This is ugly, but figure out what position the specified iter is
+			// at so we can return a value accordingly.
+			do {
+				ITask tempTask = model.GetValue (tempIter, 0) as ITask;
+				if (tempTask == task)
+					break;
+				
+				pos++;
+			} while (model.IterNext (ref tempIter) == true);
+
+//Logger.Debug ("pos: {0}", pos);
+//Logger.Debug ("height: {0}", height);			
+//Logger.Debug ("returning: {0}", pos * height + header.Requisition.Height + 10);
+			// + 10 is for the spacing added on when packing the header
+			return pos * height + header.Requisition.Height;
+		}
+		
+		public void EnterEditMode (ITask task, Gtk.TreeIter iter)
+		{
+			Gtk.TreePath path;
+			
+			// Select the iter and go into editing mode on the task name
+			
+			// TODO: Figure out a way to NOT hard-code the column number
+			Gtk.TreeViewColumn nameColumn = treeView.Columns [2];
+			Gtk.CellRendererText nameCellRendererText =
+				nameColumn.CellRenderers [0] as Gtk.CellRendererText;
+			path = treeView.Model.GetPath (iter);
+			
+			treeView.Model.IterNChildren();
+				
+			treeView.SetCursorOnCell (path, nameColumn, nameCellRendererText, true);
+		}
+		#endregion // Methods
+		
+		#region Private Methods
+		protected override void OnRealized ()
+		{
+			base.OnRealized ();
+			
+			if (treeView.GetNumberOfTasks () == 0
+					&& (showCompletedTasks == false || hideWhenEmpty == true))
+				Hide ();
+			else
+				Show ();
+		}
+
+        /// <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)
+		{
+			ITask task = model.GetValue (iter, 0) as ITask;
+			if (task == null)
+				return false;
+			
+			// Do something special when task.DueDate == DateTime.MinValue since
+			// these tasks should always be in the very last category.
+			if (task.DueDate == DateTime.MinValue) {
+				if (timeRangeEnd == DateTime.MaxValue) {
+					if (ShowCompletedTask (task) == false)
+						return false;
+					
+					return true;
+				} else {
+					return false;
+				}
+			}
+			
+			if (task.DueDate < timeRangeStart || task.DueDate > timeRangeEnd)
+				return false;
+			
+			if (ShowCompletedTask (task) == false)
+				return false;
+			
+			return true;
+		}
+		
+		private bool ShowCompletedTask (ITask task)
+		{
+			if (task.State == TaskState.Completed) {
+				if (showCompletedTasks == false)
+					return false;
+				
+				// Only show completed tasks that are from "Today".  Once it's
+				// tomorrow, don't show completed tasks in this group and
+				// instead, show them in the Completed Tasks Group.
+				if (task.CompletionDate == DateTime.MinValue)
+					return false; // Just in case
+				
+				if (IsToday (task.CompletionDate) == false)
+					return false;
+			}
+			
+			return true;
+		}
+		
+		private bool IsToday (DateTime testDate)
+		{
+			DateTime today = DateTime.Now;
+			if (today.Year != testDate.Year
+					|| today.DayOfYear != testDate.DayOfYear)
+				return false;
+			
+			return true;
+		}
+		
+		/// <summary>
+		/// Refilter the hard way by discovering the category to filter on
+		/// </summary>
+		private void Refilter ()
+		{
+			ICategory cat = GetSelectedCategory ();
+			if (cat != null)
+				Refilter (cat);
+		}
+		
+		/// <summary>
+		/// This returns the currently selected category.
+		/// TODO: This should really be moved as a method Application or
+		/// or something.
+		/// </summary>
+		/// <returns>
+		/// A <see cref="ICategory"/>
+		/// </returns>
+		private ICategory GetSelectedCategory ()
+		{
+			// TODO: Move this code into some function in the backend/somewhere
+			// with the signature of GetCategoryForName (string catName):ICategory
+			string selectedCategoryName =
+				Application.Preferences.Get (Preferences.SelectedCategoryKey);
+			
+			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) == true) {
+					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) == true);
+				}
+			}
+			
+			return null;
+		}
+		#endregion // Private Methods
+		
+		#region Event Handlers
+		void OnNumberOfTasksChanged (object sender, EventArgs args)
+		{
+			//Logger.Debug ("TaskGroup (\"{0}\").OnNumberOfTasksChanged ()", DisplayName);
+			// Check to see whether this group should be hidden or shown.
+			if (treeView.GetNumberOfTasks () == 0
+					&& (showCompletedTasks == false || hideWhenEmpty == true))
+				Hide ();
+			else
+				Show ();
+		}
+		
+		void OnRowActivated (object sender, Gtk.RowActivatedArgs args)
+		{
+			// Pass this on to the TaskWindow
+			if (RowActivated != null)
+				RowActivated (sender, args);
+		}
+		
+		[GLib.ConnectBefore]
+		void OnButtonPressed (object sender, Gtk.ButtonPressEventArgs args)
+		{
+			// Pass this on to the TaskWindow
+			if (ButtonPressed != null)
+				ButtonPressed (sender, args);
+		}
+		
+		protected void OnSettingChanged (Preferences preferences,
+										 string settingKey)
+		{
+			if (settingKey.CompareTo (Preferences.ShowCompletedTasksKey) != 0)
+				return;
+			
+			bool newValue =
+				preferences.GetBool (Preferences.ShowCompletedTasksKey);
+			if (showCompletedTasks == newValue)
+				return; // don't do anything if nothing has changed
+			
+			showCompletedTasks = newValue;
+			
+			ICategory cat = GetSelectedCategory ();
+			if (cat != null)
+				Refilter (cat);
+		}
+
+		#endregion // Event Handlers
+	}
+}

Added: trunk/src/TaskPriority.cs
==============================================================================
--- (empty file)
+++ trunk/src/TaskPriority.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,15 @@
+// TaskPriority.cs created with MonoDevelop
+// User: boyd at 12:29 PMÂ2/11/2008
+
+using System;
+
+namespace Tasquer
+{
+	public enum TaskPriority
+	{
+		None = 0,
+		Low,
+		Medium,
+		High
+	}	
+}

Added: trunk/src/TaskState.cs
==============================================================================
--- (empty file)
+++ trunk/src/TaskState.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,34 @@
+// TaskState.cs created with MonoDevelop
+// User: boyd at 8:37 AMÂ2/12/2008
+
+using System;
+
+namespace Tasquer
+{
+	public enum TaskState
+	{
+		/// <summary>
+		/// A task that has not been completed.
+		/// </summary>
+		Active,
+		
+		/// <summary>
+		/// A task that's in limbo...the user has clicked that it should be
+		/// completed, but we're delaying so the user can get a visual of what's
+		/// gonna happen.  This feature ROCKS!
+		/// </summary>
+		Inactive,
+		
+		/// <summary>
+		/// A completed task.
+		/// </summary>
+		Completed,
+		
+		/// <summary>
+		/// A tasks that's deleted.  This is used when tasks are cached locally.
+		/// As soon as the task is actually deleted from the backend system, the
+		/// task should really be deleted.
+		/// </summary>
+		Deleted
+	}
+}

Added: trunk/src/TaskTreeView.cs
==============================================================================
--- (empty file)
+++ trunk/src/TaskTreeView.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,727 @@
+// TaskTreeView.cs created with MonoDevelop
+// User: boyd on 2/9/2008
+
+using System;
+using System.Collections.Generic;
+using Gtk;
+using Mono.Unix;
+
+namespace Tasquer
+{
+	/// <summary>
+	/// This is the main TreeView widget that is used to show tasks in Tasquer's
+	/// main window.
+	/// </summary>
+	public class TaskTreeView : Gtk.TreeView
+	{
+		private static Gdk.Pixbuf notePixbuf;
+		
+		private static Gdk.Pixbuf[] inactiveAnimPixbufs;
+		
+		private Gtk.TreeModelFilter modelFilter;
+		private ICategory filterCategory;
+		
+		static TaskTreeView ()
+		{
+			notePixbuf = Utilities.GetIcon ("note", 16);
+			
+			inactiveAnimPixbufs = new Gdk.Pixbuf [12];
+			for (int i = 0; i < 12; i++) {
+				string iconName = string.Format ("clock-16-{0}", i);
+				inactiveAnimPixbufs [i] = Utilities.GetIcon (iconName, 16);
+			}
+		}
+		
+		public event EventHandler NumberOfTasksChanged;
+
+		public TaskTreeView (Gtk.TreeModel model)
+			: base ()
+		{
+			// TODO: Modify the behavior of the TreeView so that it doesn't show
+			// the highlighted row.  Then, also tie in with the mouse hovering
+			// so that as you hover the mouse around, it will automatically
+			// select the row that the mouse is hovered over.  By doing this,
+			// we should be able to not require the user to click on a task
+			// to select it and THEN have to click on the column item they want
+			// to modify.
+			
+			filterCategory = null;
+			
+			modelFilter = new Gtk.TreeModelFilter (model, null);
+			modelFilter.VisibleFunc = FilterFunc;
+			
+			modelFilter.RowInserted += OnRowInsertedHandler;
+			modelFilter.RowDeleted += OnRowDeletedHandler;
+			
+			//Model = modelFilter
+			
+			Selection.Mode = Gtk.SelectionMode.Single;
+			RulesHint = false;
+			HeadersVisible = false;
+			HoverSelection = true;
+			
+			// TODO: Figure out how to turn off selection highlight
+			
+			Gtk.CellRenderer renderer;
+			
+			//
+			// Checkbox Column
+			//
+			Gtk.TreeViewColumn column = new Gtk.TreeViewColumn ();
+			// Title for Completed/Checkbox Column
+			column.Title = Catalog.GetString ("Completed");
+			column.Sizing = Gtk.TreeViewColumnSizing.Autosize;
+			column.Resizable = false;
+			column.Clickable = true;
+			
+			renderer = new Gtk.CellRendererToggle ();
+			(renderer as Gtk.CellRendererToggle).Toggled += OnTaskToggled;
+			column.PackStart (renderer, false);
+			column.SetCellDataFunc (renderer,
+							new Gtk.TreeCellDataFunc (TaskToggleCellDataFunc));
+			AppendColumn (column);
+			
+			//
+			// Priority Column
+			//
+			column = new Gtk.TreeViewColumn ();
+			// Title for Priority Column
+			column.Title = Catalog.GetString ("Priority");
+			//column.Sizing = Gtk.TreeViewColumnSizing.Autosize;
+			column.Sizing = Gtk.TreeViewColumnSizing.Fixed;
+			column.Alignment = 0.5f;
+			column.FixedWidth = 30;
+			column.Resizable = false;
+			column.Clickable = true;
+
+			renderer = new Gtk.CellRendererCombo ();
+			(renderer as Gtk.CellRendererCombo).Editable = true;
+			(renderer as Gtk.CellRendererCombo).HasEntry = false;
+			(renderer as Gtk.CellRendererCombo).Edited += OnTaskPriorityEdited;
+			Gtk.ListStore priorityStore = new Gtk.ListStore (typeof (string));
+			priorityStore.AppendValues (Catalog.GetString ("1")); // High
+			priorityStore.AppendValues (Catalog.GetString ("2")); // Medium
+			priorityStore.AppendValues (Catalog.GetString ("3")); // Low
+			priorityStore.AppendValues (Catalog.GetString ("-")); // None
+			(renderer as Gtk.CellRendererCombo).Model = priorityStore;
+			(renderer as Gtk.CellRendererCombo).TextColumn = 0;
+			renderer.Xalign = 0.5f;
+			column.PackStart (renderer, true);
+			column.SetCellDataFunc (renderer,
+					new Gtk.TreeCellDataFunc (TaskPriorityCellDataFunc));
+			AppendColumn (column);
+
+			//
+			// Task Name Column
+			//
+			column = new Gtk.TreeViewColumn ();
+			// Title for Task Name Column
+			column.Title = Catalog.GetString ("Task Name");
+//			column.Sizing = Gtk.TreeViewColumnSizing.Fixed;
+			column.Sizing = Gtk.TreeViewColumnSizing.Autosize;
+			column.Expand = true;
+			column.Resizable = true;
+			
+			// TODO: Add in code to determine how wide we should make the name
+			// column.
+			// TODO: Add in code to readjust the size of the name column if the
+			// user resizes the Task Window.
+			//column.FixedWidth = 250;
+			
+			renderer = new Gtk.CellRendererText ();
+			column.PackStart (renderer, true);
+			column.SetCellDataFunc (renderer,
+				new Gtk.TreeCellDataFunc (TaskNameTextCellDataFunc));
+			((Gtk.CellRendererText)renderer).Editable = true;
+			((Gtk.CellRendererText)renderer).Edited += OnTaskNameEdited;
+			
+			AppendColumn (column);
+			
+			
+			//
+			// Due Date Column
+			//
+
+			//  2/11 - Today
+			//  2/12 - Tomorrow
+			//  2/13 - Wed
+			//  2/14 - Thu
+			//  2/15 - Fri
+			//  2/16 - Sat
+			//  2/17 - Sun
+			// --------------
+			//  2/18 - In 1 Week
+			// --------------
+			//  No Date
+			// ---------------
+			//  Choose Date...
+			
+			column = new Gtk.TreeViewColumn ();
+			// Title for Due Date Column
+			column.Title = Catalog.GetString ("Due Date");
+			column.Sizing = Gtk.TreeViewColumnSizing.Fixed;
+			column.Alignment = 0f;
+			column.FixedWidth = 90;
+			column.Resizable = false;
+			column.Clickable = true;
+
+			renderer = new Gtk.CellRendererCombo ();
+			(renderer as Gtk.CellRendererCombo).Editable = true;
+			(renderer as Gtk.CellRendererCombo).HasEntry = false;
+			(renderer as Gtk.CellRendererCombo).Edited += OnDateEdited;
+			Gtk.ListStore dueDateStore = new Gtk.ListStore (typeof (string));
+			DateTime today = DateTime.Now;
+			dueDateStore.AppendValues (
+				today.ToString(Catalog.GetString("M/d - ")) + Catalog.GetString("Today"));
+			dueDateStore.AppendValues (
+				today.AddDays(1).ToString(Catalog.GetString("M/d - ")) + Catalog.GetString("Tomorrow"));
+			dueDateStore.AppendValues (
+				today.AddDays(2).ToString(Catalog.GetString("M/d - ddd")));
+			dueDateStore.AppendValues (
+				today.AddDays(3).ToString(Catalog.GetString("M/d - ddd")));
+			dueDateStore.AppendValues (
+				today.AddDays(4).ToString(Catalog.GetString("M/d - ddd")));
+			dueDateStore.AppendValues (
+				today.AddDays(5).ToString(Catalog.GetString("M/d - ddd")));
+			dueDateStore.AppendValues (
+				today.AddDays(6).ToString(Catalog.GetString("M/d - ddd")));
+			dueDateStore.AppendValues (
+				today.AddDays(7).ToString(Catalog.GetString("M/d - ")) + Catalog.GetString("In 1 Week"));			
+			dueDateStore.AppendValues (Catalog.GetString ("No Date"));
+			dueDateStore.AppendValues (Catalog.GetString ("Choose Date..."));
+			(renderer as Gtk.CellRendererCombo).Model = dueDateStore;
+			(renderer as Gtk.CellRendererCombo).TextColumn = 0;
+			renderer.Xalign = 0.0f;
+			column.PackStart (renderer, true);
+			column.SetCellDataFunc (renderer,
+					new Gtk.TreeCellDataFunc (DueDateCellDataFunc));
+			AppendColumn (column);
+
+
+			
+			//
+			// Notes Column
+			//
+			column = new Gtk.TreeViewColumn ();
+			// Title for Notes Column
+			column.Title = Catalog.GetString ("Notes");
+			column.Sizing = Gtk.TreeViewColumnSizing.Fixed;
+			column.FixedWidth = 20;
+			column.Resizable = false;
+			
+			renderer = new Gtk.CellRendererPixbuf ();
+			column.PackStart (renderer, false);
+			column.SetCellDataFunc (renderer,
+				new Gtk.TreeCellDataFunc (TaskNotesCellDataFunc));
+			
+			AppendColumn (column);
+			
+			//
+			// Timer Column
+			//
+			column = new Gtk.TreeViewColumn ();
+			// Title for Timer Column
+			column.Title = Catalog.GetString ("Timer");
+			column.Sizing = Gtk.TreeViewColumnSizing.Fixed;
+			column.FixedWidth = 20;
+			column.Resizable = false;
+			
+			renderer = new Gtk.CellRendererPixbuf ();
+			renderer.Xalign = 0.5f;
+			column.PackStart (renderer, false);
+			column.SetCellDataFunc (renderer,
+				new Gtk.TreeCellDataFunc (TaskTimerCellDataFunc));
+			
+			AppendColumn (column);
+		}
+		
+		#region Public Methods
+		public void Refilter ()
+		{
+			Refilter (filterCategory);
+		}
+		
+		public void Refilter (ICategory selectedCategory)
+		{
+			this.filterCategory = selectedCategory;
+			Model = modelFilter;
+			modelFilter.Refilter ();
+		}
+		
+		public int GetNumberOfTasks ()
+		{
+			return modelFilter.IterNChildren ();
+		}
+		#endregion // Public Methods
+		
+		#region Private Methods
+		
+		protected override void OnRealized ()
+		{
+			base.OnRealized ();
+			
+			// Not sure why we need this, but without it, completed items are
+			// initially appearing in the view.
+			Refilter (filterCategory);
+		}
+
+		
+		private void TaskToggleCellDataFunc (Gtk.TreeViewColumn column,
+											 Gtk.CellRenderer cell,
+											 Gtk.TreeModel model,
+											 Gtk.TreeIter iter)
+		{
+			Gtk.CellRendererToggle crt = cell as Gtk.CellRendererToggle;
+			ITask task = model.GetValue (iter, 0) as ITask;
+			if (task == null)
+				crt.Active = false;
+			else {
+				crt.Active =
+					task.State == TaskState.Active ? false : true;
+			}
+		}
+
+		void TaskPriorityCellDataFunc (Gtk.TreeViewColumn tree_column,
+									   Gtk.CellRenderer cell,
+									   Gtk.TreeModel tree_model,
+									   Gtk.TreeIter iter)
+		{
+			// TODO: Add bold (for high), light (for None), and also colors to priority?
+			Gtk.CellRendererCombo crc = cell as Gtk.CellRendererCombo;
+			ITask task = Model.GetValue (iter, 0) as ITask;
+			switch (task.Priority) {
+			case TaskPriority.Low:
+				crc.Text = Catalog.GetString ("3");
+				break;
+			case TaskPriority.Medium:
+				crc.Text = Catalog.GetString ("2");
+				break;
+			case TaskPriority.High:
+				crc.Text = Catalog.GetString ("1");
+				break;
+			default:
+				crc.Text = Catalog.GetString ("-");
+				break;
+			}
+		}
+		
+		private void TaskNameTextCellDataFunc (Gtk.TreeViewColumn treeColumn,
+				Gtk.CellRenderer renderer, Gtk.TreeModel model,
+				Gtk.TreeIter iter)
+		{
+			Gtk.CellRendererText crt = renderer as Gtk.CellRendererText;
+			crt.Ellipsize = Pango.EllipsizeMode.End;
+			ITask task = model.GetValue (iter, 0) as ITask;
+			if (task == null) {
+				crt.Text = string.Empty;
+				return;
+			}
+			
+			string formatString = "{0}";
+			switch (task.State) {
+			case TaskState.Inactive:
+				// Strikeout the text
+				formatString = "<span strikethrough=\"true\">{0}</span>";
+				break;
+			case TaskState.Deleted:
+			case TaskState.Completed:
+				// Gray out the text and add strikeout
+				// TODO: Determine the grayed-out text color appropriate for the current theme
+				formatString =
+					"<span foreground=\"#AAAAAA\" strikethrough=\"true\">{0}</span>";
+				break;
+			}
+			
+			crt.Markup = string.Format (formatString,
+				GLib.Markup.EscapeText (task.Name));
+		}
+		
+		protected virtual void DueDateCellDataFunc (Gtk.TreeViewColumn treeColumn,
+				Gtk.CellRenderer renderer, Gtk.TreeModel model,
+				Gtk.TreeIter iter)
+		{
+			Gtk.CellRendererCombo crc = renderer as Gtk.CellRendererCombo;
+			ITask task = Model.GetValue (iter, 0) as ITask;
+			DateTime date = task.State == TaskState.Completed ?
+									task.CompletionDate :
+									task.DueDate;
+			if (date == DateTime.MinValue || date == DateTime.MaxValue) {
+				crc.Text = "-";
+				return;
+			}
+			
+			crc.Text = 	date.ToString(Catalog.GetString("M/d - ddd"));
+			
+			//Utilities.GetPrettyPrintDate (task.DueDate, false);
+		}
+		
+		private void TaskNotesCellDataFunc (Gtk.TreeViewColumn treeColumn,
+				Gtk.CellRenderer renderer, Gtk.TreeModel model,
+				Gtk.TreeIter iter)
+		{
+			Gtk.CellRendererPixbuf crp = renderer as Gtk.CellRendererPixbuf;
+			ITask task = model.GetValue (iter, 0) as ITask;
+			if (task == null) {
+				crp.Pixbuf = null;
+				return;
+			}
+			
+			crp.Pixbuf = task.HasNotes ? notePixbuf : null;
+		}
+		
+		private void TaskTimerCellDataFunc (Gtk.TreeViewColumn treeColumn,
+				Gtk.CellRenderer renderer, Gtk.TreeModel model,
+				Gtk.TreeIter iter)
+		{
+			Gtk.CellRendererPixbuf crp = renderer as Gtk.CellRendererPixbuf;
+			ITask task = model.GetValue (iter, 0) as ITask;
+			if (task == null)
+				return;
+			
+			if (task.State != TaskState.Inactive) {
+				// The task is not in the inactive state so don't show any icon
+				crp.Pixbuf = null;
+				return;
+			}
+			
+			Preferences prefs = Application.Preferences;
+			int timerSeconds = prefs.GetInt (Preferences.InactivateTimeoutKey);
+			// convert to milliseconds for more granularity
+			long timeout = timerSeconds * 1000;
+			
+			//Logger.Debug ("TaskTimerCellDataFunc ()\n\tNow.Ticks: {0}\n\tCompletionDate.Ticks: {1}",
+			//				DateTime.Now.Ticks, task.CompletionDate.Ticks);
+			long elapsedTicks = DateTime.Now.Ticks - task.CompletionDate.Ticks;
+			//Logger.Debug ("\tElapsed Ticks: {0}", elapsedTicks);
+			long elapsedMillis = elapsedTicks / 10000;
+			//Logger.Debug ("\tElapsed Milliseconds: {0}", elapsedMillis);
+			
+			double percentComplete = (double)elapsedMillis / (double)timeout;
+			//Logger.Debug ("\tPercent Complete: {0}", percentComplete);
+			
+			Gdk.Pixbuf pixbuf = GetIconForPercentage (percentComplete * 100);
+			crp.Pixbuf = pixbuf;
+		}
+		
+		protected static Gdk.Pixbuf GetIconForPercentage (double timeoutPercent)
+		{
+			int iconNum = GetIconNumForPercentage (timeoutPercent);
+			if (iconNum == -1 || iconNum > 11)
+				return null;
+			
+			return inactiveAnimPixbufs [iconNum];
+		}
+		
+		protected static int GetIconNumForPercentage (double timeoutPercent)
+		{
+			//Logger.Debug ("GetIconNumForPercentage: {0}", timeoutPercent);
+			int numOfIcons = 12;
+			double percentIncrement = (double)100 / (double)numOfIcons;
+			//Logger.Debug ("\tpercentIncrement: {0}", percentIncrement);
+			
+			if (timeoutPercent < percentIncrement)
+				return 0;
+			if (timeoutPercent < percentIncrement * 2)
+				return 1;
+			if (timeoutPercent < percentIncrement * 3)
+				return 2;
+			if (timeoutPercent < percentIncrement * 4)
+				return 3;
+			if (timeoutPercent < percentIncrement * 5)
+				return 4;
+			if (timeoutPercent < percentIncrement * 6)
+				return 5;
+			if (timeoutPercent < percentIncrement * 7)
+				return 6;
+			if (timeoutPercent < percentIncrement * 8)
+				return 7;
+			if (timeoutPercent < percentIncrement * 9)
+				return 8;
+			if (timeoutPercent < percentIncrement * 10)
+				return 9;
+			if (timeoutPercent < percentIncrement * 11)
+				return 10;
+			if (timeoutPercent < percentIncrement * 12)
+				return 11;
+			
+			return -1;
+		}
+		
+		protected virtual bool FilterFunc (Gtk.TreeModel model,
+										   Gtk.TreeIter iter)
+		{
+			// Filter out deleted tasks
+			ITask task = model.GetValue (iter, 0) as ITask;
+			
+			if (task.State == TaskState.Deleted) {
+				//Logger.Debug ("TaskTreeView.FilterFunc:\n\t{0}\n\t{1}\n\tReturning false", task.Name, task.State);  
+				return false;
+			}
+			
+			if (filterCategory == null)
+				return true;
+			
+			return filterCategory.ContainsTask (task);
+		}
+		#endregion // Private Methods
+		
+		#region EventHandlers
+		void OnTaskToggled (object sender, Gtk.ToggledArgs args)
+		{
+			Logger.Debug ("OnTaskToggled");
+			Gtk.TreeIter iter;
+			Gtk.TreePath path = new Gtk.TreePath (args.Path);
+			if (Model.GetIter (out iter, path) == false)
+				return; // Do nothing
+			
+			ITask task = Model.GetValue (iter, 0) as ITask;
+			if (task == null)
+				return;
+
+			// remove any timer set up on this task			
+			InactivateTimer.CancelTimer(task);
+			
+			if (task.State == TaskState.Active) {
+				bool showCompletedTasks =
+					Application.Preferences.GetBool (
+						Preferences.ShowCompletedTasksKey);
+				
+				// When showCompletedTasks is true, complete the tasks right
+				// away.  Otherwise, set a timer and show the timer animation
+				// before marking the task completed.
+				if (showCompletedTasks == true) {
+					task.Complete ();
+				} else {
+					task.Inactivate ();
+					
+					// Read the inactivate timeout from a preference
+					int timeout =
+						Application.Preferences.GetInt (Preferences.InactivateTimeoutKey);
+					Logger.Debug ("Read timeout from prefs: {0}", timeout);
+					InactivateTimer timer =
+						new InactivateTimer (this, iter, task, (uint) timeout);
+					timer.StartTimer ();
+				}
+			} else {
+				task.Activate ();
+			}
+		}
+
+		void OnTaskPriorityEdited (object sender, Gtk.EditedArgs args)
+		{
+			Gtk.TreeIter iter;
+			Gtk.TreePath path = new TreePath (args.Path);
+			if (Model.GetIter (out iter, path) == false)
+				return;
+
+			TaskPriority newPriority;
+			if (args.NewText.CompareTo (Catalog.GetString ("3")) == 0)
+				newPriority = TaskPriority.Low;
+			else if (args.NewText.CompareTo (Catalog.GetString ("2")) == 0)
+				newPriority = TaskPriority.Medium;
+			else if (args.NewText.CompareTo (Catalog.GetString ("1")) == 0)
+				newPriority = TaskPriority.High;
+			else
+				newPriority = TaskPriority.None;
+
+			// Update the priority if it's different
+			ITask task = Model.GetValue (iter, 0) as ITask;
+			if (task.Priority != newPriority)
+				task.Priority = newPriority;
+		}
+		
+		void OnTaskNameEdited (object sender, Gtk.EditedArgs args)
+		{
+			Gtk.TreeIter iter;
+			Gtk.TreePath path = new TreePath (args.Path);
+			if (Model.GetIter (out iter, path) == false)
+				return;
+			
+			ITask task = Model.GetValue (iter, 0) as ITask;
+			if (task == null)
+				return;
+			
+			task.Name = args.NewText;
+		}
+		
+		/// <summary>
+		/// Modify the due date or completion date depending on whether the
+		/// task being modified is completed or active.
+		/// </summary>
+		/// <param name="sender">
+		/// A <see cref="System.Object"/>
+		/// </param>
+		/// <param name="args">
+		/// A <see cref="Gtk.EditedArgs"/>
+		/// </param>
+		void OnDateEdited (object sender, Gtk.EditedArgs args)
+		{
+			Gtk.TreeIter iter;
+			Gtk.TreePath path = new TreePath (args.Path);
+			if (Model.GetIter (out iter, path) == false)
+				return;
+			
+			//  2/11 - Today
+			//  2/12 - Tomorrow
+			//  2/13 - Wed
+			//  2/14 - Thu
+			//  2/15 - Fri
+			//  2/16 - Sat
+			//  2/17 - Sun
+			// --------------
+			//  2/18 - In 1 Week
+			// --------------
+			//  No Date
+			// ---------------
+			//  Choose Date...
+			
+			DateTime newDate = DateTime.MinValue;
+			DateTime today = DateTime.Now;
+			ITask task = Model.GetValue (iter, 0) as ITask;			
+			
+			if (args.NewText.CompareTo (
+							today.ToString(Catalog.GetString("M/d - ")) + Catalog.GetString("Today") ) == 0)
+				newDate = today;
+			else if (args.NewText.CompareTo (
+						today.AddDays(1).ToString(Catalog.GetString("M/d - ")) + Catalog.GetString("Tomorrow") ) == 0)
+				newDate = today.AddDays (1);
+			else if (args.NewText.CompareTo (Catalog.GetString ("No Date")) == 0)
+				newDate = DateTime.MinValue;
+			else if (args.NewText.CompareTo (
+				today.AddDays(7).ToString(Catalog.GetString("M/d - ")) + Catalog.GetString("In 1 Week")	) == 0)
+				newDate = today.AddDays (7);
+			else if (args.NewText.CompareTo (Catalog.GetString ("Choose Date...")) == 0) {
+				TaskCalendar tc = new TaskCalendar(task, this.Parent);
+				tc.ShowCalendar();
+				return;
+			} else {
+				for (int i = 2; i <= 6; i++) {
+					DateTime testDate = today.AddDays (i);
+					if (testDate.ToString(Catalog.GetString("M/d - ddd")).CompareTo (
+							args.NewText) == 0) {
+						newDate = testDate;
+						break;
+					}
+				}
+			}
+			
+			if (task.State == TaskState.Completed) {
+				// Modify the completion date
+				task.CompletionDate = newDate;
+			} else {
+				// Modify the due date
+				task.DueDate = newDate;
+			}
+		}
+		
+		void OnRowInsertedHandler (object sender, Gtk.RowInsertedArgs args)
+		{
+			if (NumberOfTasksChanged == null)
+				return;
+			
+			NumberOfTasksChanged (this, EventArgs.Empty);
+		}
+		
+		void OnRowDeletedHandler (object sender, Gtk.RowDeletedArgs args)
+		{
+			if (NumberOfTasksChanged == null)
+				return;
+			
+			NumberOfTasksChanged (this, EventArgs.Empty);
+		}
+		#endregion // EventHandlers
+		
+		#region Private Classes
+		/// <summary>
+		/// Does the work of walking a task through the Inactive -> Complete
+		/// states
+		/// </summary>
+		class InactivateTimer
+		{
+			/// <summary>
+			/// Keep track of all the timers so that the pulseTimeoutId can
+			/// be removed at the proper time.
+			/// </summary>
+			private static Dictionary<uint, InactivateTimer> timers;
+			
+			static InactivateTimer ()
+			{
+				timers = new Dictionary<uint,InactivateTimer> ();
+			}
+			
+			private TaskTreeView tree;
+			private ITask task;
+			private uint delay;
+			protected uint pulseTimeoutId;
+			private Gtk.TreeIter iter;
+			private Gtk.TreePath path;
+			
+			public InactivateTimer (TaskTreeView treeView,
+									Gtk.TreeIter taskIter,
+									ITask taskToComplete,
+									uint delayInSeconds)
+			{
+				tree = treeView;
+				iter = taskIter;
+				path = treeView.Model.GetPath (iter);
+				task = taskToComplete;
+				delay = delayInSeconds * 1000; // Convert to milliseconds
+				pulseTimeoutId = 0;
+			}
+			
+			public void StartTimer ()
+			{
+				pulseTimeoutId = GLib.Timeout.Add (500, PulseAnimation);
+				task.TimerID = GLib.Timeout.Add (delay, CompleteTask);
+				timers [task.TimerID] = this;
+			}
+			
+			public static void CancelTimer(ITask task)
+			{
+				Logger.Debug("Timeout Canceled for task: " + task.Name);
+				InactivateTimer timer = null;
+				uint timerId = task.TimerID;
+				if(timerId != 0) {
+					if (timers.ContainsKey (timerId)) {
+						timer = timers [timerId];
+						timers.Remove (timerId);
+					}
+					GLib.Source.Remove(timerId);
+					task.TimerID = 0;
+				}
+				
+				if (timer != null) {
+					GLib.Source.Remove (timer.pulseTimeoutId);
+					timer.pulseTimeoutId = 0;
+				}
+			}
+			
+			private bool CompleteTask ()
+			{
+				GLib.Source.Remove (pulseTimeoutId);
+				if (timers.ContainsKey (task.TimerID))
+					timers.Remove (task.TimerID);
+				
+				if(task.State != TaskState.Inactive)
+					return false;
+					
+				task.Complete ();
+				tree.Refilter ();
+				return false; // Don't automatically call this handler again
+			}
+			
+			private bool PulseAnimation ()
+			{
+				// Emit this signal to cause the TreeView to update the row
+				// where the task is located.  This will allow the
+				// CellRendererPixbuf to update the icon.
+				tree.Model.EmitRowChanged (path, iter);
+				
+				// Return true so that this method will be called after an
+				// additional timeout duration has elapsed.
+				return true;
+			}
+		}
+		#endregion // Private Classes
+	}
+}

Added: trunk/src/TaskWindow.cs
==============================================================================
--- (empty file)
+++ trunk/src/TaskWindow.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,999 @@
+/***************************************************************************
+ *  TargetWindow.cs
+ *
+ *  Copyright (C) 2007 Novell, Inc.
+ *  Written by:
+ *		Calvin Gaisford <calvinrg gmail com>
+ *		Boyd Timothy <btimothy 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 Gtk;
+using Mono.Unix;
+
+using Tasquer.Backends;
+
+namespace Tasquer
+{
+	public class TaskWindow : Gtk.Window 
+	{
+		private static TaskWindow taskWindow = null;
+		private static int lastXPos;
+		private static int lastYPos;
+		private static Gdk.Pixbuf noteIcon;
+		
+		private IBackend backend;
+		private ScrolledWindow scrolledWindow;
+		
+		private MenuToolButton addTaskButton;
+		private Gtk.ComboBox categoryComboBox;
+		private Gtk.VBox targetVBox;
+		
+		private TaskGroup overdueGroup;
+		private TaskGroup todayGroup;
+		private TaskGroup tomorrowGroup;
+		private TaskGroup nextSevenDaysGroup;
+		private TaskGroup futureGroup;
+		private CompletedTaskGroup completedTaskGroup;
+		
+		private List<TaskGroup> taskGroups;
+		
+		private Dictionary<ITask, NoteDialog> noteDialogs;
+		
+		private Gtk.Statusbar statusbar;
+		private uint statusContext;
+		private uint currentStatusMessageId;
+		
+		private ITask clickedTask;
+		
+		private Gtk.AccelGroup accelGroup;
+		private GlobalKeybinder globalKeys;
+		
+		static TaskWindow ()
+		{
+			noteIcon = Utilities.GetIcon ("note", 16);
+		}
+		
+		public TaskWindow (IBackend aBackend) : base (WindowType.Toplevel)
+		{
+			this.backend = aBackend;
+			taskGroups = new List<TaskGroup> ();
+			noteDialogs = new Dictionary<ITask, NoteDialog> ();
+			InitWindow();
+		}
+
+		void InitWindow()
+		{
+			int height;
+			int width;
+			
+			this.Icon = Utilities.GetIcon ("tasquer-48", 48);
+			// Update the window title
+			Title = string.Format ("Tasquer");	
+
+			width = Application.Preferences.GetInt("MainWindowWidth");
+			height = Application.Preferences.GetInt("MainWindowHeight");
+			
+			if(width == -1)
+				width = 600;
+			if(height == -1)
+				height = 600;
+			
+			this.DefaultSize = new Gdk.Size( width, height);
+			
+			accelGroup = new AccelGroup ();
+			AddAccelGroup (accelGroup);
+			globalKeys = new GlobalKeybinder (accelGroup);
+
+			// Start with an event box to paint the background white
+			EventBox eb = new EventBox();
+			eb.BorderWidth = 0;
+			eb.ModifyBg(	StateType.Normal, 
+					new Gdk.Color(255,255,255));
+			eb.ModifyBase(	StateType.Normal, 
+					new Gdk.Color(255,255,255));
+			
+			VBox mainVBox = new VBox();
+			mainVBox.BorderWidth = 0;
+			mainVBox.Show ();
+			eb.Add(mainVBox);
+			this.Add (eb);
+			
+			HBox topHBox = new HBox (false, 0);
+			topHBox.BorderWidth = 4;
+			
+			categoryComboBox = new ComboBox ();
+			categoryComboBox.WrapWidth = 1;
+			CellRendererText comboBoxRenderer = new Gtk.CellRendererText ();
+			categoryComboBox.PackStart (comboBoxRenderer, true);
+			categoryComboBox.SetCellDataFunc (comboBoxRenderer,
+				new Gtk.CellLayoutDataFunc (CategoryComboBoxDataFunc));
+			
+			categoryComboBox.Show ();
+			topHBox.PackStart (categoryComboBox, false, false, 0);
+			
+			// Space the addTaskButton and the categoryComboBox
+			// far apart by using a blank label that expands
+			Label spacer = new Label (string.Empty);
+			spacer.Show ();
+			topHBox.PackStart (spacer, true, true, 0);
+			
+			// Use a small add icon so the button isn't mammoth-sized
+			HBox buttonHBox = new HBox (false, 6);
+			Image addImage = new Image (Gtk.Stock.Add, IconSize.Menu);
+			addImage.Show ();
+			buttonHBox.PackStart (addImage, false, false, 0);
+			Label l = new Label (Catalog.GetString ("_Add Task"));
+			l.Show ();
+			buttonHBox.PackStart (l, true, true, 0);
+			buttonHBox.Show ();
+			addTaskButton =
+				new MenuToolButton (buttonHBox, Catalog.GetString ("_Add Task"));
+			addTaskButton.UseUnderline = true;
+			// Disactivate the button until the backend is initialized
+			addTaskButton.Sensitive = false;
+			Gtk.Menu addTaskMenu = new Gtk.Menu ();
+			addTaskButton.Menu = addTaskMenu;
+			addTaskButton.Clicked += OnAddTask;
+			addTaskButton.Show ();
+			topHBox.PackStart (addTaskButton, false, false, 0);
+			
+			globalKeys.AddAccelerator (OnAddTask,
+			                           (uint) Gdk.Key.n,
+			                           Gdk.ModifierType.ControlMask,
+			                           Gtk.AccelFlags.Visible);
+			
+			topHBox.Show ();
+			mainVBox.PackStart (topHBox, false, false, 0);
+
+			scrolledWindow = new ScrolledWindow ();
+			scrolledWindow.VscrollbarPolicy = PolicyType.Automatic;
+			scrolledWindow.HscrollbarPolicy = PolicyType.Never;
+
+			scrolledWindow.BorderWidth = 0;
+			scrolledWindow.CanFocus = true;
+			scrolledWindow.Show ();
+			mainVBox.PackStart (scrolledWindow, true, true, 0);
+
+			EventBox innerEb = new EventBox();
+			innerEb.BorderWidth = 0;
+			innerEb.ModifyBg(	StateType.Normal, 
+						new Gdk.Color(255,255,255));
+			innerEb.ModifyBase(	StateType.Normal, 
+						new Gdk.Color(255,255,255));
+
+			targetVBox = new VBox();
+			targetVBox.BorderWidth = 5;
+			targetVBox.Show ();
+			innerEb.Add(targetVBox);
+
+			scrolledWindow.AddWithViewport(innerEb);
+			
+			statusbar = new Gtk.Statusbar ();
+			statusContext = statusbar.GetContextId ("tasquer-statusbar");
+			currentStatusMessageId =
+				statusbar.Push (statusContext,
+								Catalog.GetString ("Loading tasks..."));
+			
+			statusbar.HasResizeGrip = true;
+			
+			statusbar.Show ();
+
+			mainVBox.PackEnd (statusbar, false, false, 0);
+			
+			//
+			// Delay adding in the TaskGroups until the backend is initialized
+			//
+			
+			Shown += OnWindowShown;
+			DeleteEvent += WindowDeleted;
+			
+			backend.BackendInitialized += OnBackendInitialized;
+			backend.BackendSyncStarted += OnBackendSyncStarted;
+			backend.BackendSyncFinished += OnBackendSyncFinished;
+			// if the backend is already initialized, go ahead... initialize
+			if(backend.Initialized) {
+				OnBackendInitialized();
+			}
+			
+			Application.Preferences.SettingChanged += OnSettingChanged;
+		}
+
+
+		void PopulateWindow()
+		{
+			
+
+			// Add in the groups
+			
+			//
+			// Overdue Group
+			//
+			DateTime rangeStart;
+			DateTime rangeEnd;
+			
+			rangeStart = DateTime.MinValue;
+			rangeEnd = DateTime.Now.AddDays (-1);
+			rangeEnd = new DateTime (rangeEnd.Year, rangeEnd.Month, rangeEnd.Day,
+									 23, 59, 59);
+			
+			overdueGroup = new TaskGroup (Catalog.GetString ("Overdue"),
+										  rangeStart, rangeEnd,
+										  backend.Tasks);
+			overdueGroup.RowActivated += OnRowActivated;
+			overdueGroup.ButtonPressed += OnButtonPressed;
+			overdueGroup.Show ();
+			targetVBox.PackStart (overdueGroup, false, false, 0);
+			taskGroups.Add(overdueGroup);
+			
+			//
+			// Today Group
+			//
+			rangeStart = DateTime.Now;
+			rangeStart = new DateTime (rangeStart.Year, rangeStart.Month,
+									   rangeStart.Day, 0, 0, 0);
+			rangeEnd = DateTime.Now;
+			rangeEnd = new DateTime (rangeEnd.Year, rangeEnd.Month,
+									 rangeEnd.Day, 23, 59, 59);
+			todayGroup = new TaskGroup (Catalog.GetString ("Today"),
+										rangeStart, rangeEnd,
+										backend.Tasks);
+			todayGroup.RowActivated += OnRowActivated;
+			todayGroup.ButtonPressed += OnButtonPressed;
+			todayGroup.Show ();
+			targetVBox.PackStart (todayGroup, false, false, 0);
+			taskGroups.Add (todayGroup);
+			
+			//
+			// Tomorrow Group
+			//
+			rangeStart = DateTime.Now.AddDays (1);
+			rangeStart = new DateTime (rangeStart.Year, rangeStart.Month,
+									   rangeStart.Day, 0, 0, 0);
+			rangeEnd = DateTime.Now.AddDays (1);
+			rangeEnd = new DateTime (rangeEnd.Year, rangeEnd.Month,
+									 rangeEnd.Day, 23, 59, 59);
+			tomorrowGroup = new TaskGroup (Catalog.GetString ("Tomorrow"),
+										   rangeStart, rangeEnd,
+										   backend.Tasks);
+			tomorrowGroup.RowActivated += OnRowActivated;
+			tomorrowGroup.ButtonPressed += OnButtonPressed;			
+			tomorrowGroup.Show ();
+			targetVBox.PackStart (tomorrowGroup, false, false, 0);
+			taskGroups.Add (tomorrowGroup);
+			
+			//
+			// Next 7 Days Group
+			//
+			rangeStart = DateTime.Now.AddDays (2);
+			rangeStart = new DateTime (rangeStart.Year, rangeStart.Month,
+									   rangeStart.Day, 0, 0, 0);
+			rangeEnd = DateTime.Now.AddDays (6);
+			rangeEnd = new DateTime (rangeEnd.Year, rangeEnd.Month,
+									 rangeEnd.Day, 23, 59, 59);
+			nextSevenDaysGroup = new TaskGroup (Catalog.GetString ("Next 7 Days"),
+										   rangeStart, rangeEnd,
+										   backend.Tasks);
+			nextSevenDaysGroup.RowActivated += OnRowActivated;
+			nextSevenDaysGroup.ButtonPressed += OnButtonPressed;				
+			nextSevenDaysGroup.Show ();
+			targetVBox.PackStart (nextSevenDaysGroup, false, false, 0);
+			taskGroups.Add (nextSevenDaysGroup);
+			
+			//
+			// Future Group
+			//
+			rangeStart = DateTime.Now.AddDays (7);
+			rangeStart = new DateTime (rangeStart.Year, rangeStart.Month,
+									   rangeStart.Day, 0, 0, 0);
+			rangeEnd = DateTime.MaxValue;
+			futureGroup = new TaskGroup (Catalog.GetString ("Future"),
+										 rangeStart, rangeEnd,
+										 backend.Tasks);
+			futureGroup.RowActivated += OnRowActivated;
+			futureGroup.ButtonPressed += OnButtonPressed;			
+			futureGroup.Show ();
+			targetVBox.PackStart (futureGroup, false, false, 0);
+			taskGroups.Add (futureGroup);
+			
+			//
+			// Completed Tasks Group
+			//
+			rangeStart = DateTime.MinValue;
+			rangeEnd = DateTime.MaxValue;
+			completedTaskGroup = new CompletedTaskGroup (
+					Catalog.GetString ("Completed"),
+					rangeStart, rangeEnd,
+					backend.Tasks);
+			completedTaskGroup.RowActivated += OnRowActivated;
+			completedTaskGroup.ButtonPressed += OnButtonPressed;
+			completedTaskGroup.Show ();
+			targetVBox.PackStart (completedTaskGroup, false, false, 0);
+			taskGroups.Add (completedTaskGroup);
+			
+
+			//manualTarget = new TargetService();
+			//manualTarget.Show ();
+			//mainVBox.PackStart(manualTarget, false, false, 0);
+			
+			
+			// Set up the combo box (after the above to set the current filter)
+
+			categoryComboBox.Model = Application.Backend.Categories;		
+
+			// Read preferences for the last-selected category and select it
+			string selectedCategoryName =
+				Application.Preferences.Get (Preferences.SelectedCategoryKey);
+			
+			categoryComboBox.Changed += OnCategoryChanged;
+			
+			SelectCategory (selectedCategoryName);
+		}
+
+		
+		#region Public Methods
+		/// <summary>
+		/// Method to allow other classes to "click" on the "Add Task" button.
+		/// </summary>
+		public static void AddTask ()
+		{
+			if (taskWindow == null)
+				TaskWindow.ShowWindow ();
+			
+			taskWindow.OnAddTask (null, EventArgs.Empty);
+		}
+
+		public static void SavePosition()
+		{
+			if(taskWindow != null) {
+				int x;
+				int y;
+				int width;
+				int height;
+
+				taskWindow.GetPosition(out x, out y);
+				taskWindow.GetSize(out width, out height);
+
+				lastXPos = x;
+				lastYPos = y;
+				
+				Application.Preferences.SetInt("MainWindowLastXPos", lastXPos);
+				Application.Preferences.SetInt("MainWindowLastYPos", lastYPos);
+				Application.Preferences.SetInt("MainWindowWidth", width);
+				Application.Preferences.SetInt("MainWindowHeight", height);	
+			}
+
+		}
+		
+		public static void ShowWindow()
+		{
+			if(taskWindow != null) {
+				if(taskWindow.IsActive) {
+					int x;
+					int y;
+
+					taskWindow.GetPosition(out x, out y);
+
+					lastXPos = x;
+					lastYPos = y;
+
+					taskWindow.Hide();
+				} else {
+					if(!taskWindow.Visible) {
+						int x = lastXPos;
+						int y = lastYPos;
+
+						if (x >= 0 && y >= 0)
+							taskWindow.Move(x, y);						
+					}
+					taskWindow.Present();
+				}
+			} else {
+				// TODO: Eventually move the creation of the IBackend into
+				// something that will parse the command line to read which
+				// backend should be used.  If no specific backend is specified,
+				// use RtmTaskBackend by default.
+				TaskWindow.taskWindow = new TaskWindow(Application.Backend);
+				if(lastXPos == 0 || lastYPos == 0)
+				{
+					lastXPos = Application.Preferences.GetInt("MainWindowLastXPos");
+					lastYPos = Application.Preferences.GetInt("MainWindowLastYPos");				
+				}
+
+				int x = lastXPos;
+				int y = lastYPos;
+
+				if (x >= 0 && y >= 0)
+					taskWindow.Move(x, y);						
+
+				taskWindow.ShowAll();
+			}
+		}
+		
+		public static void SelectAndEdit (ITask task)
+		{
+			ShowWindow ();
+			taskWindow.EnterEditMode (task, true);
+			taskWindow.Present ();
+		}
+		
+		public static void ShowStatus (string statusText)
+		{
+			if (taskWindow == null) {
+				Logger.Warn ("Cannot set status when taskWindow is null");
+				return;
+			}
+			
+			if (taskWindow.currentStatusMessageId != 0) {
+				// Pop the old message
+				taskWindow.statusbar.Remove (taskWindow.statusContext,
+											 taskWindow.currentStatusMessageId);
+				taskWindow.currentStatusMessageId = 0;
+			}
+			
+			taskWindow.currentStatusMessageId =
+				taskWindow.statusbar.Push (taskWindow.statusContext,
+										   statusText);
+		}
+		
+		/// <summary>
+		/// This should be called after a new IBackend has been set
+		/// </summary>
+		public static void Reinitialize ()
+		{
+			if (TaskWindow.taskWindow != null) {
+				TaskWindow.taskWindow.Hide ();
+				TaskWindow.taskWindow.Destroy ();
+				TaskWindow.taskWindow = null;
+			}
+			
+			TaskWindow.ShowWindow ();
+		}
+		#endregion // Public Methods
+		
+		#region Private Methods
+		void CategoryComboBoxDataFunc (Gtk.CellLayout layout,
+									   Gtk.CellRenderer renderer,
+									   Gtk.TreeModel model,
+									   Gtk.TreeIter iter)
+		{
+			Gtk.CellRendererText crt = renderer as Gtk.CellRendererText;
+			ICategory category = model.GetValue (iter, 0) as ICategory;
+
+			// CRG: What?  I added this check for null and we don't crash
+			// but I never see anything called unknown
+			if(category != null && category.Name != null) {
+				crt.Text =
+					string.Format ("{0} ({1})",
+								   category.Name,
+								   GetTaskCountInCategory (category));
+			} else
+				crt.Text = "unknown";
+		}
+		
+		// TODO: Move this method into a property of ICategory.TaskCount
+		private int GetTaskCountInCategory (ICategory category)
+		{
+			// This is disgustingly inefficient, but, oh well
+			int count = 0;
+			
+			Gtk.TreeIter iter;
+			Gtk.TreeModel model = Application.Backend.Tasks;
+			
+			if (model.GetIterFirst (out iter) == false)
+				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) == true);
+			
+			return count;
+		}
+		
+		
+		/// <summary>
+		/// Search through the TaskGroups looking for the specified task and:
+		/// 1) scroll the window to its location, 2) enter directly into edit
+		/// mode.  This method should be called right after a new task is
+		/// created.
+		/// </summary>
+		/// <param name="task">
+		/// A <see cref="ITask"/>
+		/// </param>
+		/// <param name="adjustScrolledWindow">
+		/// A <see cref="bool"/> which indicates whether the task should be
+		/// scrolled to.
+		/// </param>
+		private void EnterEditMode (ITask task, bool adjustScrolledWindow)
+		{
+			Gtk.TreeIter iter;
+			
+			// Make sure we've waited around for the new task to fully
+			// be added to the TreeModel before continuing.  Some
+			// backends might be threaded and will have used something
+			// like Gtk.Idle.Add () to actually store the new Task in
+			// their TreeModel.
+			while (Gtk.Application.EventsPending ())
+				Gtk.Application.RunIteration ();
+
+			int taskGroupHeights = 0;
+			
+			foreach (TaskGroup taskGroup in taskGroups) {
+				
+				//Logger.Debug("taskGroupHeights: {0}", taskGroupHeights);
+				
+				if (taskGroup.ContainsTask (task, out iter) == true) {
+					Logger.Debug ("Found new task group: {0}", taskGroup.DisplayName);
+					
+					// Get the header height
+					int headerHeight = taskGroup.HeaderHeight;
+				
+					// Get the total number items in the TaskGroup
+					int nChildren = taskGroup.GetNChildren(iter);
+					//Logger.Debug("n children: {0}", nChildren);
+
+					// Calculate size of each item
+					double itemSize = (double)(taskGroup.Requisition.Height-headerHeight) / nChildren;
+					//Logger.Debug("item size: {0}", itemSize);
+				
+					// Get the index of the new item within the TaskGroup
+					int newTaskIndex = taskGroup.GetIterIndex (iter);
+					//Logger.Debug("new task index: {0}", newTaskIndex);
+						
+					// Calculate the scrolling distance
+					double scrollDistance = (itemSize*newTaskIndex)+taskGroupHeights;
+					//Logger.Debug("Scroll distance = ({0}*{1})+{2}+{3}: {4}", itemSize, newTaskIndex, taskGroupHeights, headerHeight, scrollDistance);
+	
+					//scroll to the new task
+					if (adjustScrolledWindow == true)
+						scrolledWindow.Vadjustment.Value = scrollDistance;
+					
+					taskGroup.EnterEditMode (task, iter);
+					return;
+				}
+				if (taskGroup.Visible) {
+					taskGroupHeights += taskGroup.Requisition.Height;
+				}
+			}
+		}
+		
+		private void RebuildAddTaskMenu (Gtk.TreeModel categoriesModel)
+		{
+			Gtk.Menu menu = new Menu ();
+			
+			Gtk.TreeIter iter;
+			if (categoriesModel.GetIterFirst (out iter) == true) {
+				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) == true);
+			}
+			
+			addTaskButton.Menu = menu;
+		}
+		
+		private void SelectCategory (string categoryName)
+		{
+			Gtk.TreeIter iter;
+			Gtk.TreeModel model = categoryComboBox.Model;
+			bool categoryWasSelected = false;
+
+			if (categoryName != null) {
+				// Iterate through (yeah, I know this is gross!) and find the
+				// matching category
+				if (model.GetIterFirst (out iter) == true) {
+					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 (categoryName) == 0) {
+							categoryComboBox.SetActiveIter (iter);
+							categoryWasSelected = true;
+							break;
+						}
+					} while (model.IterNext (ref iter) == true);
+				}
+			}
+			
+			if (categoryWasSelected == false) {
+				// Select the first item in the list (which should be the "All"
+				// category.
+				if (model.GetIterFirst (out iter) == true) {
+					// Make sure we can actually get a category
+					ICategory cat = model.GetValue (iter, 0) as ICategory;
+					if (cat != null)
+						categoryComboBox.SetActiveIter (iter);
+				}
+			}
+		}
+		
+		private void ShowTaskNotes (ITask task)
+		{
+			NoteDialog dialog = null;
+			if (noteDialogs.ContainsKey (task) == false) {
+				dialog = new NoteDialog (this, task);
+				dialog.Hidden += OnNoteDialogHidden;
+				noteDialogs [task] = dialog;
+			} else {
+				dialog = noteDialogs [task];
+			}
+			
+			dialog.Present ();
+		}
+		#endregion // Private Methods
+
+		#region Event Handlers
+		private void WindowDeleted (object sender, DeleteEventArgs args)
+		{
+			int x;
+			int y;
+			int width;
+			int height;
+
+			this.GetPosition(out x, out y);
+			this.GetSize(out width, out height);
+
+			lastXPos = x;
+			lastYPos = y;
+			
+			Application.Preferences.SetInt("MainWindowLastXPos", lastXPos);
+			Application.Preferences.SetInt("MainWindowLastYPos", lastYPos);
+			Application.Preferences.SetInt("MainWindowWidth", width);
+			Application.Preferences.SetInt("MainWindowHeight", height);
+
+			Logger.Debug("WindowDeleted was called");
+			taskWindow = null;
+		}
+
+		private void OnWindowShown (object sender, EventArgs args)
+		{
+
+		}
+		
+		void OnSettingChanged (Preferences preferences, string settingKey)
+		{
+			if (settingKey.CompareTo (Preferences.ShowInAllCategory) != 0)
+				return;
+			
+			OnCategoryChanged (this, EventArgs.Empty);
+		}
+
+		void OnAddTask (object sender, EventArgs args)
+		{
+			Gtk.TreeIter iter;
+			if (categoryComboBox.GetActiveIter (out iter) == false)
+				return;
+			
+			ICategory category =
+				categoryComboBox.Model.GetValue (iter, 0) as ICategory;
+
+			ITask task = backend.CreateTask (Catalog.GetString ("New task"), category);
+			
+			// Scroll to the task and put it into "edit" mode
+			EnterEditMode (task, true);
+			
+		}
+		
+		void OnNewTaskByCategory (object sender, EventArgs args)
+		{
+			CategoryMenuItem item = sender as CategoryMenuItem;
+			if (item == null)
+				return;
+			
+			// Determine if the selected category is currently shown in the
+			// task window.  If we're in a specific cateogory or on the All
+			// category and the selected category is not showing, we've got
+			// to switch the category first so the user will be able to edit
+			// the title of the task.
+			Gtk.TreeIter iter;
+			if (categoryComboBox.GetActiveIter (out iter) == true) {
+				ICategory selectedCategory =
+					categoryComboBox.Model.GetValue (iter, 0) as ICategory;
+				
+				// Check to see if "All" is selected
+				if (selectedCategory is AllCategory) {
+					// See if the item.Category is currently being shown in
+					// the "All" category and if not, select the category
+					// specifically.
+					List<string> categoriesToShow =
+						Application.Preferences.GetStringList (
+							Preferences.ShowInAllCategory);
+					if (categoriesToShow != null && categoriesToShow.Count > 0
+							&& categoriesToShow.Contains (item.Category.Name) == false) {
+						SelectCategory (item.Category.Name);
+					}
+				} else if (selectedCategory.Name.CompareTo (item.Category.Name) != 0) {
+					SelectCategory (item.Category.Name);
+				}
+			}
+			
+			ITask task =
+				backend.CreateTask (Catalog.GetString ("New task"),
+									item.Category);
+			
+			EnterEditMode (task, true);
+		}
+		
+		void OnCategoryChanged (object sender, EventArgs args)
+		{
+			Gtk.TreeIter iter;
+			if (categoryComboBox.GetActiveIter (out iter) == false)
+				return;
+			
+			ICategory category =
+				categoryComboBox.Model.GetValue (iter, 0) as ICategory;
+				
+			// Update the TaskGroups so they can filter accordingly
+			overdueGroup.Refilter (category);
+			todayGroup.Refilter (category);
+			tomorrowGroup.Refilter (category);
+			nextSevenDaysGroup.Refilter (category);
+			futureGroup.Refilter (category);
+			completedTaskGroup.Refilter (category);
+			
+			// Save the selected category in preferences
+			Application.Preferences.Set (Preferences.SelectedCategoryKey,
+										 category.Name);
+		}
+		
+		void OnRowActivated (object sender, Gtk.RowActivatedArgs args)
+		{
+			// Check to see if a note dialog is already open for the activated
+			// task.  If so, just bring it forward.  Otherwise, open a new one.
+			Gtk.TreeView tv = sender as Gtk.TreeView;
+			if (tv == null)
+				return;
+			
+			Gtk.TreeModel model = tv.Model;
+			
+			Gtk.TreeIter iter;
+			
+			if (model.GetIter (out iter, args.Path) == false)
+				return;
+			
+			ITask task = model.GetValue (iter, 0) as ITask;
+			if (task == null)
+				return;
+			
+			ShowTaskNotes (task);
+		}
+		
+
+		[GLib.ConnectBefore]
+		void OnButtonPressed (object sender, Gtk.ButtonPressEventArgs args)
+		{
+	        switch (args.Event.Button) {
+	            case 3: // third mouse button (right-click)
+		            clickedTask = null;
+
+					Gtk.TreeView tv = sender as Gtk.TreeView;
+					if (tv == null)
+						return;
+					
+					Gtk.TreeModel model = tv.Model;
+					
+					Gtk.TreeIter iter;
+					Gtk.TreePath path;
+					Gtk.TreeViewColumn column = null;
+
+					if (tv.GetPathAtPos ((int) args.Event.X,
+									(int) args.Event.Y, out path, out column) == false)
+						return;
+
+					if (model.GetIter (out iter, path) == false)
+						return;
+					
+					clickedTask = model.GetValue (iter, 0) as ITask;
+					if (clickedTask == null)
+						return;
+					
+					Menu popupMenu = new Menu ();
+					ImageMenuItem item;
+					
+					item = new ImageMenuItem (Catalog.GetString ("_Notes..."));
+					item.Image = new Image (noteIcon);
+					item.Activated += OnShowTaskNotes;
+					popupMenu.Add (item);
+					
+					popupMenu.Add (new SeparatorMenuItem ());
+
+					item = new ImageMenuItem (Catalog.GetString ("_Delete task"));
+					item.Image = new Image(Gtk.Stock.Delete, IconSize.Menu);
+					item.Activated += OnDeleteTask;
+					popupMenu.Add (item);
+
+					item = new ImageMenuItem(Catalog.GetString ("_Edit task"));
+					item.Image = new Image(Gtk.Stock.Edit, IconSize.Menu);
+					item.Activated += OnEditTask;
+					popupMenu.Add (item);
+
+					popupMenu.ShowAll();
+					popupMenu.Popup ();
+				
+					// Logger.Debug ("Right clicked on task: " + task.Name);
+					break;
+			}
+		}
+		
+		private void OnShowTaskNotes (object sender, EventArgs args)
+		{
+			if (clickedTask == null)
+				return;
+			
+			ShowTaskNotes (clickedTask);
+		}
+		
+		private void OnDeleteTask (object sender, EventArgs args)
+		{
+			if (clickedTask == null)
+				return;
+			
+			Application.Backend.DeleteTask(clickedTask);
+		}
+
+
+		private void OnEditTask (object sender, EventArgs args)
+		{
+			if (clickedTask == null)
+				return;
+			
+			EnterEditMode (clickedTask, false);
+		}
+		
+		
+		void OnNoteDialogHidden (object sender, EventArgs args)
+		{
+			NoteDialog dialog = sender as NoteDialog;
+			if (dialog == null) {
+				Logger.Warn ("OnNoteDialogHidden (), sender is not NoteDialog, it's: {0}", sender.GetType ().ToString ());
+				return;
+			}
+			
+			if (noteDialogs.ContainsKey (dialog.Task) == false) {
+				Logger.Warn ("Closed NoteDialog not found in noteDialogs");
+				return;
+			}
+			
+			Logger.Debug ("Removing NoteDialog from noteDialogs");
+			noteDialogs.Remove (dialog.Task);
+			
+			dialog.Destroy ();
+		}
+		
+		private void OnBackendInitialized()
+		{		
+			backend.BackendInitialized -= OnBackendInitialized;
+			PopulateWindow();
+			OnBackendSyncFinished (); // To update the statusbar
+		}
+		
+		private void OnBackendSyncStarted ()
+		{
+			TaskWindow.ShowStatus (Catalog.GetString ("Reloading tasks..."));
+		}
+		
+		private void OnBackendSyncFinished ()
+		{
+			Logger.Debug("Backend sync finished");
+			string status =
+				string.Format ("Tasks loaded: {0}",DateTime.Now.ToString ());
+			TaskWindow.ShowStatus (status);
+			if (Application.Backend.Configured) {
+				RebuildAddTaskMenu (Application.Backend.Categories);
+				addTaskButton.Sensitive = true;
+			}
+		}
+		#endregion // Event Handlers
+		
+		#region Private Classes
+		class CategoryMenuItem : Gtk.MenuItem
+		{
+			private ICategory cat;
+			
+			public CategoryMenuItem (ICategory category) : base (category.Name)
+			{
+				cat = category;
+			}
+			
+			public ICategory Category
+			{
+				get { return cat; }
+			}
+		}
+		#endregion // Private Classes
+	}
+	
+	/// <summary>
+	/// Provide keybindings via a fake Gtk.Menu.
+	/// </summary>
+	public class GlobalKeybinder
+	{
+		Gtk.AccelGroup accel_group;
+		Gtk.Menu fake_menu;
+
+		/// <summary>
+		/// Create a global keybinder for the given Gtk.AccelGroup.
+		/// </summary>
+		/// </param>
+		public GlobalKeybinder (Gtk.AccelGroup accel_group)
+		{
+			this.accel_group = accel_group;
+
+			fake_menu = new Gtk.Menu ();
+			fake_menu.AccelGroup = accel_group;
+		}
+
+		/// <summary>
+		/// Add a keybinding for this keybinder's AccelGroup.
+		/// </summary>
+		/// <param name="handler">
+		/// A <see cref="EventHandler"/> for when the keybinding is
+		/// activated.
+		/// </param>
+		/// <param name="key">
+		/// A <see cref="System.UInt32"/> specifying the key that will
+		/// be bound (see the Gdk.Key enumeration for common values).
+		/// </param>
+		/// <param name="modifiers">
+		/// The <see cref="Gdk.ModifierType"/> to be used on key
+		/// for this binding.
+		/// </param>
+		/// <param name="flags">
+		/// The <see cref="Gtk.AccelFlags"/> for this binding.
+		/// </param>
+		public void AddAccelerator (EventHandler handler,
+		                            uint key,
+		                            Gdk.ModifierType modifiers,
+		                            Gtk.AccelFlags flags)
+		{
+			Gtk.MenuItem foo = new Gtk.MenuItem ();
+			foo.Activated += handler;
+			foo.AddAccelerator ("activate",
+			                    accel_group,
+			                    key,
+			                    modifiers,
+			                    flags);
+			foo.Show ();
+
+			fake_menu.Append (foo);
+		}
+	}
+}

Added: trunk/src/TrayLib.cs
==============================================================================
--- (empty file)
+++ trunk/src/TrayLib.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,270 @@
+/***************************************************************************
+ *  TrayLib.cs
+ *
+ *  Copyright (C) 2007 Novell, Inc.
+ ****************************************************************************/
+
+/*  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.Runtime.InteropServices;
+ 
+using Gtk;
+using Gdk;
+ 
+namespace Egg
+{
+	public class TrayIcon : Plug
+	{
+		//int stamp;
+		
+		// FIXEME::get set but never used - investigate
+		//Orientation orientation;
+		
+		int selection_atom;
+		//int manager_atom;
+		int system_tray_opcode_atom;
+		//int orientation_atom;
+		IntPtr manager_window;
+		//FilterFunc filter;
+		
+		public TrayIcon (string name)
+		{
+			Title = name;
+			//stamp = 1;
+			//orientation = Orientation.Horizontal;
+			AddEvents ((int)EventMask.PropertyChangeMask);
+			//filter = new FilterFunc (ManagerFilter);
+		}
+ 
+		protected override void OnRealized ()
+		{
+			base.OnRealized ();
+			Display display = Screen.Display;
+			IntPtr xdisplay = gdk_x11_display_get_xdisplay (display.Handle);
+			selection_atom = XInternAtom (xdisplay, "_NET_SYSTEM_TRAY_S" + Screen.Number.ToString (), false);
+			//manager_atom = XInternAtom (xdisplay, "MANAGER", false);
+			system_tray_opcode_atom = XInternAtom (xdisplay, "_NET_SYSTEM_TRAY_OPCODE", false);
+			//orientation_atom = XInternAtom (xdisplay, "_NET_SYSTEM_TRAY_ORIENTATION", false);
+			UpdateManagerWindow ();
+			//Screen.RootWindow.AddFilter (filter);
+		}
+ 
+		protected override void OnUnrealized ()
+		{
+			if (manager_window != IntPtr.Zero)
+			{
+				//Gdk.Window gdkwin = Gdk.Window.LookupForDisplay (Display, (uint)manager_window);
+				//gdkwin.RemoveFilter (filter);
+			}
+			
+			//Screen.RootWindow.RemoveFilter (filter);
+			base.OnUnrealized ();
+		}
+ 
+		private void UpdateManagerWindow ()
+		{
+			IntPtr xdisplay = gdk_x11_display_get_xdisplay (Display.Handle);
+			if (manager_window != IntPtr.Zero)
+			{
+				//Gdk.Window gdkwin = Gdk.Window.LookupForDisplay (Display, (uint)manager_window);
+				//gdkwin.RemoveFilter (filter);
+			}
+ 
+			XGrabServer (xdisplay);
+ 
+			manager_window = XGetSelectionOwner (xdisplay, selection_atom);
+			if (manager_window != IntPtr.Zero)
+				XSelectInput (xdisplay, manager_window, EventMask.StructureNotifyMask | EventMask.PropertyChangeMask);
+			XUngrabServer (xdisplay);
+			XFlush (xdisplay);
+ 
+			if (manager_window != IntPtr.Zero) {
+				//Gdk.Window gdkwin = Gdk.Window.LookupForDisplay (Display, (uint)manager_window);
+				//gdkwin.AddFilter (filter);
+				SendDockRequest ();
+				GetOrientationProperty ();
+			}
+		}
+ 
+		private void SendDockRequest ()
+		{
+			SendManagerMessage (SystemTrayMessage.RequestDock, manager_window, Id, 0, 0);
+		}
+ 
+		private void SendManagerMessage (SystemTrayMessage message, IntPtr window, uint data1, uint data2, uint data3)
+		{
+			XClientMessageEvent ev = new XClientMessageEvent ();
+			IntPtr display;
+ 
+			ev.type = XEventName.ClientMessage;
+			ev.window = window;
+			ev.message_type = (IntPtr)system_tray_opcode_atom;
+			ev.format = 32;
+			ev.ptr1 = gdk_x11_get_server_time (GdkWindow.Handle);
+			ev.ptr2 = (IntPtr)message;
+			ev.ptr3 = (IntPtr)data1;
+			ev.ptr4 = (IntPtr)data2;
+			ev.ptr5 = (IntPtr)data3;
+ 
+			display = gdk_x11_display_get_xdisplay (Display.Handle);
+			gdk_error_trap_push ();
+			XSendEvent (display, manager_window, false, EventMask.NoEventMask, ref ev);
+			gdk_error_trap_pop ();
+		}
+ 
+ 		/*
+		private FilterReturn ManagerFilter (IntPtr xevent, Event evnt)
+		{
+			//TODO: Implement;
+			return FilterReturn.Continue;
+		}
+		*/
+ 
+		private void GetOrientationProperty ()
+		{
+			//TODO: Implement;
+		}
+ 
+		[DllImport ("gdk-x11-2.0")]
+		static extern IntPtr gdk_x11_display_get_xdisplay (IntPtr display);
+		[DllImport ("gdk-x11-2.0")]
+		static extern IntPtr gdk_x11_get_server_time (IntPtr window);
+		[DllImport ("gdk-x11-2.0")]
+		static extern void gdk_error_trap_push ();
+		[DllImport ("gdk-x11-2.0")]
+		static extern void gdk_error_trap_pop ();
+		
+		[DllImport ("libX11", EntryPoint="XInternAtom")]
+		extern static int XInternAtom(IntPtr display, string atom_name, bool only_if_exists);
+		[DllImport ("libX11")]
+		extern static void XGrabServer (IntPtr display);
+		[DllImport ("libX11")]
+		extern static void XUngrabServer (IntPtr display);
+		[DllImport ("libX11")]
+		extern static int XFlush (IntPtr display);
+		[DllImport ("libX11")]
+		extern static IntPtr XGetSelectionOwner (IntPtr display, int atom);
+		[DllImport ("libX11")]
+		extern static IntPtr XSelectInput (IntPtr window, IntPtr display, EventMask mask);
+		[DllImport ("libX11", EntryPoint="XSendEvent")]
+		extern static int XSendEvent(IntPtr display, IntPtr window, bool propagate, EventMask event_mask, ref XClientMessageEvent send_event);
+	}
+ 
+	[Flags]
+	internal enum EventMask {
+		NoEventMask             = 0,
+		KeyPressMask            = 1<<0,
+		KeyReleaseMask          = 1<<1,
+		ButtonPressMask         = 1<<2,
+		ButtonReleaseMask       = 1<<3,
+		EnterWindowMask         = 1<<4,
+		LeaveWindowMask         = 1<<5,
+		PointerMotionMask       = 1<<6,
+		PointerMotionHintMask   = 1<<7,
+		Button1MotionMask       = 1<<8,
+		Button2MotionMask       = 1<<9,
+		Button3MotionMask       = 1<<10,
+		Button4MotionMask       = 1<<11,
+		Button5MotionMask       = 1<<12,
+		ButtonMotionMask        = 1<<13,
+		KeymapStateMask         = 1<<14,
+		ExposureMask            = 1<<15,
+		VisibilityChangeMask    = 1<<16,
+		StructureNotifyMask     = 1<<17,
+		ResizeRedirectMask      = 1<<18,
+                SubstructureNotifyMask  = 1<<19,
+		SubstructureRedirectMask= 1<<20,
+		FocusChangeMask         = 1<<21,
+		PropertyChangeMask      = 1<<22,
+		ColormapChangeMask      = 1<<23,
+		OwnerGrabButtonMask     = 1<<24
+	}
+ 
+	internal enum SystemTrayMessage
+	{
+		RequestDock,
+		BeginMessage,
+		CancelMessage
+	}
+ 
+	internal enum SystemTrayOrientation
+	{
+		Horz,
+		Vert
+	}
+	
+	[StructLayout(LayoutKind.Sequential)]
+	internal struct XClientMessageEvent {
+		internal XEventName     type;
+		internal IntPtr         serial;
+		internal bool           send_event;
+		internal IntPtr         display;
+		internal IntPtr         window;
+		internal IntPtr         message_type;
+		internal int            format;
+		internal IntPtr         ptr1;
+		internal IntPtr         ptr2;
+		internal IntPtr         ptr3;
+		internal IntPtr         ptr4;
+		internal IntPtr         ptr5;
+	}
+ 
+	internal enum XEventName {
+		KeyPress                = 2,
+		KeyRelease              = 3,
+		ButtonPress             = 4,
+		ButtonRelease           = 5,
+		MotionNotify            = 6,
+		EnterNotify             = 7,
+		LeaveNotify             = 8,
+		FocusIn                 = 9,
+		FocusOut                = 10,
+		KeymapNotify            = 11,
+		Expose                  = 12,
+		GraphicsExpose          = 13,
+		NoExpose                = 14,
+		VisibilityNotify        = 15,
+		CreateNotify            = 16,
+		DestroyNotify           = 17,
+		UnmapNotify             = 18,
+		MapNotify               = 19,
+		MapRequest              = 20,
+		ReparentNotify          = 21,
+		ConfigureNotify         = 22,
+		ConfigureRequest        = 23,
+		GravityNotify           = 24,
+		ResizeRequest           = 25,
+		CirculateNotify         = 26,
+		CirculateRequest        = 27,
+		PropertyNotify          = 28,
+		SelectionClear          = 29,
+		SelectionRequest        = 30,
+		SelectionNotify         = 31,
+		ColormapNotify          = 32,
+		ClientMessage           = 33,
+		MappingNotify           = 34,
+		TimerNotify             = 100,
+		
+		LASTEvent
+	}
+}// created on 3/16/2007 at 5:33 PM
\ No newline at end of file

Added: trunk/src/Utilities.cs
==============================================================================
--- (empty file)
+++ trunk/src/Utilities.cs	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,339 @@
+/***************************************************************************
+ *  Utilities.cs
+ *
+ *  Copyright (C) 2007 Novell, Inc.
+ *  Written by:
+ * 		Calvin Gaisford <calvinrg gmail com>
+ *		Boyd Timothy <btimothy 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.Runtime.InteropServices;
+using System.Text;
+using System.Net;
+using System.IO;
+using System.Security.Cryptography;
+using Mono.Unix;
+
+
+namespace Tasquer
+{
+	internal class Utilities
+	{
+		[DllImport("libc")]
+		private static extern int prctl(int option, byte [] arg2, 
+					IntPtr arg3, IntPtr arg4, IntPtr arg5);
+
+		public static void SetProcessName(string name)
+		{
+			// 15 = PR_SET_NAME
+			if(prctl(15, Encoding.ASCII.GetBytes(name + "\0"), IntPtr.Zero, IntPtr.Zero, IntPtr.Zero) != 0)
+			{
+				throw new ApplicationException("Error setting process name: " + Mono.Unix.Native.Stdlib.GetLastError());
+			}
+		}
+
+		public static string ReplaceString (string originalString, string searchString, string replaceString)
+		{
+			return Utilities.ReplaceString (originalString, searchString, replaceString, false);
+		}
+
+		public static string EscapeForJavaScript (string text)
+		{
+			// Replace all single quote characters with: &apos;
+			text = ReplaceString (text, "'", "&apos;", true);
+
+			// Replace all double quote characters with: &quot;
+			text = ReplaceString (text, "\"", "&quot;", true);
+
+			return text;
+		}
+
+		public static string ReplaceString (
+				string originalString,
+				string searchString,
+				string replaceString,
+				bool replaceAllOccurrences)
+		{
+			string replacedString = originalString;
+			int pos = replacedString.IndexOf (searchString);
+			while (pos >= 0) {
+				replacedString = string.Format (
+						"{0}{1}{2}",
+						replacedString.Substring (0, pos),
+						replaceString,
+						replacedString.Substring (pos + searchString.Length));
+
+				if (!replaceAllOccurrences)
+					break;
+
+				pos = replacedString.IndexOf (searchString);
+			}
+
+			return replacedString;
+		}
+
+		public static Gdk.Pixbuf GetIcon (string iconName, int size)
+		{
+			try {
+				return Gtk.IconTheme.Default.LoadIcon (iconName, size, 0);
+			} catch (GLib.GException) {}
+
+			try {
+				Gdk.Pixbuf ret = new Gdk.Pixbuf (null, iconName + ".png");
+				return ret.ScaleSimple (size, size, Gdk.InterpType.Bilinear);
+			} catch (ArgumentException) {}
+
+			Logger.Debug ("Unable to load icon '{0}'.", iconName);
+			return null;
+		}
+
+		public static bool ParseNameValuePair (string pair, out string name, out string nameValue)
+		{
+			name = null;
+			nameValue = null;
+			if (pair == null || pair.Trim ().Length == 0 || pair.IndexOf ("=") <= 0)
+				//				throw new ArgumentException ("The pair passed in does not contain a valid name/value");
+				return false;
+
+			int equalsPos = pair.IndexOf ("=");
+			name = pair.Substring (0, equalsPos);
+
+			if (pair.Length <= equalsPos)
+				return false; // Not enough room for a value to exist
+
+			nameValue = pair.Substring (equalsPos + 1);
+
+			if (name == null || name.Length < 1 || nameValue == null || nameValue.Length < 1)
+				return false;
+
+			return true;
+		}
+
+		/// <summary>
+		/// Create the specified path if needed
+		/// <param name="path">The path to create if it does not exist</param>
+		/// </summary>
+		public static void CreateDirectoryIfNeeded (string path)
+		{
+			if (System.IO.Directory.Exists (path) == false) {
+				try {
+					System.IO.Directory.CreateDirectory (path);
+				} catch {
+					Logger.Warn ("Couldn't create the directory: {0}", path);
+				}
+			}
+		}
+
+
+		public static Gdk.Pixbuf GetPhotoFromUri(string uri)
+		{
+			Gdk.Pixbuf pixbuf = null;
+
+			System.Net.HttpWebRequest request = (HttpWebRequest) HttpWebRequest.Create(uri);
+
+			// Send request to send the file
+			request.Method = "GET";
+			//request.ContentLength = 0;
+			// request.GetRequestStream().Close();
+
+			// Read the response to the request
+			HttpWebResponse response = (HttpWebResponse)request.GetResponse();
+
+			if( response.StatusCode == HttpStatusCode.OK ) {
+				Logger.Debug("Response was OK");
+
+				byte[] buffer = new byte[response.ContentLength];
+				int sizeRead = 0;
+				int totalRead = 0;
+				Stream stream = response.GetResponseStream();
+				Logger.Debug("About to read photo of size {0}", response.ContentLength);
+
+				try {
+
+					do {
+						sizeRead = stream.Read(buffer, totalRead, (int)(response.ContentLength - totalRead));
+						totalRead += sizeRead;
+						Logger.Debug("SizeRead = {0}, totalRead = {1}", sizeRead, totalRead);
+					} while( (sizeRead > 0) && (totalRead < response.ContentLength) );
+
+					Logger.Debug("We Read the photo and it's {0} bytes", totalRead);
+					Logger.Debug("The content length is {0} bytes", response.ContentLength);
+
+					stream.Close();
+				} catch (Exception e) {
+					Logger.Debug("Exception when reading file from stream: {0}", e.Message);
+					Logger.Debug("Exception {0}", e);
+				}
+
+				pixbuf = new Gdk.Pixbuf(buffer);
+
+			} else {
+				Logger.Debug("Unable to get the photo because {0}", response.StatusDescription);
+			}
+			return pixbuf;
+		}
+
+		public static string GetGravatarUri(string gravatarID)
+		{
+			System.Text.StringBuilder image = new System.Text.StringBuilder();
+			image.Append("http://www.gravatar.com/avatar.php?";);
+			image.Append("gravatar_id=");
+			image.Append(gravatarID);
+			//image.Append("&rating=G");
+			image.Append("&size=48");
+
+			Logger.Debug("The Gravatar Uri is {0}", image.ToString());
+
+			return image.ToString();
+		}
+
+
+		// Create an md5 sum string of this string
+		public static string GetMd5Sum(string str)
+		{
+			System.Security.Cryptography.MD5CryptoServiceProvider x = 
+				new System.Security.Cryptography.MD5CryptoServiceProvider();
+
+			byte[] bs = System.Text.Encoding.UTF8.GetBytes(str);
+			bs = x.ComputeHash(bs);
+
+			System.Text.StringBuilder s = new System.Text.StringBuilder();
+			foreach (byte b in bs)
+			{
+				s.Append(b.ToString("x2").ToLower());
+			}
+
+			return s.ToString();
+			/*
+
+
+
+			// First we need to convert the string into bytes, which
+			// means using a text encoder.
+			Encoder enc = System.Text.Encoding.Unicode.GetEncoder();
+
+			// Create a buffer large enough to hold the string
+			byte[] unicodeText = new byte[str.Length * 2];
+			enc.GetBytes(str.ToCharArray(), 0, str.Length, unicodeText, 0, true);
+
+			// Now that we have a byte array we can ask the CSP to hash it
+			MD5 md5 = new MD5CryptoServiceProvider();
+			byte[] result = md5.ComputeHash(unicodeText);
+
+			// Build the final string by converting each byte
+			// into hex and appending it to a StringBuilder
+			StringBuilder sb = new StringBuilder();
+			for (int i=0;i<result.Length;i++)
+			{
+			sb.Append(result[i].ToString("X2"));
+			}
+
+			// And return it
+			return sb.ToString();
+			 */
+		}
+		
+		/// <summary>
+		/// Get a string that is more friendly/pretty for the specified date.
+		/// For example, "Today, 3:00 PM", "4 days ago, 9:20 AM".
+		/// <param name="date">The DateTime to evaluate</param>
+		/// <param name="show_time">If true, output the time along with the
+		/// date</param>
+		/// </summary>
+		public static string GetPrettyPrintDate (DateTime date, bool show_time)
+		{
+			string pretty_str = String.Empty;
+			DateTime now = DateTime.Now;
+			string short_time = date.ToShortTimeString ();
+
+			if (date.Year == now.Year) {
+				if (date.DayOfYear == now.DayOfYear)
+					pretty_str = show_time ?
+					             String.Format (Catalog.GetString ("Today, {0}"),
+					                            short_time) :
+					             Catalog.GetString ("Today");
+				else if (date.DayOfYear < now.DayOfYear
+				                && date.DayOfYear == now.DayOfYear - 1)
+					pretty_str = show_time ?
+					             String.Format (Catalog.GetString ("Yesterday, {0}"),
+					                            short_time) :
+					             Catalog.GetString ("Yesterday");
+				else if (date.DayOfYear < now.DayOfYear
+				                && date.DayOfYear > now.DayOfYear - 6)
+					pretty_str = show_time ?
+					             String.Format (Catalog.GetString ("{0} days ago, {1}"),
+					                            now.DayOfYear - date.DayOfYear, short_time) :
+					             String.Format (Catalog.GetString ("{0} days ago"),
+					                            now.DayOfYear - date.DayOfYear);
+				else if (date.DayOfYear > now.DayOfYear
+				                && date.DayOfYear == now.DayOfYear + 1)
+					pretty_str = show_time ?
+					             String.Format (Catalog.GetString ("Tomorrow, {0}"),
+					                            short_time) :
+					             Catalog.GetString ("Tomorrow");
+				else if (date.DayOfYear > now.DayOfYear
+				                && date.DayOfYear < now.DayOfYear + 6)
+					pretty_str = show_time ?
+					             String.Format (Catalog.GetString ("In {0} days, {1}"),
+					                            date.DayOfYear - now.DayOfYear, short_time) :
+					             String.Format (Catalog.GetString ("In {0} days"),
+					                            date.DayOfYear - now.DayOfYear);
+				else
+					pretty_str = show_time ?
+					             date.ToString (Catalog.GetString ("MMMM d, h:mm tt")) :
+					             date.ToString (Catalog.GetString ("MMMM d"));
+			} else if (date == DateTime.MinValue)
+				pretty_str = Catalog.GetString ("No Date");
+			else
+				pretty_str = show_time ?
+				             date.ToString (Catalog.GetString ("MMMM d yyyy, h:mm tt")) :
+				             date.ToString (Catalog.GetString ("MMMM d yyyy"));
+
+			return pretty_str;
+		}
+		
+		public static string GetLocalizedDayOfWeek (System.DayOfWeek dayOfWeek)
+		{
+			switch (dayOfWeek) {
+			case DayOfWeek.Sunday:
+				return Catalog.GetString ("Sunday");
+			case DayOfWeek.Monday:
+				return Catalog.GetString ("Monday");
+			case DayOfWeek.Tuesday:
+				return Catalog.GetString ("Tuesday");
+			case DayOfWeek.Wednesday:
+				return Catalog.GetString ("Wednesday");
+			case DayOfWeek.Thursday:
+				return Catalog.GetString ("Thursday");
+			case DayOfWeek.Friday:
+				return Catalog.GetString ("Friday");
+			case DayOfWeek.Saturday:
+				return Catalog.GetString ("Saturday");
+			}
+			
+			return string.Empty;
+		}
+	}
+}

Added: trunk/src/tasquer.in
==============================================================================
--- (empty file)
+++ trunk/src/tasquer.in	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+prefix="@prefix@"
+exec_prefix="@exec_prefix@"
+libdir="${prefix}/lib/tasquer"
+
+cd ${libdir}
+
+exec -a "Tasquer" mono "Tasquer.exe" "$@"

Added: trunk/src/tasquer.pc.in
==============================================================================
--- (empty file)
+++ trunk/src/tasquer.pc.in	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,6 @@
+Name: Tasquer
+Description: Simple Task Management
+Version: 0.1
+
+Requires: 
+Libs: -r:@prefix@/lib/@PACKAGE@/Tasquer.exe

Added: trunk/tasquer-opensuse-1click.png
==============================================================================
Binary file. No diff available.

Added: trunk/tasquer.mdp
==============================================================================
--- (empty file)
+++ trunk/tasquer.mdp	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,89 @@
+<Project name="tasquer" fileversion="2.0" language="C#" clr-version="Net_2_0" ctype="DotNetProject">
+  <Configurations active="Debug">
+    <Configuration name="Debug" ctype="DotNetProjectConfiguration">
+      <Output directory="bin/Debug" assembly="tasquer" />
+      <Build debugmode="True" target="Exe" />
+      <Execution runwithwarnings="True" consolepause="True" runtime="MsNet" clr-version="Net_2_0" />
+      <CodeGeneration compiler="Mcs" warninglevel="4" optimize="True" unsafecodeallowed="False" generateoverflowchecks="True" generatexmldocumentation="False" ctype="CSharpCompilerParameters" />
+    </Configuration>
+    <Configuration name="Release" ctype="DotNetProjectConfiguration">
+      <Output directory="bin/Release" assembly="tasquer" />
+      <Build debugmode="False" target="Exe" />
+      <Execution runwithwarnings="True" consolepause="True" runtime="MsNet" clr-version="Net_2_0" />
+      <CodeGeneration compiler="Mcs" warninglevel="4" optimize="True" unsafecodeallowed="False" generateoverflowchecks="True" generatexmldocumentation="False" ctype="CSharpCompilerParameters" />
+    </Configuration>
+  </Configurations>
+  <Contents>
+    <File name="src/Application.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Defines.cs.in" subtype="Code" buildaction="Nothing" />
+    <File name="src/Logger.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Preferences.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/PreferencesDialog.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/TaskWindow.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/TrayLib.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Utilities.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/CellRendererDate.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/DateButton.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/TaskTreeView.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/ITask.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Backends" subtype="Directory" buildaction="Compile" />
+    <File name="src/Backends/Rtm" subtype="Directory" buildaction="Compile" />
+    <File name="src/Backends/Rtm/RtmBackend.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Backends/Rtm/RtmTask.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/ICategory.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Backends/Rtm/RtmCategory.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Backends/Dummy" subtype="Directory" buildaction="Compile" />
+    <File name="src/Backends/Dummy/DummyBackend.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Backends/Dummy/DummyCategory.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Backends/Dummy/DummyTask.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/TaskPriority.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/TaskGroup.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/AbstractTask.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/IBackend.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/TaskState.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/INote.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Backends/Dummy/DummyNote.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Backends/Rtm/RtmNote.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/AllCategory.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/TaskCalendar.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/NoteDialog.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/NoteWidget.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Backends/IceCore" subtype="Directory" buildaction="Compile" />
+    <File name="src/Backends/IceCore/IceBackend.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Backends/IceCore/IceCategory.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Backends/IceCore/IceNote.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Backends/IceCore/IceTask.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/RemoteControl.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/RemoteControlProxy.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Backends/Sqlite/SqliteBackend.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Backends/Sqlite/SqliteCategory.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Backends/Sqlite/SqliteNote.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Backends/Sqlite/SqliteTask.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Backends/Sqlite/Database.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/Backends/Rtm/RtmPreferencesWidget.cs" subtype="Code" buildaction="Compile" />
+    <File name="src/CompletedTaskGroup.cs" subtype="Code" buildaction="Compile" />
+  </Contents>
+  <References>
+    <ProjectReference type="Gac" localcopy="True" refto="glib-sharp, Version=2.10.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
+    <ProjectReference type="Gac" localcopy="True" refto="gdk-sharp, Version=2.10.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
+    <ProjectReference type="Gac" localcopy="True" refto="gtk-sharp, Version=2.10.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
+    <ProjectReference type="Gac" localcopy="True" refto="notify-sharp, Version=0.4.0.0, Culture=neutral, PublicKeyToken=2df29c54e245917a" />
+    <ProjectReference type="Assembly" localcopy="True" refto="RtmNet/RtmNet.dll" />
+    <ProjectReference type="Gac" localcopy="True" refto="System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
+    <ProjectReference type="Gac" localcopy="True" refto="Mono.Posix, Version=2.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756" />
+    <ProjectReference type="Gac" localcopy="True" refto="NDesk.DBus, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f6716e4f9b2ed099" />
+    <ProjectReference type="Gac" localcopy="True" refto="NDesk.DBus.GLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f6716e4f9b2ed099" />
+    <ProjectReference type="Gac" localcopy="True" refto="Mono.Data.Sqlite, Version=2.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756" />
+    <ProjectReference type="Gac" localcopy="True" refto="Mono.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756" />
+    <ProjectReference type="Gac" localcopy="True" refto="System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
+  </References>
+  <MonoDevelop.Autotools.MakefileInfo IntegrationEnabled="True" RelativeMakefileName="Makefile.am" IsAutotoolsProject="True" RelativeConfigureInPath=".">
+    <BuildFilesVar />
+    <DeployFilesVar />
+    <ResourcesVar />
+    <OthersVar />
+    <GacRefVar />
+    <AsmRefVar />
+    <ProjectRefVar />
+  </MonoDevelop.Autotools.MakefileInfo>
+</Project>

Added: trunk/tasquer.mds
==============================================================================
--- (empty file)
+++ trunk/tasquer.mds	Tue Mar 11 23:04:59 2008
@@ -0,0 +1,21 @@
+<Combine name="tasquer" fileversion="2.0">
+  <Configurations active="Debug">
+    <Configuration name="Debug" ctype="CombineConfiguration">
+      <Entry build="True" name="tasquer" configuration="Debug" />
+      <Entry build="True" name="RtmNet" configuration="Debug" />
+    </Configuration>
+    <Configuration name="Release" ctype="CombineConfiguration">
+      <Entry build="True" name="tasquer" configuration="Release" />
+      <Entry build="True" name="RtmNet" configuration="Release" />
+    </Configuration>
+  </Configurations>
+  <StartMode startupentry="tasquer" single="True">
+    <Execute type="None" entry="tasquer" />
+    <Execute type="None" entry="RtmNet" />
+    <Execute type="None" entry="ICalParser.Net" />
+  </StartMode>
+  <Entries>
+    <Entry filename="tasquer.mdp" />
+    <Entry filename="RtmNet/RtmNet.mdp" />
+  </Entries>
+</Combine>



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