9c876a883d592ac419a3a800fb5fad7d13b67fc7
[modest] / src / hildon2 / modest-header-window.c
1 /* Copyright (c) 2008, Nokia Corporation
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  *   notice, this list of conditions and the following disclaimer in the
12  *   documentation and/or other materials provided with the distribution.
13  * * Neither the name of the Nokia Corporation nor the names of its
14  *   contributors may be used to endorse or promote products derived from
15  *   this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include <modest-header-window.h>
31 #include <modest-osso-state-saving.h>
32 #include <libosso.h>
33 #include <hildon/hildon-pannable-area.h>
34 #include <modest-window-mgr.h>
35 #include <modest-signal-mgr.h>
36 #include <modest-runtime.h>
37 #include <modest-platform.h>
38 #include <modest-maemo-utils.h>
39 #include <modest-icon-names.h>
40 #include <modest-ui-constants.h>
41 #include <modest-account-mgr.h>
42 #include <modest-account-mgr-helpers.h>
43 #include <modest-defs.h>
44 #include <modest-widget-memory.h>
45 #include <modest-ui-actions.h>
46 #include <hildon/hildon-button.h>
47 #include <hildon/hildon-program.h>
48 #include <hildon/hildon-banner.h>
49
50 typedef enum {
51         CONTENTS_STATE_NONE = 0,
52         CONTENTS_STATE_EMPTY = 1,
53         CONTENTS_STATE_HEADERS = 2
54 } ContentsState;
55
56 typedef struct _ModestHeaderWindowPrivate ModestHeaderWindowPrivate;
57 struct _ModestHeaderWindowPrivate {
58
59         GtkWidget *header_view;
60         GtkWidget *empty_view;
61         GtkWidget *contents_view;
62         GtkWidget *new_message_button;
63
64         ContentsState contents_state;
65
66         TnyFolder *folder;
67
68         /* signals */
69         GSList *sighandlers;
70
71         /* Display state */
72         osso_display_state_t display_state;
73 };
74 #define MODEST_HEADER_WINDOW_GET_PRIVATE(o)  (G_TYPE_INSTANCE_GET_PRIVATE((o), \
75                                                                           MODEST_TYPE_HEADER_WINDOW, \
76                                                                           ModestHeaderWindowPrivate))
77
78 /* 'private'/'protected' functions */
79 static void modest_header_window_class_init  (ModestHeaderWindowClass *klass);
80 static void modest_header_window_init        (ModestHeaderWindow *obj);
81 static void modest_header_window_finalize    (GObject *obj);
82
83 static void connect_signals (ModestHeaderWindow *self);
84 static void modest_header_window_disconnect_signals (ModestWindow *self);
85
86 static gboolean on_zoom_minus_plus_not_implemented (ModestWindow *window);
87 static void add_to_menu (ModestHeaderWindow *self,
88                          HildonAppMenu *menu,
89                          gchar *label,
90                          GCallback callback);
91 static void setup_menu (ModestHeaderWindow *self);
92 static GtkWidget *create_empty_view (void);
93 static GtkWidget *create_header_view (TnyFolder *folder);
94
95 static void update_view (ModestHeaderWindow *self,
96                          TnyFolderChange *change);
97 static void set_contents_state (ModestHeaderWindow *window, 
98                                 ContentsState state);
99
100 static void on_msg_count_changed (ModestHeaderView *header_view,
101                                   TnyFolder *folder,
102                                   TnyFolderChange *change,
103                                   ModestHeaderWindow *header_window);
104 static void on_header_activated (ModestHeaderView *header_view,
105                                  TnyHeader *header,
106                                  GtkTreePath *path,
107                                  ModestHeaderWindow *header_window);
108
109
110 /* globals */
111 static GtkWindowClass *parent_class = NULL;
112
113 #define EMPTYVIEW_XALIGN 0.5
114 #define EMPTYVIEW_YALIGN 0.0
115 #define EMPTYVIEW_XSPACE 1.0
116 #define EMPTYVIEW_YSPACE 0.0
117
118
119
120 /************************************************************************/
121
122 GType
123 modest_header_window_get_type (void)
124 {
125         static GType my_type = 0;
126         if (!my_type) {
127                 static const GTypeInfo my_info = {
128                         sizeof(ModestHeaderWindowClass),
129                         NULL,           /* base init */
130                         NULL,           /* base finalize */
131                         (GClassInitFunc) modest_header_window_class_init,
132                         NULL,           /* class finalize */
133                         NULL,           /* class data */
134                         sizeof(ModestHeaderWindow),
135                         1,              /* n_preallocs */
136                         (GInstanceInitFunc) modest_header_window_init,
137                         NULL
138                 };
139                 my_type = g_type_register_static (MODEST_TYPE_WINDOW,
140                                                   "ModestHeaderWindow",
141                                                   &my_info, 0);
142         }
143         return my_type;
144 }
145
146 static void
147 modest_header_window_class_init (ModestHeaderWindowClass *klass)
148 {
149         GObjectClass *gobject_class;
150         gobject_class = (GObjectClass*) klass;
151         ModestWindowClass *modest_window_class = (ModestWindowClass *) klass;
152
153         parent_class            = g_type_class_peek_parent (klass);
154         gobject_class->finalize = modest_header_window_finalize;
155
156         g_type_class_add_private (gobject_class, sizeof(ModestHeaderWindowPrivate));
157         
158         modest_window_class->zoom_minus_func = on_zoom_minus_plus_not_implemented;
159         modest_window_class->zoom_plus_func = on_zoom_minus_plus_not_implemented;
160         modest_window_class->disconnect_signals_func = modest_header_window_disconnect_signals;
161 }
162
163 static void
164 modest_header_window_init (ModestHeaderWindow *obj)
165 {
166         ModestHeaderWindowPrivate *priv;
167
168         priv = MODEST_HEADER_WINDOW_GET_PRIVATE(obj);
169
170         priv->sighandlers = NULL;
171         priv->display_state = OSSO_DISPLAY_ON;
172         
173         priv->header_view = NULL;
174         priv->empty_view = NULL;
175         priv->contents_view = NULL;
176         priv->contents_state = CONTENTS_STATE_NONE;
177         priv->folder = NULL;
178         
179         modest_window_mgr_register_help_id (modest_runtime_get_window_mgr(),
180                                             GTK_WINDOW(obj),
181                                             "applications_email_headerview");
182 }
183
184 static void
185 modest_header_window_finalize (GObject *obj)
186 {
187         ModestHeaderWindowPrivate *priv;
188
189         priv = MODEST_HEADER_WINDOW_GET_PRIVATE(obj);
190
191         g_object_unref (priv->header_view);
192         g_object_unref (priv->empty_view);
193         g_object_unref (priv->folder);
194
195         /* Sanity check: shouldn't be needed, the window mgr should
196            call this function before */
197         modest_header_window_disconnect_signals (MODEST_WINDOW (obj));  
198
199         G_OBJECT_CLASS(parent_class)->finalize (obj);
200 }
201
202 static void
203 modest_header_window_disconnect_signals (ModestWindow *self)
204 {       
205         ModestHeaderWindowPrivate *priv;        
206         priv = MODEST_HEADER_WINDOW_GET_PRIVATE(self);
207
208         modest_signal_mgr_disconnect_all_and_destroy (priv->sighandlers);
209         priv->sighandlers = NULL;       
210 }
211
212 static void
213 connect_signals (ModestHeaderWindow *self)
214 {       
215         ModestHeaderWindowPrivate *priv;
216         
217         priv = MODEST_HEADER_WINDOW_GET_PRIVATE(self);
218
219         /* header view */
220
221         priv->sighandlers = 
222                 modest_signal_mgr_connect (priv->sighandlers,G_OBJECT(priv->header_view), 
223                                            "msg_count_changed",
224                                            G_CALLBACK(on_msg_count_changed), self);
225         priv->sighandlers =
226                 modest_signal_mgr_connect (priv->sighandlers, G_OBJECT (priv->header_view),
227                                            "header-activated",
228                                            G_CALLBACK (on_header_activated), self);
229
230         /* TODO: connect header view activate */
231
232         /* new message button */
233
234         g_signal_connect (G_OBJECT (priv->new_message_button), "clicked",
235                           G_CALLBACK (modest_ui_actions_on_new_msg), (gpointer) self);
236         
237         /* window */
238
239         /* we don't register this in sighandlers, as it should be run after disconnecting all signals,
240          * in destroy stage */
241
242         
243 }
244
245 static void 
246 osso_display_event_cb (osso_display_state_t state, 
247                        gpointer data)
248 {
249         ModestHeaderWindowPrivate *priv = MODEST_HEADER_WINDOW_GET_PRIVATE (data);
250
251         priv->display_state = state;
252
253         /* Stop blinking if the screen becomes on */
254         if (priv->display_state == OSSO_DISPLAY_ON)
255                 modest_platform_remove_new_mail_notifications (TRUE);
256 }
257
258 static GtkWidget *
259 create_header_view (TnyFolder *folder)
260 {
261         GtkWidget *header_view;
262
263         header_view  = modest_header_view_new (NULL, MODEST_HEADER_VIEW_STYLE_TWOLINES);
264         modest_header_view_set_folder (MODEST_HEADER_VIEW (header_view), folder, 
265                                        TRUE, NULL, NULL);
266         modest_widget_memory_restore (modest_runtime_get_conf (), G_OBJECT(header_view),
267                                       MODEST_CONF_HEADER_VIEW_KEY);
268
269         return header_view;
270 }
271
272 static GtkWidget *
273 create_empty_view (void)
274 {
275         GtkLabel *label = NULL;
276         GtkWidget *align = NULL;
277
278         align = gtk_alignment_new(EMPTYVIEW_XALIGN, EMPTYVIEW_YALIGN, EMPTYVIEW_XSPACE, EMPTYVIEW_YSPACE);
279         label = GTK_LABEL(gtk_label_new (_("mcen_ia_nomessages")));
280         gtk_label_set_justify (label, GTK_JUSTIFY_CENTER);      
281         gtk_container_add (GTK_CONTAINER (align), GTK_WIDGET(label));
282
283         return GTK_WIDGET(align);
284 }
285
286
287 ModestWindow *
288 modest_header_window_new (TnyFolder *folder)
289 {
290         ModestHeaderWindow *self = NULL;        
291         ModestHeaderWindowPrivate *priv = NULL;
292         HildonProgram *app;
293         GdkPixbuf *window_icon;
294         GtkWidget *pannable;
295         
296         self  = MODEST_HEADER_WINDOW(g_object_new(MODEST_TYPE_HEADER_WINDOW, NULL));
297         priv = MODEST_HEADER_WINDOW_GET_PRIVATE(self);
298
299         priv->folder = g_object_ref (folder);
300
301         pannable = hildon_pannable_area_new ();
302
303         priv->header_view  = create_header_view (folder);
304         priv->empty_view = create_empty_view ();
305         g_object_ref (priv->header_view);
306         g_object_ref (priv->empty_view);
307         priv->contents_view = gtk_vbox_new (FALSE, 0);
308         priv->new_message_button = hildon_button_new (MODEST_EDITABLE_SIZE,
309                                                       HILDON_BUTTON_ARRANGEMENT_HORIZONTAL);
310         hildon_button_set_title (HILDON_BUTTON (priv->new_message_button), _("mcen_me_viewer_newemail"));
311         hildon_button_set_image (HILDON_BUTTON (priv->new_message_button), 
312                                  gtk_image_new_from_stock (MODEST_STOCK_NEW_MAIL, GTK_ICON_SIZE_DIALOG));
313         hildon_button_set_title_alignment (HILDON_BUTTON (priv->new_message_button), 0.0, 0.5);
314         hildon_button_set_image_alignment (HILDON_BUTTON (priv->new_message_button), 0.0, 0.5);
315         hildon_button_set_alignment (HILDON_BUTTON (priv->new_message_button), 0.0, 0.5, 1.0, 0.0);
316         hildon_button_set_image_position (HILDON_BUTTON (priv->new_message_button), GTK_POS_LEFT);
317
318         setup_menu (self);
319
320         gtk_box_pack_start (GTK_BOX (priv->contents_view), priv->new_message_button, FALSE, FALSE, 0);
321         hildon_pannable_area_add_with_viewport (HILDON_PANNABLE_AREA (pannable), priv->contents_view);
322         gtk_container_add (GTK_CONTAINER (self), pannable);
323
324         gtk_widget_show (priv->contents_view);
325         gtk_widget_show (pannable);
326         gtk_widget_show (priv->new_message_button);
327
328         connect_signals (MODEST_HEADER_WINDOW (self));
329
330         update_view (self, NULL);
331
332         /* Load previous osso state, for instance if we are being restored from 
333          * hibernation:  */
334         modest_osso_load_state ();
335
336         /* Get device name */
337         modest_maemo_utils_get_device_name ();
338
339         app = hildon_program_get_instance ();
340         hildon_program_add_window (app, HILDON_WINDOW (self));
341         
342         /* Set window icon */
343         window_icon = modest_platform_get_icon (MODEST_APP_ICON, MODEST_ICON_SIZE_BIG);
344         if (window_icon) {
345                 gtk_window_set_icon (GTK_WINDOW (self), window_icon);
346                 g_object_unref (window_icon);
347         }
348
349         /* Listen for changes in the screen, we don't want to show a
350            led pattern when the display is on for example */
351         osso_hw_set_display_event_cb (modest_maemo_utils_get_osso_context (),
352                                       osso_display_event_cb,
353                                       self); 
354
355         /* Dont't restore settings here, 
356          * because it requires a gtk_widget_show(), 
357          * and we don't want to do that until later,
358          * so that the UI is not visible for non-menu D-Bus activation.
359          */
360
361         return MODEST_WINDOW(self);
362 }
363
364 static gboolean
365 on_zoom_minus_plus_not_implemented (ModestWindow *window)
366 {
367         g_return_val_if_fail (MODEST_IS_HEADER_WINDOW (window), FALSE);
368
369         hildon_banner_show_information (NULL, NULL, dgettext("hildon-common-strings", "ckct_ib_cannot_zoom_here"));
370         return FALSE;
371
372 }
373
374 gboolean
375 modest_header_window_screen_is_on (ModestHeaderWindow *self)
376 {
377         ModestHeaderWindowPrivate *priv = NULL;
378
379         g_return_val_if_fail (MODEST_IS_HEADER_WINDOW(self), FALSE);
380
381         priv = MODEST_HEADER_WINDOW_GET_PRIVATE (self);
382         
383         return (priv->display_state == OSSO_DISPLAY_ON) ? TRUE : FALSE;
384 }
385
386 ModestHeaderView *
387 modest_header_window_get_header_view (ModestHeaderWindow *self)
388 {
389         ModestHeaderWindowPrivate *priv = NULL;
390
391         g_return_val_if_fail (MODEST_IS_HEADER_WINDOW(self), FALSE);
392
393         priv = MODEST_HEADER_WINDOW_GET_PRIVATE (self);
394         
395         return MODEST_HEADER_VIEW (priv->header_view);
396 }
397
398 static void add_to_menu (ModestHeaderWindow *self,
399                          HildonAppMenu *menu,
400                          gchar *label,
401                          GCallback callback)
402 {
403         GtkWidget *button;
404
405         button = gtk_button_new_with_label (label);
406         g_signal_connect_after (G_OBJECT (button), "clicked",
407                                 callback, (gpointer) self);
408         hildon_app_menu_append (menu, GTK_BUTTON (button));
409 }
410
411 static void setup_menu (ModestHeaderWindow *self)
412 {
413         ModestHeaderWindowPrivate *priv = NULL;
414         GtkWidget *app_menu;
415
416         g_return_if_fail (MODEST_IS_HEADER_WINDOW(self));
417
418         priv = MODEST_HEADER_WINDOW_GET_PRIVATE (self);
419
420         app_menu = hildon_app_menu_new ();
421
422         add_to_menu (self, HILDON_APP_MENU (app_menu), _("mcen_me_inbox_sendandreceive"),
423                      G_CALLBACK (modest_ui_actions_on_send_receive));
424
425         hildon_stackable_window_set_main_menu (HILDON_STACKABLE_WINDOW (self), 
426                                                HILDON_APP_MENU (app_menu));
427 }
428
429 static void 
430 update_view (ModestHeaderWindow *self,
431              TnyFolderChange *change)
432 {
433         ModestHeaderWindowPrivate *priv = NULL;
434         gboolean refilter = FALSE;
435         gboolean folder_empty = FALSE;
436         gboolean all_marked_as_deleted = FALSE;
437
438         g_return_if_fail (MODEST_IS_HEADER_WINDOW(self));
439         priv = MODEST_HEADER_WINDOW_GET_PRIVATE (self);
440         g_return_if_fail (priv->folder);
441
442         if (change != NULL) {
443                 TnyFolderChangeChanged changed;
444
445                 changed = tny_folder_change_get_changed (change);
446                 /* If something changes */
447                 if ((changed) & TNY_FOLDER_CHANGE_CHANGED_ALL_COUNT)
448                         folder_empty = (((guint) tny_folder_change_get_new_all_count (change)) == 0);
449                 else
450                         folder_empty = (((guint) tny_folder_get_all_count (TNY_FOLDER (priv->folder))) == 0);
451
452                 if ((changed) & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)
453                         refilter = TRUE;
454         } else {
455                 folder_empty = (((guint) tny_folder_get_all_count (TNY_FOLDER (priv->folder))) == 0);
456         }
457
458         /* Check if all messages are marked to be deleted */
459         all_marked_as_deleted = modest_header_view_is_empty (MODEST_HEADER_VIEW (priv->header_view));
460         folder_empty = folder_empty || all_marked_as_deleted;
461
462         /* Set style of headers view */
463         set_contents_state (self, folder_empty?CONTENTS_STATE_EMPTY:CONTENTS_STATE_HEADERS);
464
465         if (refilter)
466                 modest_header_view_refilter (MODEST_HEADER_VIEW (priv->header_view));
467 }
468
469 static void 
470 set_contents_state (ModestHeaderWindow *self, 
471                     ContentsState state)
472 {
473         ModestHeaderWindowPrivate *priv = NULL;
474
475         g_return_if_fail (MODEST_IS_HEADER_WINDOW(self));
476         priv = MODEST_HEADER_WINDOW_GET_PRIVATE (self);
477
478         if (priv->contents_state == state)
479                 return;
480
481         /* Remove from container the old content */
482         switch (priv->contents_state) {
483         case CONTENTS_STATE_EMPTY:
484                 gtk_container_remove (GTK_CONTAINER (priv->contents_view), priv->empty_view);
485                 break;
486         case CONTENTS_STATE_HEADERS:
487                 gtk_container_remove (GTK_CONTAINER (priv->contents_view), priv->header_view);
488                 break;
489         case CONTENTS_STATE_NONE:
490                 break;
491         }
492
493         /* Add the new content */
494         switch (state) {
495         case CONTENTS_STATE_EMPTY:
496                 gtk_box_pack_start (GTK_BOX (priv->contents_view), priv->empty_view, TRUE, TRUE, 0);
497                 gtk_widget_show (priv->empty_view);
498                 break;
499         case CONTENTS_STATE_HEADERS:
500                 gtk_box_pack_start (GTK_BOX (priv->contents_view), priv->header_view, TRUE, TRUE, 0);
501                 gtk_widget_show (priv->header_view);
502                 break;
503         case CONTENTS_STATE_NONE:
504                 break;
505         }
506         priv->contents_state = state;
507         
508 }
509
510 static void
511 on_msg_count_changed (ModestHeaderView *header_view,
512                       TnyFolder *folder,
513                       TnyFolderChange *change,
514                       ModestHeaderWindow *header_window)
515 {       
516         g_return_if_fail (MODEST_IS_HEADER_WINDOW (header_window));
517         
518         update_view (MODEST_HEADER_WINDOW (header_window), change);
519 }
520
521 static void 
522 on_header_activated (ModestHeaderView *header_view,
523                      TnyHeader *header,
524                      GtkTreePath *path,
525                      ModestHeaderWindow *header_window)
526 {
527         modest_ui_actions_on_header_activated (header_view, header, path, MODEST_WINDOW (header_window));
528 }