Logo Search packages:      
Sourcecode: tasks version File versions

openmoko-tasks.c

/*
 * Derived from test.c
 *
 * Original Copyright (C) 2007 OpenedHand Ltd
 * Modifications: Copyright (C) 2007 OpenMoko Inc.
 * Modifications made by Rob Bradford <rob@o-hand.com>
 *
 * 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
 */

#include <config.h>
#include <string.h>
#include <libecal/e-cal.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>

#include "ical-util.h"
#include <libkoto/koto-category-group.h>
#include <libkoto/koto-all-group.h>
#include <libkoto/koto-no-category-group.h>
#include <libkoto/koto-meta-group.h>
#include <libkoto/koto-group-store.h>
#include "koto-hint-entry.h"
#include "koto-task.h"
#include "koto-task-editor.h"
#include "koto-task-store.h"
#include "koto-task-view.h"
#include "koto-group-filter-model.h"
#include "koto-group-combo.h"
#include "koto-platform.h"

#include <libmokoui/moko-paned-window.h>
#include <libmokoui/moko-scrolled-pane.h>

static GtkWidget *window, *combo, *treeview, *details_pane;
static GtkWidget *editor = NULL;

static ECal *cal;
static GtkTreeModel *task_store, *filter, *group_filter_store, *group_selector_store;

static gboolean
select_uid (char *uid)
{
  GtkTreeSelection *selection;
  GtkTreeIter iter, real_iter;
  GtkTreePath *path;

  g_assert (uid);
  
  if (koto_task_store_get_iter_for_uid (KOTO_TASK_STORE (task_store), uid, &iter)) {
    selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
    
    gtk_tree_model_filter_convert_child_iter_to_iter (GTK_TREE_MODEL_FILTER (filter),
                                                      &real_iter, &iter);
    
    path = gtk_tree_model_get_path (filter, &real_iter);
    gtk_tree_view_set_cursor (GTK_TREE_VIEW (treeview), path, NULL, FALSE);
    gtk_tree_path_free (path);
  }
  
  g_free (uid);
  return FALSE;
}

/* Checks for pending changes to a task in the editor and then commits them if necessary */
static void
update_from_editor ()
{
  if (editor != NULL && 
      koto_task_editor_is_dirty (KOTO_TASK_EDITOR (editor)))
  {
    GError *error = NULL;
    KotoTask *task = NULL;

    g_object_get (editor, "task", &task, NULL);

    if (task != NULL)
    {
      if (!e_cal_modify_object (cal, task->comp, CALOBJ_MOD_THIS, &error))
      {
        g_warning ("Cannot modify object: %s", error->message);
        g_error_free (error);
      }
    }
  }
}

static void
edit_task (KotoTask *task)
{
  g_assert (task);

  /* 
   * Been asked to edit a task, we should commit any changes to the previous
   * task if necessary
   */
  update_from_editor ();

  g_object_set (editor,
                "groups", group_selector_store,
                "task", task,
                NULL);

  gtk_widget_show_all (editor);
}

static void
on_new_clicked (GtkWidget *widget, gpointer user_data)
{
  GError *error = NULL;
  const char *text;
  char *uid = NULL;
  char *group = NULL;
  icalcomponent *comp;
  KotoTask *task;

  text = g_strdup_printf (_("New task"));

  comp = icalcomponent_new (ICAL_VTODO_COMPONENT);

  icalcomponent_add_property (comp, icalproperty_new_summary (text));
  icalcomponent_add_property (comp, icalproperty_new_class (ICAL_CLASS_PUBLIC));

  group = koto_group_combo_get_active_group (KOTO_GROUP_COMBO (combo));

  if (group) {
    icalproperty *group_prop;

    group_prop = icalproperty_new (ICAL_CATEGORIES_PROPERTY);
    icalproperty_set_categories (group_prop, group);
    icalcomponent_add_property (comp, group_prop);
    
    g_free (group);
  }

  if (!e_cal_create_object (cal, comp, &uid, &error)) {
    g_warning (G_STRLOC ": cannot create task: %s", error->message);
    g_error_free (error);
  }

  /*
   * Select the new task in an idle function so that the store can process the
   * signals that are waiting for it (as we did a blocking call to add the
   * task).
   */
  if (uid) {
    /* Needed as a temporary fix for bgo #31135 */
    icalcomponent_set_uid (comp, uid);
    g_idle_add ((GSourceFunc)select_uid, uid);
  }

  task = koto_task_new (comp);
  edit_task (task);
}

