abf9ec7e75723e73bc7d272ce4158eb8456a81b4
[milk] / src / milk-main-window.c
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License as
4  * published by the Free Software Foundation; either version 2 of the
5  * License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
10  * General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public
13  * License along with this program; if not, write to the
14  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
15  * Boston, MA  02110-1301  USA
16  *
17  * Authors: Travis Reitter <treitter@gmail.com>
18  */
19
20 #include <config.h>
21
22 #include <glib.h>
23 #include <glib/gi18n.h>
24 #include <gtk/gtk.h>
25 #include <hildon/hildon.h>
26 #include <rtm-glib/rtm-glib.h>
27
28 #include "milk-main-window.h"
29 #include "milk-cache.h"
30 #include "milk-task-model.h"
31
32 G_DEFINE_TYPE (MilkMainWindow, milk_main_window, HILDON_TYPE_WINDOW)
33
34 /* less expensive than G_TYPE_INSTANCE_GET_PRIVATE */
35 #define MILK_MAIN_WINDOW_PRIVATE(o) ((MILK_MAIN_WINDOW ((o)))->priv)
36
37 #define NEW_TASK_PLACEHOLDER_TEXT "Enter a new task..."
38
39 static GtkWidget *default_window = NULL;
40
41 struct _MilkMainWindowPrivate
42 {
43         MilkCache *cache;
44
45         GtkWidget *app_menu;
46
47         GtkWidget *main_vbox;
48
49         GtkWidget *new_task_entry;
50         GtkWidget *task_view;
51         GtkWidget *task_selector;
52 };
53
54 enum {
55         TASK_VIEW_COLUMN_TITLE,
56         N_VIEW_COLUMNS
57 };
58
59 typedef struct {
60         const char *display_name;
61         const char *id;
62         gpointer    callback;
63 } MenuItem;
64
65 static void
66 milk_main_window_get_property (GObject    *object,
67                                guint       property_id,
68                                GValue     *value,
69                                GParamSpec *pspec)
70 {
71         switch (property_id)
72         {
73                 default:
74                         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id,
75                                         pspec);
76         }
77 }
78
79 static void
80 milk_main_window_set_property (GObject      *object,
81                                guint         property_id,
82                                const GValue *value,
83                                GParamSpec   *pspec)
84 {
85         switch (property_id)
86         {
87                 default:
88                         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id,
89                                         pspec);
90         }
91 }
92
93 static void
94 milk_main_window_dispose (GObject *object)
95 {
96         G_OBJECT_CLASS (milk_main_window_parent_class)->dispose (object);
97 }
98
99 static void
100 new_task_clicked_cb (GtkButton      *button,
101                      MilkMainWindow *window)
102 {
103         g_debug ("FIXME: implement 'new task' action");
104 }
105
106 static GList*
107 get_selected_tasks (MilkMainWindow *window)
108 {
109         MilkMainWindowPrivate *priv;
110         GList *rows;
111         GtkTreeModel *model;
112         GList *tasks = NULL;
113
114         priv = MILK_MAIN_WINDOW_PRIVATE (window);
115
116         rows = hildon_touch_selector_get_selected_rows (
117                         HILDON_TOUCH_SELECTOR (priv->task_view),
118                         TASK_VIEW_COLUMN_TITLE);
119         model = hildon_touch_selector_get_model (
120                         HILDON_TOUCH_SELECTOR (priv->task_view),
121                         TASK_VIEW_COLUMN_TITLE);
122
123         while (rows) {
124                 GtkTreeIter iter;
125                 RtmTask *task;
126
127                 gtk_tree_model_get_iter (model, &iter, rows->data);
128                 gtk_tree_model_get (model, &iter,
129                                 MILK_TASK_MODEL_COLUMN_TASK, &task,
130                                 -1);
131
132                 tasks = g_list_prepend (tasks, task);
133                 rows = g_list_delete_link (rows, rows);
134         }
135
136         return tasks;
137 }
138
139 static void
140 complete_clicked_cb (GtkButton      *button,
141                      MilkMainWindow *window)
142 {
143         MilkMainWindowPrivate *priv;
144         GList *tasks;
145         char *timeline;
146         GError *error = NULL;
147
148         priv = MILK_MAIN_WINDOW_PRIVATE (window);
149
150         tasks = get_selected_tasks (window);
151         timeline = milk_cache_timeline_create (priv->cache, &error);
152
153         if (error) {
154                 g_warning (G_STRLOC ": failed to create a timeline: %s",
155                            error->message);
156                 g_clear_error (&error);
157         } else {
158                 while (tasks) {
159                         milk_cache_task_complete (priv->cache, timeline,
160                                         tasks->data, &error);
161                         tasks = g_list_delete_link (tasks, tasks);
162                 }
163         }
164 }
165
166 static void
167 delete_clicked_cb (GtkButton      *button,
168                    MilkMainWindow *window)
169 {
170         MilkMainWindowPrivate *priv;
171         GList *tasks;
172         char *timeline;
173         GError *error = NULL;
174
175         priv = MILK_MAIN_WINDOW_PRIVATE (window);
176
177         tasks = get_selected_tasks (window);
178         timeline = milk_cache_timeline_create (priv->cache, &error);
179
180         if (error) {
181                 g_warning (G_STRLOC ": failed to create a timeline: %s",
182                            error->message);
183                 g_clear_error (&error);
184         } else {
185                 while (tasks) {
186                         milk_cache_task_delete (priv->cache, timeline,
187                                         tasks->data, &error);
188                         tasks = g_list_delete_link (tasks, tasks);
189                 }
190         }
191 }
192
193 static void
194 priority_plus_clicked_cb (GtkButton      *button,
195                           MilkMainWindow *window)
196 {
197         g_debug ("FIXME: implement 'priority plus' action");
198 }
199
200 static void
201 priority_minus_clicked_cb (GtkButton      *button,
202                            MilkMainWindow *window)
203 {
204         g_debug ("FIXME: implement 'priority minus' action");
205 }
206
207 static void
208 edit_clicked_cb (GtkButton      *button,
209                  MilkMainWindow *window)
210 {
211         g_debug ("FIXME: implement 'edit' action");
212 }
213
214 static MenuItem menu_items_always_shown[] = {
215         {"New Task",   "menu-item-new-task",       new_task_clicked_cb},
216 };
217
218 static MenuItem menu_items_selection_required[] = {
219         {"Edit",       "menu-item-edit",           edit_clicked_cb},
220         {"Priority +", "menu-item-priority_plus",  priority_plus_clicked_cb},
221         {"Priority -", "menu-item-priority_minus", priority_minus_clicked_cb},
222         {"Complete",   "menu-item-complete",       complete_clicked_cb},
223         {"Delete",     "menu-item-delete",         delete_clicked_cb},
224 };
225
226 static void
227 new_task_entry_activated_cb (GtkEntry       *entry,
228                              MilkMainWindow *window)
229 {
230         MilkMainWindowPrivate *priv;
231         char *name;
232
233         priv = MILK_MAIN_WINDOW_PRIVATE (window);
234
235         name = g_strdup (gtk_entry_get_text (entry));
236
237         /* Strip the contents of leading and trailing whitespace, and add as a
238          * new task if the result is non-empty */
239         if (g_strcmp0 (g_strstrip (name), "")) {
240                 char *timeline;
241                 GError *error = NULL;
242
243                 timeline = milk_cache_timeline_create (priv->cache, &error);
244
245                 if (error) {
246                         g_warning (G_STRLOC ": failed to create a timeline: %s",
247                                 error->message);
248                         g_clear_error (&error);
249                 } else {
250                         RtmTask *task;
251
252                         task = milk_cache_task_add (priv->cache, timeline, name,
253                                         &error);
254                         if (task) {
255                                 /* empty out the entry and show its placeholder
256                                  * text */
257                                 gtk_entry_set_text (entry, "");
258                                 gtk_widget_grab_focus (priv->task_view);
259
260                                 /* FIXME: we should probably scroll to this new
261                                  * task in the model view, if it's not currently
262                                  * visible (and highlight only it in any case */
263                         } else {
264                                 g_warning (G_STRLOC ": failed to add task: %s",
265                                                 error->message);
266                                 g_clear_error (&error);
267                         }
268                 }
269         }
270
271         g_free (name);
272 }
273
274 static gboolean
275 new_task_entry_key_press_event_cb (GtkEntry       *entry,
276                                    GdkEventKey    *event,
277                                    MilkMainWindow *window)
278 {
279         MilkMainWindowPrivate *priv;
280
281         priv = MILK_MAIN_WINDOW_PRIVATE (window);
282
283         if (!event || event->type != GDK_KEY_PRESS) {
284                 return FALSE;
285         }
286
287         switch (event->keyval) {
288                 case GDK_KP_Enter:
289                 case GDK_Return:
290                         new_task_entry_activated_cb (entry, window);
291                         return TRUE;
292         }
293
294         return FALSE;
295 }
296
297 static void
298 task_view_selection_changed_cb (HildonTouchSelector *view,
299                                 gint                 column,
300                                 MilkMainWindow      *window)
301 {
302         MilkMainWindowPrivate *priv;
303         GList *rows;
304         gboolean show = FALSE;
305         gint i;
306
307         priv = MILK_MAIN_WINDOW_PRIVATE (window);
308
309         rows = hildon_touch_selector_get_selected_rows (view, column);
310         show = (g_list_length (rows) > 0);
311
312         for (i = 0; i < G_N_ELEMENTS (menu_items_selection_required); i++) {
313                 GtkWidget *w;
314
315                 w = g_object_get_data (
316                                 G_OBJECT (priv->app_menu),
317                                 menu_items_selection_required[i].id);
318
319                 if (show)
320                         gtk_widget_show (w);
321                 else
322                         gtk_widget_hide (w);
323         }
324
325         g_list_free (rows);
326 }
327
328 static GtkWidget*
329 create_menu (gpointer user_data)
330 {
331         HildonAppMenu *menu;
332         MenuItem *menu_array;
333         gint i, length;
334         GtkWidget *w;
335
336         menu = HILDON_APP_MENU (hildon_app_menu_new ());
337
338         menu_array = menu_items_always_shown;
339         length = G_N_ELEMENTS (menu_items_always_shown);
340         for (i = 0; i < length; i++) {
341                 w = hildon_button_new_with_text (
342                                 HILDON_SIZE_FINGER_HEIGHT | HILDON_SIZE_AUTO_WIDTH,
343                                 HILDON_BUTTON_ARRANGEMENT_VERTICAL,
344                                 _(menu_array[i].display_name), "");
345                 g_signal_connect (w, "clicked",
346                                 G_CALLBACK (menu_array[i].callback), user_data);
347                 g_object_set_data (G_OBJECT (menu), menu_array[i].id, w);
348                 hildon_app_menu_append (menu, GTK_BUTTON (w));
349                 gtk_widget_show (w);
350         }
351
352         menu_array = menu_items_selection_required;
353         length = G_N_ELEMENTS (menu_items_selection_required);
354         for (i = 0; i < length; i++) {
355                 w = hildon_button_new_with_text (
356                                 HILDON_SIZE_FINGER_HEIGHT | HILDON_SIZE_AUTO_WIDTH,
357                                 HILDON_BUTTON_ARRANGEMENT_VERTICAL,
358                                 menu_array[i].display_name, "");
359                 g_signal_connect (w, "clicked",
360                                 G_CALLBACK (menu_array[i].callback), user_data);
361                 g_object_set_data (G_OBJECT (menu), menu_array[i].id, w);
362                 hildon_app_menu_append (menu, GTK_BUTTON (w));
363                 gtk_widget_hide (w);
364         }
365
366         gtk_widget_show (GTK_WIDGET (menu));
367
368         return GTK_WIDGET (menu);
369 }
370
371 static void
372 contact_column_render_func (GtkCellLayout   *cell_layout,
373                             GtkCellRenderer *renderer,
374                             GtkTreeModel    *model,
375                             GtkTreeIter     *iter,
376                             gpointer         user_data)
377 {
378         RtmTask *task;
379
380         gtk_tree_model_get (
381                         model, iter, MILK_TASK_MODEL_COLUMN_TASK, &task, -1);
382         g_object_set (renderer, "text", rtm_task_get_name (task), NULL);
383
384         g_object_unref (task);
385 }
386
387 static gboolean
388 begin_cache_idle (MilkMainWindow *window)
389 {
390         MilkMainWindowPrivate *priv;
391
392         priv = MILK_MAIN_WINDOW_PRIVATE (window);
393
394         milk_cache_authenticate (priv->cache);
395
396         return FALSE;
397 }
398
399 static void
400 milk_main_window_constructed (GObject* object)
401 {
402         MilkMainWindow *self = MILK_MAIN_WINDOW (object);
403         MilkMainWindowPrivate *priv = MILK_MAIN_WINDOW_PRIVATE (object);
404         GtkWidget *w;
405         GtkTreeModel *model;
406         GtkCellRenderer *renderer;
407         HildonTouchSelectorColumn *col;
408
409         w = gtk_vbox_new (FALSE, HILDON_MARGIN_DEFAULT);
410         gtk_container_add (GTK_CONTAINER (self), w);
411         priv->main_vbox = w;
412
413         /*
414          * New Task entry
415          */
416         w = hildon_entry_new (HILDON_SIZE_FINGER_HEIGHT);
417         gtk_box_pack_start (GTK_BOX (priv->main_vbox), w, FALSE, FALSE, 0);
418
419         /* FIXME: change this to hildon_gtk_entry_set_placeholder_text() is
420          * fixed, since this is deprecated */
421         hildon_entry_set_placeholder (HILDON_ENTRY (w),
422                         _(NEW_TASK_PLACEHOLDER_TEXT));
423         priv->new_task_entry = w;
424         g_signal_connect (G_OBJECT (w), "activate",
425                         G_CALLBACK (new_task_entry_activated_cb), self);
426         g_signal_connect (G_OBJECT (w), "key-press-event",
427                         G_CALLBACK (new_task_entry_key_press_event_cb), self);
428
429         /*
430          * Task List
431          */
432         model = GTK_TREE_MODEL (milk_task_model_new ());
433         w = hildon_touch_selector_new ();
434
435         renderer = gtk_cell_renderer_text_new ();
436         g_object_set (renderer,
437                         "ellipsize", PANGO_ELLIPSIZE_END,
438                         NULL);
439
440         col = hildon_touch_selector_append_column (
441                         HILDON_TOUCH_SELECTOR (w), model, NULL, NULL);
442         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (col), renderer, TRUE);
443         gtk_cell_layout_set_cell_data_func (
444                         GTK_CELL_LAYOUT (col), renderer,
445                         (GtkCellLayoutDataFunc) contact_column_render_func,
446                         self, NULL);
447         g_object_unref (model);
448
449         hildon_touch_selector_set_column_selection_mode (
450                         HILDON_TOUCH_SELECTOR (w),
451                         HILDON_TOUCH_SELECTOR_SELECTION_MODE_MULTIPLE);
452         hildon_touch_selector_set_hildon_ui_mode (
453                         HILDON_TOUCH_SELECTOR (w), HILDON_UI_MODE_EDIT);
454         hildon_touch_selector_unselect_all (
455                         HILDON_TOUCH_SELECTOR (w), TASK_VIEW_COLUMN_TITLE);
456
457         gtk_box_pack_start (GTK_BOX (priv->main_vbox), w, TRUE, TRUE, 0);
458         g_object_set (w, "can-focus", TRUE, NULL);
459         gtk_widget_grab_focus (w);
460
461         g_signal_connect (
462                         G_OBJECT (w), "changed",
463                         G_CALLBACK (task_view_selection_changed_cb), self);
464         priv->task_view = w;
465
466         priv->app_menu = create_menu (self);
467         hildon_window_set_app_menu (
468                         HILDON_WINDOW (self), HILDON_APP_MENU (priv->app_menu));
469
470         /* set up the cache */
471         priv->cache = milk_cache_get_default ();
472
473         /* break a cyclical dependency by doing this after the window is
474          * constructed */
475         g_idle_add ((GSourceFunc) begin_cache_idle, self);
476 }
477
478 static void
479 milk_main_window_class_init (MilkMainWindowClass *klass)
480 {
481         GObjectClass *object_class = G_OBJECT_CLASS (klass);
482
483         g_type_class_add_private (klass, sizeof (MilkMainWindowPrivate));
484
485         object_class->get_property = milk_main_window_get_property;
486         object_class->set_property = milk_main_window_set_property;
487         object_class->constructed = milk_main_window_constructed;
488         object_class->dispose = milk_main_window_dispose;
489 }
490
491 static void
492 milk_main_window_init (MilkMainWindow *self)
493 {
494         self->priv = G_TYPE_INSTANCE_GET_PRIVATE (
495                         self, MILK_TYPE_MAIN_WINDOW, MilkMainWindowPrivate);
496 }
497
498 GtkWidget*
499 milk_main_window_get_default ()
500 {
501         if (!default_window) {
502                 default_window = g_object_new (MILK_TYPE_MAIN_WINDOW, NULL);
503         }
504
505         return default_window;
506 }