--- /dev/null
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ *
+ * Authors: Travis Reitter <treitter@gmail.com>
+ */
+
+#include <config.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <hildon/hildon.h>
+#include <rtm-glib/rtm-glib.h>
+
+#include "milk-task-model.h"
+#include "milk-auth.h"
+
+static void
+milk_task_model_tree_model_init (GtkTreeModelIface *iface);
+
+G_DEFINE_TYPE_EXTENDED (MilkTaskModel,
+ milk_task_model,
+ G_TYPE_OBJECT,
+ 0,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,
+ milk_task_model_tree_model_init));
+
+/* less expensive than G_TYPE_INSTANCE_GET_PRIVATE */
+#define MILK_TASK_MODEL_PRIVATE(o) ((MILK_TASK_MODEL ((o)))->priv)
+
+/* FIXME: make this configurable at runtime, pref. as a gconf value */
+/* time between syncing with the server, in ms */
+#define MODEL_UPDATE_PERIOD 60000
+
+struct _MilkTaskModelPrivate
+{
+ GHashTable *tasks;
+ GtkListStore *store;
+ MilkAuth *auth;
+ guint update_id;
+ char *last_sync;
+};
+
+enum {
+ PROP_AUTH = 1,
+};
+
+static gboolean
+task_is_finished (RtmTask *task)
+{
+ return (rtm_task_get_completed_date (task) ||
+ rtm_task_get_deleted_date (task));
+}
+
+static GtkTreeModelFlags
+milk_task_model_get_flags (GtkTreeModel *model)
+{
+ return GTK_TREE_MODEL_LIST_ONLY;
+}
+
+static gint
+milk_task_model_get_n_columns (GtkTreeModel *model)
+{
+ return MILK_TASK_MODEL_N_COLUMNS;
+}
+
+static GType
+milk_task_model_get_column_type (GtkTreeModel *model,
+ gint column)
+{
+ switch (column) {
+ case MILK_TASK_MODEL_COLUMN_TASK:
+ return RTM_TYPE_TASK;
+
+ default:
+ g_warning (G_STRLOC ": invalid column: %d", column);
+ return G_TYPE_INVALID;
+ }
+}
+
+static gboolean
+milk_task_model_get_iter (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GtkTreePath *path)
+{
+ MilkTaskModelPrivate *priv;
+
+ g_return_val_if_fail (MILK_IS_TASK_MODEL (model), FALSE);
+ g_return_val_if_fail (iter, FALSE);
+ g_return_val_if_fail (gtk_tree_path_get_depth (path) == 1, FALSE);
+
+ priv = MILK_TASK_MODEL_PRIVATE (model);
+
+ return gtk_tree_model_get_iter (
+ GTK_TREE_MODEL (priv->store), iter, path);
+}
+
+static GtkTreePath*
+milk_task_model_get_path (GtkTreeModel *model,
+ GtkTreeIter *iter)
+{
+ MilkTaskModelPrivate *priv;
+
+ g_return_val_if_fail (MILK_IS_TASK_MODEL (model), NULL);
+ g_return_val_if_fail (iter, NULL);
+
+ priv = MILK_TASK_MODEL_PRIVATE (model);
+
+ return gtk_tree_model_get_path (GTK_TREE_MODEL (priv->store), iter);
+}
+
+static void
+milk_task_model_get_value (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gint column,
+ GValue *value)
+{
+ MilkTaskModelPrivate *priv;
+
+ g_return_if_fail (MILK_IS_TASK_MODEL (model));
+ g_return_if_fail (iter);
+ g_return_if_fail (value);
+
+ priv = MILK_TASK_MODEL_PRIVATE (model);
+
+ gtk_tree_model_get_value (
+ GTK_TREE_MODEL (priv->store), iter, column, value);
+}
+
+static gboolean
+milk_task_model_iter_next (GtkTreeModel *model,
+ GtkTreeIter *iter)
+{
+ MilkTaskModelPrivate *priv;
+
+ g_return_val_if_fail (MILK_IS_TASK_MODEL (model), FALSE);
+ g_return_val_if_fail (iter, FALSE);
+
+ priv = MILK_TASK_MODEL_PRIVATE (model);
+
+ return gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->store), iter);
+}
+
+static gboolean
+milk_task_model_iter_nth_child (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent,
+ gint index)
+{
+ MilkTaskModelPrivate *priv;
+
+ g_return_val_if_fail (MILK_IS_TASK_MODEL (model), FALSE);
+ g_return_val_if_fail (iter, FALSE);
+ /* we're one-dimensional */
+ g_return_val_if_fail (!parent, FALSE);
+ g_return_val_if_fail (index >= 0, FALSE);
+
+ priv = MILK_TASK_MODEL_PRIVATE (model);
+
+ return gtk_tree_model_iter_nth_child (
+ GTK_TREE_MODEL (priv->store), iter, parent, index);
+}
+
+static gboolean
+milk_task_model_iter_children (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent)
+{
+ MilkTaskModelPrivate *priv;
+
+ g_return_val_if_fail (MILK_IS_TASK_MODEL (model), FALSE);
+ g_return_val_if_fail (iter, FALSE);
+ /* we're one-dimensional */
+ g_return_val_if_fail (!parent, FALSE);
+
+ priv = MILK_TASK_MODEL_PRIVATE (model);
+
+ return gtk_tree_model_iter_children (
+ GTK_TREE_MODEL (priv->store), iter, parent);
+}
+
+static gboolean
+milk_task_model_iter_has_child (GtkTreeModel *model,
+ GtkTreeIter *iter)
+{
+ MilkTaskModelPrivate *priv;
+
+ g_return_val_if_fail (MILK_IS_TASK_MODEL (model), FALSE);
+ g_return_val_if_fail (iter, FALSE);
+
+ priv = MILK_TASK_MODEL_PRIVATE (model);
+
+ return gtk_tree_model_iter_has_child (
+ GTK_TREE_MODEL (priv->store), iter);
+}
+
+static gint
+milk_task_model_iter_n_children (GtkTreeModel *model,
+ GtkTreeIter *iter)
+{
+ MilkTaskModelPrivate *priv;
+
+ g_return_val_if_fail (MILK_IS_TASK_MODEL (model), -1);
+ g_return_val_if_fail (iter, -1);
+
+ priv = MILK_TASK_MODEL_PRIVATE (model);
+
+ /* we're one-dimensional */
+ if (iter)
+ return 0;
+
+ return gtk_tree_model_iter_n_children (
+ GTK_TREE_MODEL (priv->store), iter);
+}
+
+static gboolean
+milk_task_model_iter_parent (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GtkTreeIter *child)
+{
+ MilkTaskModelPrivate *priv;
+
+ g_return_val_if_fail (MILK_IS_TASK_MODEL (model), FALSE);
+ g_return_val_if_fail (iter, FALSE);
+ g_return_val_if_fail (child, FALSE);
+
+ priv = MILK_TASK_MODEL_PRIVATE (model);
+
+ return gtk_tree_model_iter_parent (
+ GTK_TREE_MODEL (priv->store), iter, child);
+}
+
+static gboolean
+model_store_find_task (MilkTaskModel *model,
+ RtmTask *task_in,
+ GtkTreeIter *iter_in)
+{
+ MilkTaskModelPrivate *priv = MILK_TASK_MODEL_PRIVATE (model);
+ gboolean valid;
+ GtkTreeIter iter;
+ gboolean found = FALSE;
+
+ valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->store),
+ &iter);
+ while (valid && !found) {
+ RtmTask *task;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (priv->store), &iter,
+ MILK_TASK_MODEL_COLUMN_TASK, &task,
+ -1);
+
+ if (!g_strcmp0 (rtm_task_get_id (task_in),
+ rtm_task_get_id (task))) {
+ *iter_in = iter;
+ found = TRUE;
+ }
+
+ g_object_unref (task);
+
+ valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->store),
+ &iter);
+ }
+
+ return found;
+}
+
+static gboolean
+update_model (MilkTaskModel *model)
+{
+ MilkTaskModelPrivate *priv = MILK_TASK_MODEL_PRIVATE (model);
+ GList *rtm_tasks;
+ GList *l;
+ GTimeVal current_time;
+ char *new_sync;
+ GError *error = NULL;
+
+ if (milk_auth_get_state (priv->auth) != MILK_AUTH_STATE_CONNECTED) {
+ return TRUE;
+ }
+
+ g_get_current_time (¤t_time);
+ new_sync = g_time_val_to_iso8601 (¤t_time);
+ rtm_tasks = milk_auth_get_tasks (priv->auth, priv->last_sync, &error);
+
+ if (error) {
+ g_error (G_STRLOC ": failed to retrieve latest tasks: %s",
+ error->message);
+ g_clear_error (&error);
+ } else {
+ g_free (priv->last_sync);
+ priv->last_sync = new_sync;
+ }
+
+ /* Populate model */
+ for (l = rtm_tasks; l; l = g_list_delete_link (l, l)) {
+ GtkTreeIter iter;
+ RtmTask *rtm_task;
+ const char *id;
+ gboolean task_in_store;
+
+ rtm_task = RTM_TASK (l->data);
+
+ id = rtm_task_get_id (rtm_task);
+ g_hash_table_insert (priv->tasks, g_strdup (id),
+ g_object_ref (rtm_task));
+
+ task_in_store = model_store_find_task (model, rtm_task, &iter);
+
+ /* Task is deleted or completed */
+ if (task_is_finished (rtm_task)) {
+ if (task_in_store) {
+ gtk_list_store_remove (priv->store, &iter);
+ }
+ /* Task has been changed */
+ } else if (task_in_store) {
+ RtmTask *old_task;
+ GtkTreePath *path;
+
+ /* rtm-glib doesn't re-use task structs when they're
+ * updated, so we have to replace the changed */
+ gtk_tree_model_get (
+ GTK_TREE_MODEL (priv->store), &iter,
+ MILK_TASK_MODEL_COLUMN_TASK, &old_task,
+ -1);
+
+ gtk_list_store_set (
+ priv->store, &iter,
+ MILK_TASK_MODEL_COLUMN_TASK, rtm_task,
+ -1);
+
+ path = gtk_tree_model_get_path (
+ GTK_TREE_MODEL (priv->store), &iter);
+ gtk_tree_model_row_changed (
+ GTK_TREE_MODEL (priv->store),
+ path, &iter);
+ gtk_tree_path_free (path);
+
+ g_object_unref (old_task);
+
+ /* Task is new */
+ } else {
+ gtk_list_store_append (priv->store, &iter);
+ gtk_list_store_set (
+ priv->store, &iter,
+ MILK_TASK_MODEL_COLUMN_TASK, rtm_task,
+ -1);
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+auth_notify_cb (MilkAuth *auth,
+ GParamSpec *spec,
+ MilkTaskModel *model)
+{
+ if (milk_auth_get_state (auth) == MILK_AUTH_STATE_CONNECTED) {
+ update_model (model);
+ }
+}
+
+static void
+row_changed_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ MilkTaskModel *self)
+{
+ gtk_tree_model_row_changed (GTK_TREE_MODEL (self), path, iter);
+}
+
+static void
+row_deleted_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ MilkTaskModel *self)
+{
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL (self), path);
+}
+
+static void
+row_inserted_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ MilkTaskModel *self)
+{
+ gtk_tree_model_row_inserted (GTK_TREE_MODEL (self), path, iter);
+}
+
+static void
+rows_reordered_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gint *new_order,
+ MilkTaskModel *self)
+{
+ gtk_tree_model_rows_reordered (GTK_TREE_MODEL (self), path, NULL,
+ new_order);
+}
+
+void
+milk_task_model_set_auth (MilkTaskModel *model,
+ MilkAuth *auth)
+{
+ MilkTaskModelPrivate *priv;
+
+ g_return_if_fail (model);
+ g_return_if_fail (MILK_IS_TASK_MODEL (model));
+ g_return_if_fail (auth);
+ g_return_if_fail (MILK_IS_AUTH (auth));
+
+ priv = MILK_TASK_MODEL_PRIVATE (model);
+
+ if (priv->auth) {
+ g_object_unref (priv->auth);
+ }
+ priv->auth = g_object_ref (auth);
+
+ if (priv->update_id) {
+ g_source_remove (priv->update_id);
+ }
+ priv->update_id = g_timeout_add (MODEL_UPDATE_PERIOD,
+ (GSourceFunc) update_model, model);
+
+ gtk_list_store_clear (priv->store);
+
+ g_signal_connect (priv->auth, "notify::state",
+ G_CALLBACK (auth_notify_cb), model);
+ auth_notify_cb (priv->auth, NULL, model);
+}
+
+static void
+milk_task_model_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ MilkTaskModelPrivate *priv = MILK_TASK_MODEL_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_AUTH:
+ g_value_set_object (value, priv->auth);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id,
+ pspec);
+ }
+}
+
+static void
+milk_task_model_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ case PROP_AUTH:
+ milk_task_model_set_auth (MILK_TASK_MODEL (object),
+ g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id,
+ pspec);
+ }
+}
+
+static void
+milk_task_model_dispose (GObject *object)
+{
+ MilkTaskModelPrivate *priv = MILK_TASK_MODEL_PRIVATE (object);
+
+ if (priv->auth) {
+ g_object_unref (priv->auth);
+ priv->auth = NULL;
+ }
+
+ g_signal_handlers_disconnect_by_func (priv->store, row_changed_cb,
+ object);
+ g_signal_handlers_disconnect_by_func (priv->store, row_deleted_cb,
+ object);
+ g_signal_handlers_disconnect_by_func (priv->store, row_inserted_cb,
+ object);
+ g_signal_handlers_disconnect_by_func (priv->store, rows_reordered_cb,
+ object);
+
+ if (priv->store) {
+ g_object_unref (priv->store);
+ priv->store = NULL;
+ }
+
+ if (priv->update_id) {
+ g_source_remove (priv->update_id);
+ priv->update_id = 0;
+ }
+
+ if (priv->tasks) {
+ g_hash_table_destroy (priv->tasks);
+ priv->tasks = NULL;
+ }
+
+ G_OBJECT_CLASS (milk_task_model_parent_class)->dispose (object);
+}
+
+static void
+milk_task_model_finalize (GObject *object)
+{
+ MilkTaskModelPrivate *priv = MILK_TASK_MODEL_PRIVATE (object);
+
+ g_free (priv->last_sync);
+
+ G_OBJECT_CLASS (milk_task_model_parent_class)->finalize (object);
+}
+
+static void
+milk_task_model_class_init (MilkTaskModelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (MilkTaskModelPrivate));
+
+ object_class->get_property = milk_task_model_get_property;
+ object_class->set_property = milk_task_model_set_property;
+ object_class->dispose = milk_task_model_dispose;
+ object_class->finalize = milk_task_model_finalize;
+
+ g_object_class_install_property
+ (object_class,
+ PROP_AUTH,
+ g_param_spec_object
+ ("auth",
+ "Authentication proxy",
+ "Remember The Milk authentication proxy.",
+ MILK_TYPE_AUTH,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+milk_task_model_init (MilkTaskModel *self)
+{
+ MilkTaskModelPrivate *priv;
+
+ self->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (
+ self, MILK_TYPE_TASK_MODEL, MilkTaskModelPrivate);
+
+ priv->last_sync = NULL;
+
+ priv->tasks = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_object_unref);
+
+ priv->store = gtk_list_store_new (
+ MILK_TASK_MODEL_N_COLUMNS, RTM_TYPE_TASK);
+
+ g_signal_connect (priv->store, "row-changed",
+ G_CALLBACK (row_changed_cb), self);
+
+ g_signal_connect (priv->store, "row-deleted",
+ G_CALLBACK (row_deleted_cb), self);
+
+ g_signal_connect (priv->store, "row-inserted",
+ G_CALLBACK (row_inserted_cb), self);
+
+ g_signal_connect (priv->store, "rows-reordered",
+ G_CALLBACK (rows_reordered_cb), self);
+}
+
+static void
+milk_task_model_tree_model_init (GtkTreeModelIface *iface)
+{
+ iface->get_flags = milk_task_model_get_flags;
+ iface->get_n_columns = milk_task_model_get_n_columns;
+ iface->get_column_type = milk_task_model_get_column_type;
+ iface->get_iter = milk_task_model_get_iter;
+ iface->get_path = milk_task_model_get_path;
+ iface->get_value = milk_task_model_get_value;
+ iface->iter_next = milk_task_model_iter_next;
+ iface->iter_children = milk_task_model_iter_children;
+ iface->iter_has_child = milk_task_model_iter_has_child;
+ iface->iter_n_children = milk_task_model_iter_n_children;
+ iface->iter_nth_child = milk_task_model_iter_nth_child;
+ iface->iter_parent = milk_task_model_iter_parent;
+}
+
+MilkTaskModel*
+milk_task_model_new (MilkAuth *auth)
+{
+ return g_object_new (MILK_TYPE_TASK_MODEL, "auth", auth, NULL);
+}