ad89faf2fd1d7ac0f89d9f2f357547413d3b6252
[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 static void
159 delete_clicked_cb (GtkButton      *button,
160                    MilkMainWindow *window)
161 {
162         g_debug ("FIXME: implement 'delete' action");
163 }
164
165 static void
166 priority_plus_clicked_cb (GtkButton      *button,
167                           MilkMainWindow *window)
168 {
169         g_debug ("FIXME: implement 'priority plus' action");
170 }
171
172 static void
173 priority_minus_clicked_cb (GtkButton      *button,
174                            MilkMainWindow *window)
175 {
176         g_debug ("FIXME: implement 'priority minus' action");
177 }
178
179 static void
180 edit_clicked_cb (GtkButton      *button,
181                  MilkMainWindow *window)
182 {
183         g_debug ("FIXME: implement 'edit' action");
184 }
185
186 static MenuItem menu_items_always_shown[] = {
187         {"New Task",   "menu-item-new-task",       new_task_clicked_cb},
188 };
189
190 static MenuItem menu_items_selection_required[] = {
191         {"Edit",       "menu-item-edit",           edit_clicked_cb},
192         {"Priority +", "menu-item-priority_plus",  priority_plus_clicked_cb},
193         {"Priority -", "menu-item-priority_minus", priority_minus_clicked_cb},
194         {"Complete",   "menu-item-complete",       complete_clicked_cb},
195         {"Delete",     "menu-item-delete",         delete_clicked_cb},
196 };
197
198 static void
199 task_view_selection_changed_cb (HildonTouchSelector *view,
200                                 gint                 column,
201                                 MilkMainWindow      *window)
202 {
203         MilkMainWindowPrivate *priv;
204         GList *rows;
205         gboolean show = FALSE;
206         gint i;
207
208         priv = MILK_MAIN_WINDOW_PRIVATE (window);
209
210         rows = hildon_touch_selector_get_selected_rows (view, column);
211         show = (g_list_length (rows) > 0);
212
213         for (i = 0; i < G_N_ELEMENTS (menu_items_selection_required); i++) {
214                 GtkWidget *w;
215
216                 w = g_object_get_data (
217                                 G_OBJECT (priv->app_menu),
218                                 menu_items_selection_required[i].id);
219
220                 if (show)
221                         gtk_widget_show (w);
222                 else
223                         gtk_widget_hide (w);
224         }
225
226         g_list_free (rows);
227 }
228
229 static GtkWidget*
230 create_menu (gpointer user_data)
231 {
232         HildonAppMenu *menu;
233         MenuItem *menu_array;
234         gint i, length;
235         GtkWidget *w;
236
237         menu = HILDON_APP_MENU (hildon_app_menu_new ());
238
239         menu_array = menu_items_always_shown;
240         length = G_N_ELEMENTS (menu_items_always_shown);
241         for (i = 0; i < length; i++) {
242                 w = hildon_button_new_with_text (
243                                 HILDON_SIZE_FINGER_HEIGHT | HILDON_SIZE_AUTO_WIDTH,
244                                 HILDON_BUTTON_ARRANGEMENT_VERTICAL,
245                                 _(menu_array[i].display_name), "");
246                 g_signal_connect (w, "clicked",
247                                 G_CALLBACK (menu_array[i].callback), user_data);
248                 g_object_set_data (G_OBJECT (menu), menu_array[i].id, w);
249                 hildon_app_menu_append (menu, GTK_BUTTON (w));
250                 gtk_widget_show (w);
251         }
252
253         menu_array = menu_items_selection_required;
254         length = G_N_ELEMENTS (menu_items_selection_required);
255         for (i = 0; i < length; i++) {
256                 w = hildon_button_new_with_text (
257                                 HILDON_SIZE_FINGER_HEIGHT | HILDON_SIZE_AUTO_WIDTH,
258                                 HILDON_BUTTON_ARRANGEMENT_VERTICAL,
259                                 menu_array[i].display_name, "");
260                 g_signal_connect (w, "clicked",
261                                 G_CALLBACK (menu_array[i].callback), user_data);
262                 g_object_set_data (G_OBJECT (menu), menu_array[i].id, w);
263                 hildon_app_menu_append (menu, GTK_BUTTON (w));
264                 gtk_widget_hide (w);
265         }
266
267         gtk_widget_show (GTK_WIDGET (menu));
268
269         return GTK_WIDGET (menu);
270 }
271
272 static void
273 contact_column_render_func (GtkCellLayout   *cell_layout,
274                             GtkCellRenderer *renderer,
275                             GtkTreeModel    *model,
276                             GtkTreeIter     *iter,
277                             gpointer         user_data)
278 {
279         RtmTask *task;
280
281         gtk_tree_model_get (
282                         model, iter, MILK_TASK_MODEL_COLUMN_TASK, &task, -1);
283         g_object_set (renderer, "text", rtm_task_get_name (task), NULL);
284
285         g_object_unref (task);
286 }
287
288 static gboolean
289 begin_auth_idle (MilkMainWindow *window)
290 {
291         MilkMainWindowPrivate *priv;
292
293         priv = MILK_MAIN_WINDOW_PRIVATE (window);
294
295         /* FIXME: cut this */
296         g_debug ("trying to run the milk auth demo");
297         milk_auth_log_in (priv->auth);
298
299         return FALSE;
300 }
301
302 static void
303 milk_main_window_constructed (GObject* object)
304 {
305         MilkMainWindow *self = MILK_MAIN_WINDOW (object);
306         MilkMainWindowPrivate *priv = MILK_MAIN_WINDOW_PRIVATE (object);
307         GtkWidget *w;
308         GtkTreeModel *model;
309         GtkCellRenderer *renderer;
310         HildonTouchSelectorColumn *col;
311
312         w = gtk_vbox_new (FALSE, HILDON_MARGIN_DEFAULT);
313         gtk_container_add (GTK_CONTAINER (self), w);
314         priv->main_vbox = w;
315
316         /*
317          * New Task entry
318          */
319         w = hildon_entry_new (HILDON_SIZE_FINGER_HEIGHT);
320         gtk_box_pack_start (GTK_BOX (priv->main_vbox), w, FALSE, FALSE, 0);
321
322         /* FIXME: change this to hildon_gtk_entry_set_placeholder_text() is
323          * fixed, since this is deprecated */
324         hildon_entry_set_placeholder (HILDON_ENTRY (w),
325                         _("Enter a new task..."));
326         priv->new_task_entry = w;
327
328         /*
329          * Task List
330          */
331         priv->auth = milk_auth_get_default ();
332         model = GTK_TREE_MODEL (milk_task_model_new (priv->auth));
333         w = hildon_touch_selector_new ();
334
335
336         renderer = gtk_cell_renderer_text_new ();
337         g_object_set (renderer,
338                         "ellipsize", PANGO_ELLIPSIZE_END,
339                         NULL);
340
341         col = hildon_touch_selector_append_column (
342                         HILDON_TOUCH_SELECTOR (w), model, NULL, NULL);
343         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (col), renderer, TRUE);
344         gtk_cell_layout_set_cell_data_func (
345                         GTK_CELL_LAYOUT (col), renderer,
346                         (GtkCellLayoutDataFunc) contact_column_render_func,
347                         self, NULL);
348         g_object_unref (model);
349
350         hildon_touch_selector_set_column_selection_mode (
351                         HILDON_TOUCH_SELECTOR (w),
352                         HILDON_TOUCH_SELECTOR_SELECTION_MODE_MULTIPLE);
353         hildon_touch_selector_set_hildon_ui_mode (
354                         HILDON_TOUCH_SELECTOR (w), HILDON_UI_MODE_EDIT);
355         hildon_touch_selector_unselect_all (
356                         HILDON_TOUCH_SELECTOR (w), TASK_VIEW_COLUMN_TITLE);
357
358         gtk_box_pack_start (GTK_BOX (priv->main_vbox), w, TRUE, TRUE, 0);
359         g_object_set (w, "can-focus", TRUE, NULL);
360         gtk_widget_grab_focus (w);
361
362         g_signal_connect (
363                         G_OBJECT (w), "changed",
364                         G_CALLBACK (task_view_selection_changed_cb), self);
365         priv->task_view = w;
366
367         priv->app_menu = create_menu (self);
368         hildon_window_set_app_menu (
369                         HILDON_WINDOW (self), HILDON_APP_MENU (priv->app_menu));
370
371         /* break a cyclical dependency by doing this after the window is
372          * constructed */
373         g_idle_add ((GSourceFunc) begin_auth_idle, self);
374 }
375
376 static void
377 milk_main_window_class_init (MilkMainWindowClass *klass)
378 {
379         GObjectClass *object_class = G_OBJECT_CLASS (klass);
380
381         g_type_class_add_private (klass, sizeof (MilkMainWindowPrivate));
382
383         object_class->get_property = milk_main_window_get_property;
384         object_class->set_property = milk_main_window_set_property;
385         object_class->constructed = milk_main_window_constructed;
386         object_class->dispose = milk_main_window_dispose;
387 }
388
389 static void
390 milk_main_window_init (MilkMainWindow *self)
391 {
392         self->priv = G_TYPE_INSTANCE_GET_PRIVATE (
393                         self, MILK_TYPE_MAIN_WINDOW, MilkMainWindowPrivate);
394 }
395
396 GtkWidget*
397 milk_main_window_get_default ()
398 {
399         if (!default_window) {
400                 default_window = g_object_new (MILK_TYPE_MAIN_WINDOW, NULL);
401         }
402
403         return default_window;
404 }