/*
 * Callback when a row in the treeview is clicked.
 */
static void
on_selection_changed (GtkTreeSelection *selection, gpointer user_data)
{
  GtkTreeModel *model;
  GtkTreeIter iter;
  KotoTask *task;

  if (gtk_tree_selection_get_selected (selection, &model, &iter))
  {
    gtk_tree_model_get (model, &iter, COLUMN_ICAL, &task, -1);
    edit_task (task);
    koto_task_unref (task);
  }
}


/*
 * Foreach function called from update_title() that simply counts the number of
 * uncompleted tasks.  @userdata is an int* to the count.
 */
static gboolean
count_pending (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
{
  int *count = data;
  gboolean done;
  
  gtk_tree_model_get (model, iter, COLUMN_DONE, &done, -1);

  if (!done)
    (*count)++;

  /*
   * Hopefully one KotoTaskStore will always be sorted, but at the moment new
   * tasks are appended and then moved to the correct location. When this is
   * fixed we can return @done here and not bother counting completed tasks.
   */
  return FALSE;
}

/*
 * Update the window title, generally as the number of tasks has changed.
 */
static void
update_title (GtkWindow *window, GtkTreeModel *model)
{
  int count = 0;
  char *title;

  g_assert (GTK_IS_WINDOW (window));
  g_assert (GTK_IS_TREE_MODEL (model));

  gtk_tree_model_foreach (model, count_pending, &count);
  title = g_strdup_printf (_("Tasks (%d)"), count);
  gtk_window_set_title (window, title);
  g_free (title);
}

/*
 * Callback when rows are inserted into the task store, to update the window
 * title.
 */
static void
on_row_inserted (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data)
{
  update_title (GTK_WINDOW (window), task_store);
}

/*
 * Callback when rows are removed from the task store, to update the window
 * title.
 */
static void
on_row_deleted (GtkTreeModel *model, GtkTreePath *path, gpointer user_data)
{
  update_title (GTK_WINDOW (window), task_store);
}

static void
on_delete_clicked (GtkButton *button, gpointer user_data)
{
  GError *error = NULL;
  GtkWidget *dialog;
  KotoTask *task;

  task = koto_task_view_get_selected_task (KOTO_TASK_VIEW (treeview));
  if (!task) {
    g_warning ("TODO: No task selected, DeleteTask should be disabled");
    return;
  }
  
  dialog = gtk_message_dialog_new (GTK_WINDOW (window),
                                   GTK_DIALOG_DESTROY_WITH_PARENT,
                                   GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
                                   _("Are you sure you want to delete \"%s\"?"),
                                   icalcomponent_get_summary (task->comp));
  gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
                                            _("If you delete an item, it is permanently lost."));
  gtk_dialog_add_buttons (GTK_DIALOG (dialog),
                          GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                          GTK_STOCK_DELETE, GTK_RESPONSE_ACCEPT,
                          NULL);
  gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
  
  if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
    if (!e_cal_remove_object (cal, icalcomponent_get_uid (task->comp), &error)) {
      g_warning ("Cannot remove object: %s", error->message);
      g_error_free (error);
    }
  }
  
  gtk_widget_destroy (dialog);

  koto_task_unref (task);

  if (editor)
    gtk_widget_hide_all (editor);
}

static void
on_close_clicked (GtkWidget *widget, gpointer user_data)
{
  update_from_editor ();
  gtk_main_quit ();
}

