b22c24f8b3561c7ae9658c061bfcf7007b077c07
[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 static GtkWidget *default_window = NULL;
38
39 struct _MilkMainWindowPrivate
40 {
41         MilkAuth *auth;
42
43         GtkWidget *app_menu;
44
45         GtkWidget *main_vbox;
46
47         GtkWidget *new_task_entry;
48         GtkWidget *task_view;
49         GtkWidget *task_selector;
50 };
51
52 enum {
53         TASK_VIEW_COLUMN_TITLE,
54         N_VIEW_COLUMNS
55 };
56
57 typedef struct {
58         const char *display_name;
59         const char *id;
60         gpointer    callback;
61 } MenuItem;
62
63 static void
64 milk_main_window_get_property (GObject    *object,
65                                guint       property_id,
66                                GValue     *value,
67                                GParamSpec *pspec)
68 {
69         switch (property_id)
70         {
71                 default:
72                         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id,
73                                         pspec);
74         }
75 }
76
77 static void
78 milk_main_window_set_property (GObject      *object,
79                                guint         property_id,
80                                const GValue *value,
81                                GParamSpec   *pspec)
82 {
83         switch (property_id)
84         {
85                 default:
86                         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id,
87                                         pspec);
88         }
89 }
90
91 static void
92 milk_main_window_dispose (GObject *object)
93 {
94         G_OBJECT_CLASS (milk_main_window_parent_class)->dispose (object);
95 }
96
97 static void
98 new_task_clicked_cb (GtkButton      *button,
99                      MilkMainWindow *window)
100 {
101         g_debug ("FIXME: implement 'new task' action");
102 }
103
104 /* XXX: The latency between clicking "complete" and actually removing the task
105  * from the view after polling the server is very long, so there's an obvious
106  * lag -- it will be completely transparent (and look very fast) as soon as
107  * we've got a cache in place */
108 static void
109 complete_clicked_cb (GtkButton      *button,
110                      MilkMainWindow *window)
111 {
112         MilkMainWindowPrivate *priv;
113         GList *rows;
114         GtkTreeModel *model;
115         char *timeline;
116         GError *error = NULL;
117
118         priv = MILK_MAIN_WINDOW_PRIVATE (window);
119
120         rows = hildon_touch_selector_get_selected_rows (
121                         HILDON_TOUCH_SELECTOR (priv->task_view),
122                         TASK_VIEW_COLUMN_TITLE);
123         model = hildon_touch_selector_get_model (
124                         HILDON_TOUCH_SELECTOR (priv->task_view),
125                         TASK_VIEW_COLUMN_TITLE);
126
127         timeline = milk_auth_timeline_create (priv->auth, &error);
128
129         if (error) {
130                 g_warning (G_STRLOC ": failed to create a timeline: %s",
131                            error->message);
132                 g_clear_error (&error);
133         } else {
134                 while (rows) {
135                         GtkTreeIter iter;
136                         RtmTask *task;
137
138                         gtk_tree_model_get_iter (model, &iter, rows->data);
139                         gtk_tree_model_get (model, &iter,
140                                         MILK_TASK_MODEL_COLUMN_TASK, &task,
141                                         -1);
142
143                         milk_auth_task_complete (priv->auth, timeline, task,
144                                         &error);
145                         if (error != NULL) {
146                                 g_warning (G_STRLOC ": failed to complete task "
147                                                 "%s: %s",
148                                                 rtm_task_get_id (task),
149                                                 error->message);
150                                 g_clear_error (&error);
151                         }
152
153                         rows = g_list_delete_link (rows, rows);
154                 }
155         }
156 }
157
158 /* XXX: high latency until we have a cache; see the note for
159  * complete_clicked_cb() */
160 static void
161 delete_clicked_cb (GtkButton      *button,
162                    MilkMainWindow *window)
163 {
164         MilkMainWindowPrivate *priv;
165         GList *rows;
166         GtkTreeModel *model;
167         char *timeline;
168         GError *error = NULL;
169
170         priv = MILK_MAIN_WINDOW_PRIVATE (window);
171
172         rows = hildon_touch_selector_get_selected_rows (
173                         HILDON_TOUCH_SELECTOR (priv->task_view),
174                         TASK_VIEW_COLUMN_TITLE);
175         model = hildon_touch_selector_get_model (
176                         HILDON_TOUCH_SELECTOR (priv->task_view),
177                         TASK_VIEW_COLUMN_TITLE);
178
179         timeline = milk_auth_timeline_create (priv->auth, &error);
180
181         if (error) {
182                 g_warning (G_STRLOC ": failed to create a timeline: %s",
183                            error->message);
184                 g_clear_error (&error);
185         } else {
186                 while (rows) {
187                         GtkTreeIter iter;
188                         RtmTask *task;
189
190                         gtk_tree_model_get_iter (model, &iter, rows->data);
191                         gtk_tree_model_get (model, &iter,
192                                         MILK_TASK_MODEL_COLUMN_TASK, &task,
193                                         -1);
194
195                         milk_auth_task_delete (priv->auth, timeline, task,
196                                         &error);
197                         if (error != NULL) {
198                                 g_warning (G_STRLOC ": failed to delete task "
199                                                 "%s: %s",
200                                                 rtm_task_get_id (task),
201                                                 error->message);
202                                 g_clear_error (&error);
203                         }
204
205                         rows = g_list_delete_link (rows, rows);
206                 }
207         }
208 }
209
210 static void
211 priority_plus_clicked_cb (GtkButton      *button,
212                           MilkMainWindow *window)
213 {
214         g_debug ("FIXME: implement 'priority plus' action");
215 }
216
217 static void
218 priority_minus_clicked_cb (GtkButton      *button,
219                            MilkMainWindow *window)
220 {
221         g_debug ("FIXME: implement 'priority minus' action");
222 }
223
224 static void
225 edit_clicked_cb (GtkButton      *button,
226                  MilkMainWindow *window)
227 {
228         g_debug ("FIXME: implement 'edit' action");
229 }
230
231 static MenuItem menu_items_always_shown[] = {
232         {"New Task",   "menu-item-new-task",       new_task_clicked_cb},
233 };
234
235 static MenuItem menu_items_selection_required[] = {
236         {"Edit",       "menu-item-edit",           edit_clicked_cb},
237         {"Priority +", "menu-item-priority_plus",  priority_plus_clicked_cb},
238         {"Priority -", "menu-item-priority_minus", priority_minus_clicked_cb},
239         {"Complete",   "menu-item-complete",       complete_clicked_cb},
240         {"Delete",     "menu-item-delete",         delete_clicked_cb},
241 };
242
243 static void
244 task_view_selection_changed_cb (HildonTouchSelector *view,
245                                 gint                 column,
246                                 MilkMainWindow      *window)
247 {
248         MilkMainWindowPrivate *priv;
249         GList *rows;
250         gboolean show = FALSE;
251         gint i;
252
253         priv = MILK_MAIN_WINDOW_PRIVATE (window);
254
255         rows = hildon_touch_selector_get_selected_rows (view, column);
256         show = (g_list_length (rows) > 0);
257
258         for (i = 0; i < G_N_ELEMENTS (menu_items_selection_required); i++) {
259                 GtkWidget *w;
260
261                 w = g_object_get_data (
262                                 G_OBJECT (priv->app_menu),
263                                 menu_items_selection_required[i].id);
264
265                 if (show)
266                         gtk_widget_show (w);
267                 else
268                         gtk_widget_hide (w);
269         }
270
271         g_list_free (rows);
272 }
273
274 static GtkWidget*
275 create_menu (gpointer user_data)
276 {
277         HildonAppMenu *menu;
278         MenuItem *menu_array;
279         gint i, length;
280         GtkWidget *w;
281
282         menu = HILDON_APP_MENU (hildon_app_menu_new ());
283
284         menu_array = menu_items_always_shown;
285         length = G_N_ELEMENTS (menu_items_always_shown);
286         for (i = 0; i < length; i++) {
287                 w = hildon_button_new_with_text (
288                                 HILDON_SIZE_FINGER_HEIGHT | HILDON_SIZE_AUTO_WIDTH,
289                                 HILDON_BUTTON_ARRANGEMENT_VERTICAL,
290                                 _(menu_array[i].display_name), "");
291                 g_signal_connect (w, "clicked",
292                                 G_CALLBACK (menu_array[i].callback), user_data);
293                 g_object_set_data (G_OBJECT (menu), menu_array[i].id, w);
294                 hildon_app_menu_append (menu, GTK_BUTTON (w));
295                 gtk_widget_show (w);
296         }
297
298         menu_array = menu_items_selection_required;
299         length = G_N_ELEMENTS (menu_items_selection_required);
300         for (i = 0; i < length; i++) {
301                 w = hildon_button_new_with_text (
302                                 HILDON_SIZE_FINGER_HEIGHT | HILDON_SIZE_AUTO_WIDTH,
303                                 HILDON_BUTTON_ARRANGEMENT_VERTICAL,
304                                 menu_array[i].display_name, "");
305                 g_signal_connect (w, "clicked",
306                                 G_CALLBACK (menu_array[i].callback), user_data);
307                 g_object_set_data (G_OBJECT (menu), menu_array[i].id, w);
308                 hildon_app_menu_append (menu, GTK_BUTTON (w));
309                 gtk_widget_hide (w);
310         }
311
312         gtk_widget_show (GTK_WIDGET (menu));
313
314         return GTK_WIDGET (menu);
315 }
316
317 static void
318 contact_column_render_func (GtkCellLayout   *cell_layout,
319                             GtkCellRenderer *renderer,
320                             GtkTreeModel    *model,
321                             GtkTreeIter     *iter,
322                             gpointer         user_data)
323 {
324         RtmTask *task;
325
326         gtk_tree_model_get (
327                         model, iter, MILK_TASK_MODEL_COLUMN_TASK, &task, -1);
328         g_object_set (renderer, "text", rtm_task_get_name (task), NULL);
329
330         g_object_unref (task);
331 }
332
333 static gboolean
334 begin_auth_idle (MilkMainWindow *window)
335 {
336         MilkMainWindowPrivate *priv;
337
338         priv = MILK_MAIN_WINDOW_PRIVATE (window);
339
340         /* FIXME: cut this */
341         g_debug ("trying to run the milk auth demo");
342         milk_auth_log_in (priv->auth);
343
344         return FALSE;
345 }
346
347 static void
348 milk_main_window_constructed (GObject* object)
349 {
350         MilkMainWindow *self = MILK_MAIN_WINDOW (object);
351         MilkMainWindowPrivate *priv = MILK_MAIN_WINDOW_PRIVATE (object);
352         GtkWidget *w;
353         GtkTreeModel *model;
354         GtkCellRenderer *renderer;
355         HildonTouchSelectorColumn *col;
356
357         w = gtk_vbox_new (FALSE, HILDON_MARGIN_DEFAULT);
358         gtk_container_add (GTK_CONTAINER (self), w);
359         priv->main_vbox = w;
360
361         /*
362          * New Task entry
363          */
364         w = hildon_entry_new (HILDON_SIZE_FINGER_HEIGHT);
365         gtk_box_pack_start (GTK_BOX (priv->main_vbox), w, FALSE, FALSE, 0);
366
367         /* FIXME: change this to hildon_gtk_entry_set_placeholder_text() is
368          * fixed, since this is deprecated */
369         hildon_entry_set_placeholder (HILDON_ENTRY (w),
370                         _("Enter a new task..."));
371         priv->new_task_entry = w;
372
373         /*
374          * Task List
375          */
376         priv->auth = milk_auth_get_default ();
377         model = GTK_TREE_MODEL (milk_task_model_new (priv->auth));
378         w = hildon_touch_selector_new ();
379
380
381         renderer = gtk_cell_renderer_text_new ();
382         g_object_set (renderer,
383                         "ellipsize", PANGO_ELLIPSIZE_END,
384                         NULL);
385
386         col = hildon_touch_selector_append_column (
387                         HILDON_TOUCH_SELECTOR (w), model, NULL, NULL);
388         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (col), renderer, TRUE);
389         gtk_cell_layout_set_cell_data_func (
390                         GTK_CELL_LAYOUT (col), renderer,
391                         (GtkCellLayoutDataFunc) contact_column_render_func,
392                         self, NULL);
393         g_object_unref (model);
394
395         hildon_touch_selector_set_column_selection_mode (
396                         HILDON_TOUCH_SELECTOR (w),
397                         HILDON_TOUCH_SELECTOR_SELECTION_MODE_MULTIPLE);
398         hildon_touch_selector_set_hildon_ui_mode (
399                         HILDON_TOUCH_SELECTOR (w), HILDON_UI_MODE_EDIT);
400         hildon_touch_selector_unselect_all (
401                         HILDON_TOUCH_SELECTOR (w), TASK_VIEW_COLUMN_TITLE);
402
403         gtk_box_pack_start (GTK_BOX (priv->main_vbox), w, TRUE, TRUE, 0);
404         g_object_set (w, "can-focus", TRUE, NULL);
405         gtk_widget_grab_focus (w);
406
407         g_signal_connect (
408                         G_OBJECT (w), "changed",
409                         G_CALLBACK (task_view_selection_changed_cb), self);
410         priv->task_view = w;
411
412         priv->app_menu = create_menu (self);
413         hildon_window_set_app_menu (
414                         HILDON_WINDOW (self), HILDON_APP_MENU (priv->app_menu));
415
416         /* break a cyclical dependency by doing this after the window is
417          * constructed */
418         g_idle_add ((GSourceFunc) begin_auth_idle, self);
419 }
420
421 static void
422 milk_main_window_class_init (MilkMainWindowClass *klass)
423 {
424         GObjectClass *object_class = G_OBJECT_CLASS (klass);
425
426         g_type_class_add_private (klass, sizeof (MilkMainWindowPrivate));
427
428         object_class->get_property = milk_main_window_get_property;
429         object_class->set_property = milk_main_window_set_property;
430         object_class->constructed = milk_main_window_constructed;
431         object_class->dispose = milk_main_window_dispose;
432 }
433
434 static void
435 milk_main_window_init (MilkMainWindow *self)
436 {
437         self->priv = G_TYPE_INSTANCE_GET_PRIVATE (
438                         self, MILK_TYPE_MAIN_WINDOW, MilkMainWindowPrivate);
439 }
440
441 GtkWidget*
442 milk_main_window_get_default ()
443 {
444         if (!default_window) {
445                 default_window = g_object_new (MILK_TYPE_MAIN_WINDOW, NULL);
446         }
447
448         return default_window;
449 }