e159b2b270c2b5a452a5ba8620b96281a42b19fa
[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-auth.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         MilkAuth *auth;
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 /* XXX: The latency between clicking "complete" and actually removing the task
107  * from the view after polling the server is very long, so there's an obvious
108  * lag -- it will be completely transparent (and look very fast) as soon as
109  * we've got a cache in place */
110 static void
111 complete_clicked_cb (GtkButton      *button,
112                      MilkMainWindow *window)
113 {
114         MilkMainWindowPrivate *priv;
115         GList *rows;
116         GtkTreeModel *model;
117         char *timeline;
118         GError *error = NULL;
119
120         priv = MILK_MAIN_WINDOW_PRIVATE (window);
121
122         rows = hildon_touch_selector_get_selected_rows (
123                         HILDON_TOUCH_SELECTOR (priv->task_view),
124                         TASK_VIEW_COLUMN_TITLE);
125         model = hildon_touch_selector_get_model (
126                         HILDON_TOUCH_SELECTOR (priv->task_view),
127                         TASK_VIEW_COLUMN_TITLE);
128
129         timeline = milk_auth_timeline_create (priv->auth, &error);
130
131         if (error) {
132                 g_warning (G_STRLOC ": failed to create a timeline: %s",
133                            error->message);
134                 g_clear_error (&error);
135         } else {
136                 while (rows) {
137                         GtkTreeIter iter;
138                         RtmTask *task;
139
140                         gtk_tree_model_get_iter (model, &iter, rows->data);
141                         gtk_tree_model_get (model, &iter,
142                                         MILK_TASK_MODEL_COLUMN_TASK, &task,
143                                         -1);
144
145                         milk_auth_task_complete (priv->auth, timeline, task,
146                                         &error);
147                         if (error != NULL) {
148                                 g_warning (G_STRLOC ": failed to complete task "
149                                                 "%s: %s",
150                                                 rtm_task_get_id (task),
151                                                 error->message);
152                                 g_clear_error (&error);
153                         }
154
155                         rows = g_list_delete_link (rows, rows);
156                 }
157         }
158 }
159
160 /* XXX: high latency until we have a cache; see the note for
161  * complete_clicked_cb() */
162 static void
163 delete_clicked_cb (GtkButton      *button,
164                    MilkMainWindow *window)
165 {
166         MilkMainWindowPrivate *priv;
167         GList *rows;
168         GtkTreeModel *model;
169         char *timeline;
170         GError *error = NULL;
171
172         priv = MILK_MAIN_WINDOW_PRIVATE (window);
173
174         rows = hildon_touch_selector_get_selected_rows (
175                         HILDON_TOUCH_SELECTOR (priv->task_view),
176                         TASK_VIEW_COLUMN_TITLE);
177         model = hildon_touch_selector_get_model (
178                         HILDON_TOUCH_SELECTOR (priv->task_view),
179                         TASK_VIEW_COLUMN_TITLE);
180
181         timeline = milk_auth_timeline_create (priv->auth, &error);
182
183         if (error) {
184                 g_warning (G_STRLOC ": failed to create a timeline: %s",
185                            error->message);
186                 g_clear_error (&error);
187         } else {
188                 while (rows) {
189                         GtkTreeIter iter;
190                         RtmTask *task;
191
192                         gtk_tree_model_get_iter (model, &iter, rows->data);
193                         gtk_tree_model_get (model, &iter,
194                                         MILK_TASK_MODEL_COLUMN_TASK, &task,
195                                         -1);
196
197                         milk_auth_task_delete (priv->auth, timeline, task,
198                                         &error);
199                         if (error != NULL) {
200                                 g_warning (G_STRLOC ": failed to delete task "
201                                                 "%s: %s",
202                                                 rtm_task_get_id (task),
203                                                 error->message);
204                                 g_clear_error (&error);
205                         }
206
207                         rows = g_list_delete_link (rows, rows);
208                 }
209         }
210 }
211
212 static void
213 priority_plus_clicked_cb (GtkButton      *button,
214                           MilkMainWindow *window)
215 {
216         g_debug ("FIXME: implement 'priority plus' action");
217 }
218
219 static void
220 priority_minus_clicked_cb (GtkButton      *button,
221                            MilkMainWindow *window)
222 {
223         g_debug ("FIXME: implement 'priority minus' action");
224 }
225
226 static void
227 edit_clicked_cb (GtkButton      *button,
228                  MilkMainWindow *window)
229 {
230         g_debug ("FIXME: implement 'edit' action");
231 }
232
233 static MenuItem menu_items_always_shown[] = {
234         {"New Task",   "menu-item-new-task",       new_task_clicked_cb},
235 };
236
237 static MenuItem menu_items_selection_required[] = {
238         {"Edit",       "menu-item-edit",           edit_clicked_cb},
239         {"Priority +", "menu-item-priority_plus",  priority_plus_clicked_cb},
240         {"Priority -", "menu-item-priority_minus", priority_minus_clicked_cb},
241         {"Complete",   "menu-item-complete",       complete_clicked_cb},
242         {"Delete",     "menu-item-delete",         delete_clicked_cb},
243 };
244
245 static void
246 new_task_entry_activated_cb (GtkEntry       *entry,
247                              MilkMainWindow *window)
248 {
249         MilkMainWindowPrivate *priv;
250         char *name;
251
252         priv = MILK_MAIN_WINDOW_PRIVATE (window);
253
254         name = g_strdup (gtk_entry_get_text (entry));
255
256         /* Strip the contents of leading and trailing whitespace, and add as a
257          * new task if the result is non-empty */
258         if (g_strcmp0 (g_strstrip (name), "")) {
259                 char *timeline;
260                 GError *error = NULL;
261
262                 timeline = milk_auth_timeline_create (priv->auth, &error);
263
264                 if (error) {
265                         g_warning (G_STRLOC ": failed to create a timeline: %s",
266                                 error->message);
267                         g_clear_error (&error);
268                 } else {
269                         RtmTask *task;
270
271                         task = milk_auth_task_add (priv->auth, timeline, name,
272                                         &error);
273                         if (task) {
274                                 /* empty out the entry and show its placeholder
275                                  * text */
276                                 gtk_entry_set_text (entry, "");
277                                 gtk_widget_grab_focus (priv->task_view);
278                         } else {
279                                 g_warning (G_STRLOC ": failed to add task: %s",
280                                                 error->message);
281                                 g_clear_error (&error);
282                         }
283                 }
284         }
285
286         g_free (name);
287 }
288
289 static gboolean
290 new_task_entry_key_press_event_cb (GtkEntry       *entry,
291                                    GdkEventKey    *event,
292                                    MilkMainWindow *window)
293 {
294         MilkMainWindowPrivate *priv;
295
296         priv = MILK_MAIN_WINDOW_PRIVATE (window);
297
298         if (!event || event->type != GDK_KEY_PRESS) {
299                 return FALSE;
300         }
301
302         switch (event->keyval) {
303                 case GDK_KP_Enter:
304                 case GDK_Return:
305                         new_task_entry_activated_cb (entry, window);
306                         return TRUE;
307         }
308
309         return FALSE;
310 }
311
312 static void
313 task_view_selection_changed_cb (HildonTouchSelector *view,
314                                 gint                 column,
315                                 MilkMainWindow      *window)
316 {
317         MilkMainWindowPrivate *priv;
318         GList *rows;
319         gboolean show = FALSE;
320         gint i;
321
322         priv = MILK_MAIN_WINDOW_PRIVATE (window);
323
324         rows = hildon_touch_selector_get_selected_rows (view, column);
325         show = (g_list_length (rows) > 0);
326
327         for (i = 0; i < G_N_ELEMENTS (menu_items_selection_required); i++) {
328                 GtkWidget *w;
329
330                 w = g_object_get_data (
331                                 G_OBJECT (priv->app_menu),
332                                 menu_items_selection_required[i].id);
333
334                 if (show)
335                         gtk_widget_show (w);
336                 else
337                         gtk_widget_hide (w);
338         }
339
340         g_list_free (rows);
341 }
342
343 static GtkWidget*
344 create_menu (gpointer user_data)
345 {
346         HildonAppMenu *menu;
347         MenuItem *menu_array;
348         gint i, length;
349         GtkWidget *w;
350
351         menu = HILDON_APP_MENU (hildon_app_menu_new ());
352
353         menu_array = menu_items_always_shown;
354         length = G_N_ELEMENTS (menu_items_always_shown);
355         for (i = 0; i < length; i++) {
356                 w = hildon_button_new_with_text (
357                                 HILDON_SIZE_FINGER_HEIGHT | HILDON_SIZE_AUTO_WIDTH,
358                                 HILDON_BUTTON_ARRANGEMENT_VERTICAL,
359                                 _(menu_array[i].display_name), "");
360                 g_signal_connect (w, "clicked",
361                                 G_CALLBACK (menu_array[i].callback), user_data);
362                 g_object_set_data (G_OBJECT (menu), menu_array[i].id, w);
363                 hildon_app_menu_append (menu, GTK_BUTTON (w));
364                 gtk_widget_show (w);
365         }
366
367         menu_array = menu_items_selection_required;
368         length = G_N_ELEMENTS (menu_items_selection_required);
369         for (i = 0; i < length; i++) {
370                 w = hildon_button_new_with_text (
371                                 HILDON_SIZE_FINGER_HEIGHT | HILDON_SIZE_AUTO_WIDTH,
372                                 HILDON_BUTTON_ARRANGEMENT_VERTICAL,
373                                 menu_array[i].display_name, "");
374                 g_signal_connect (w, "clicked",
375                                 G_CALLBACK (menu_array[i].callback), user_data);
376                 g_object_set_data (G_OBJECT (menu), menu_array[i].id, w);
377                 hildon_app_menu_append (menu, GTK_BUTTON (w));
378                 gtk_widget_hide (w);
379         }
380
381         gtk_widget_show (GTK_WIDGET (menu));
382
383         return GTK_WIDGET (menu);
384 }
385
386 static void
387 contact_column_render_func (GtkCellLayout   *cell_layout,
388                             GtkCellRenderer *renderer,
389                             GtkTreeModel    *model,
390                             GtkTreeIter     *iter,
391                             gpointer         user_data)
392 {
393         RtmTask *task;
394
395         gtk_tree_model_get (
396                         model, iter, MILK_TASK_MODEL_COLUMN_TASK, &task, -1);
397         g_object_set (renderer, "text", rtm_task_get_name (task), NULL);
398
399         g_object_unref (task);
400 }
401
402 static gboolean
403 begin_auth_idle (MilkMainWindow *window)
404 {
405         MilkMainWindowPrivate *priv;
406
407         priv = MILK_MAIN_WINDOW_PRIVATE (window);
408
409         milk_auth_log_in (priv->auth);
410
411         return FALSE;
412 }
413
414 static void
415 milk_main_window_constructed (GObject* object)
416 {
417         MilkMainWindow *self = MILK_MAIN_WINDOW (object);
418         MilkMainWindowPrivate *priv = MILK_MAIN_WINDOW_PRIVATE (object);
419         GtkWidget *w;
420         GtkTreeModel *model;
421         GtkCellRenderer *renderer;
422         HildonTouchSelectorColumn *col;
423
424         w = gtk_vbox_new (FALSE, HILDON_MARGIN_DEFAULT);
425         gtk_container_add (GTK_CONTAINER (self), w);
426         priv->main_vbox = w;
427
428         /*
429          * New Task entry
430          */
431         w = hildon_entry_new (HILDON_SIZE_FINGER_HEIGHT);
432         gtk_box_pack_start (GTK_BOX (priv->main_vbox), w, FALSE, FALSE, 0);
433
434         /* FIXME: change this to hildon_gtk_entry_set_placeholder_text() is
435          * fixed, since this is deprecated */
436         hildon_entry_set_placeholder (HILDON_ENTRY (w),
437                         _(NEW_TASK_PLACEHOLDER_TEXT));
438         priv->new_task_entry = w;
439         g_signal_connect (G_OBJECT (w), "activate",
440                         G_CALLBACK (new_task_entry_activated_cb), self);
441         g_signal_connect (G_OBJECT (w), "key-press-event",
442                         G_CALLBACK (new_task_entry_key_press_event_cb), self);
443
444         /*
445          * Task List
446          */
447         priv->auth = milk_auth_get_default ();
448         model = GTK_TREE_MODEL (milk_task_model_new (priv->auth));
449         w = hildon_touch_selector_new ();
450
451
452         renderer = gtk_cell_renderer_text_new ();
453         g_object_set (renderer,
454                         "ellipsize", PANGO_ELLIPSIZE_END,
455                         NULL);
456
457         col = hildon_touch_selector_append_column (
458                         HILDON_TOUCH_SELECTOR (w), model, NULL, NULL);
459         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (col), renderer, TRUE);
460         gtk_cell_layout_set_cell_data_func (
461                         GTK_CELL_LAYOUT (col), renderer,
462                         (GtkCellLayoutDataFunc) contact_column_render_func,
463                         self, NULL);
464         g_object_unref (model);
465
466         hildon_touch_selector_set_column_selection_mode (
467                         HILDON_TOUCH_SELECTOR (w),
468                         HILDON_TOUCH_SELECTOR_SELECTION_MODE_MULTIPLE);
469         hildon_touch_selector_set_hildon_ui_mode (
470                         HILDON_TOUCH_SELECTOR (w), HILDON_UI_MODE_EDIT);
471         hildon_touch_selector_unselect_all (
472                         HILDON_TOUCH_SELECTOR (w), TASK_VIEW_COLUMN_TITLE);
473
474         gtk_box_pack_start (GTK_BOX (priv->main_vbox), w, TRUE, TRUE, 0);
475         g_object_set (w, "can-focus", TRUE, NULL);
476         gtk_widget_grab_focus (w);
477
478         g_signal_connect (
479                         G_OBJECT (w), "changed",
480                         G_CALLBACK (task_view_selection_changed_cb), self);
481         priv->task_view = w;
482
483         priv->app_menu = create_menu (self);
484         hildon_window_set_app_menu (
485                         HILDON_WINDOW (self), HILDON_APP_MENU (priv->app_menu));
486
487         /* break a cyclical dependency by doing this after the window is
488          * constructed */
489         g_idle_add ((GSourceFunc) begin_auth_idle, self);
490 }
491
492 static void
493 milk_main_window_class_init (MilkMainWindowClass *klass)
494 {
495         GObjectClass *object_class = G_OBJECT_CLASS (klass);
496
497         g_type_class_add_private (klass, sizeof (MilkMainWindowPrivate));
498
499         object_class->get_property = milk_main_window_get_property;
500         object_class->set_property = milk_main_window_set_property;
501         object_class->constructed = milk_main_window_constructed;
502         object_class->dispose = milk_main_window_dispose;
503 }
504
505 static void
506 milk_main_window_init (MilkMainWindow *self)
507 {
508         self->priv = G_TYPE_INSTANCE_GET_PRIVATE (
509                         self, MILK_TYPE_MAIN_WINDOW, MilkMainWindowPrivate);
510 }
511
512 GtkWidget*
513 milk_main_window_get_default ()
514 {
515         if (!default_window) {
516                 default_window = g_object_new (MILK_TYPE_MAIN_WINDOW, NULL);
517         }
518
519         return default_window;
520 }