static gboolean
on_delete_event (GtkWidget *widget, GdkEvent *event, gpointer user_data)
{
  update_from_editor ();
  return FALSE;
}


/* Populate and setup the menu */
static void
setup_menu (void)
{
  GtkWidget *menu, *menuitem;

  menu = gtk_menu_new ();
  moko_paned_window_set_application_menu (MOKO_PANED_WINDOW (window), GTK_MENU (menu));

  /* New menu item */
  menuitem = gtk_menu_item_new_with_label ("New task");
  gtk_container_add (GTK_CONTAINER (menu), menuitem);
  g_signal_connect (G_OBJECT (menuitem), "activate",
      G_CALLBACK (on_new_clicked), NULL);

  /* New menu item */
  menuitem = gtk_menu_item_new_with_label ("Delete task");
  gtk_container_add (GTK_CONTAINER (menu), menuitem);
  g_signal_connect (G_OBJECT (menuitem), "activate",
      G_CALLBACK (on_delete_clicked), NULL);

  /* Close menu item */
  menuitem = gtk_menu_item_new_with_label ("Close");
  gtk_container_add (GTK_CONTAINER (menu), menuitem);
  g_signal_connect (G_OBJECT (menuitem), "activate",
      G_CALLBACK (on_close_clicked), NULL);
}

static void
setup_toolbox (void)
{
  GtkWidget *button;
  GtkWidget *toolbox;

  toolbox = moko_tool_box_new_with_search ();
  moko_paned_window_add_toolbox (MOKO_PANED_WINDOW (window), MOKO_TOOL_BOX(toolbox));

  /* Delete button */
  button = GTK_WIDGET (moko_tool_box_add_action_button (MOKO_TOOL_BOX (toolbox)));
  moko_pixmap_button_set_center_stock (MOKO_PIXMAP_BUTTON (button), GTK_STOCK_DELETE);
  g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (on_delete_clicked), NULL);
  
  /* New button */
  button = GTK_WIDGET (moko_tool_box_add_action_button (MOKO_TOOL_BOX (toolbox)));
  moko_pixmap_button_set_center_stock (MOKO_PIXMAP_BUTTON (button), GTK_STOCK_NEW);
  g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (on_new_clicked), NULL);
}

void
koto_platform_open_url (const char *url)
{
  /* Need to implement this, but it will never be called */
}

int
main (int argc, char **argv)
{
  GError *error = NULL;
  ECalView *cal_view;
  GtkWidget *box, *scrolled;
  GtkTreeSelection *selection;

#ifdef ENABLE_NLS
  /* Initialise i18n*/
  bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
  bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
  textdomain(GETTEXT_PACKAGE);
#endif

  g_set_application_name (_("Tasks"));
  gtk_init (&argc, &argv);

  cal = e_cal_new_system_tasks ();
  if (!cal)
    g_error ("Cannot get system tasks");
  
  if (!e_cal_open (cal, FALSE, &error))
    g_error("Cannot open calendar: %s", error->message);

  if (!e_cal_get_query (cal, "#t", &cal_view, &error))
    g_error("Cannot get calendar view: %s", error->message);
  /* TODO: nasty, should pass cal to the stores or add e_cal_view_get_cal() */
  g_object_set_data_full (G_OBJECT (cal_view), "koto-ecal", g_object_ref (cal), g_object_unref);

  /* Create the data stores */
  task_store = koto_task_store_new (cal_view);
  
  group_filter_store = koto_group_store_new (cal_view);
  koto_group_store_add_group (KOTO_GROUP_STORE (group_filter_store), koto_all_group_new ());
  koto_group_store_add_group (KOTO_GROUP_STORE (group_filter_store), koto_meta_group_new (KOTO_META_GROUP_SEPERATOR, -99));
  koto_group_store_add_group (KOTO_GROUP_STORE (group_filter_store), koto_meta_group_new (KOTO_META_GROUP_SEPERATOR, 99));
  koto_group_store_add_group (KOTO_GROUP_STORE (group_filter_store), koto_no_category_group_new ());

  group_selector_store = koto_group_store_new (cal_view);
  koto_group_store_add_group (KOTO_GROUP_STORE (group_selector_store), koto_meta_group_new (KOTO_META_GROUP_NONE, -100));
  koto_group_store_add_group (KOTO_GROUP_STORE (group_selector_store), koto_meta_group_new (KOTO_META_GROUP_SEPERATOR, -99));
  koto_group_store_add_group (KOTO_GROUP_STORE (group_selector_store), koto_meta_group_new (KOTO_META_GROUP_SEPERATOR, 99));
  koto_group_store_add_group (KOTO_GROUP_STORE (group_selector_store), koto_meta_group_new (KOTO_META_GROUP_NEW, 100));
  
  /* Create the UI */
  gtk_window_set_default_icon_name ("tasks");
  window = moko_paned_window_new ();
  update_title (GTK_WINDOW (window), task_store);
  gtk_window_set_default_size (GTK_WINDOW (window), 240, 320);
  g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
  g_signal_connect (window, "delete-event", G_CALLBACK (on_delete_event), NULL);

  /* Connect to the task store change events to update the title bar */
  /* TODO: use the utility function */
  g_signal_connect (task_store, "row-inserted", G_CALLBACK (on_row_inserted), NULL);
  g_signal_connect (task_store, "row-changed", G_CALLBACK (on_row_inserted), NULL);
  g_signal_connect (task_store, "row-deleted", G_CALLBACK (on_row_deleted), NULL);

  box = gtk_vbox_new (FALSE, 4);
  gtk_container_set_border_width (GTK_CONTAINER (box), 4);

  moko_paned_window_set_navigation_pane (MOKO_PANED_WINDOW (window), box);

  combo = koto_group_combo_new (KOTO_GROUP_STORE (group_filter_store));

  scrolled = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled), GTK_SHADOW_IN);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
  gtk_box_pack_start (GTK_BOX (box), scrolled, TRUE, TRUE, 0);

  filter = koto_group_model_filter_new (KOTO_TASK_STORE (task_store));
  treeview = koto_task_view_new (KOTO_TASK_STORE (task_store), KOTO_GROUP_MODEL_FILTER (filter));
//  g_signal_connect (treeview, "row-activated", G_CALLBACK (on_row_activated), NULL);

  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
  g_signal_connect (selection, "changed", G_CALLBACK (on_selection_changed), NULL);

  gtk_container_add (GTK_CONTAINER (scrolled), treeview);

  gtk_widget_grab_focus (treeview);

  /* Select the first row, the All group. */
  gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
  koto_group_combo_connect_filter (KOTO_GROUP_COMBO (combo),
                                   KOTO_GROUP_MODEL_FILTER (filter));

  e_cal_view_start (cal_view);

  details_pane = moko_scrolled_pane_new ();

  moko_paned_window_set_details_pane (MOKO_PANED_WINDOW (window), details_pane);

  /* temporary settings */
  GtkSettings *settings = gtk_settings_get_default ();
  g_object_set (settings,
      "gtk-theme-name", "openmoko-standard",
      NULL);

  setup_menu ();
  setup_toolbox ();

  gtk_widget_show_all (window);  

  editor = koto_task_editor_new ();
  koto_task_editor_add_fields (KOTO_TASK_EDITOR (editor),
                               ICAL_SUMMARY_PROPERTY,
                               ICAL_CATEGORIES_PROPERTY,
                               ICAL_PRIORITY_PROPERTY,
                               ICAL_DESCRIPTION_PROPERTY,
                               NULL);
  moko_scrolled_pane_pack_with_viewport (MOKO_SCROLLED_PANE (details_pane), editor);

  gtk_main ();

  return 0;
}

Generated by  Doxygen 1.6.0   Back to index