* update the modest-window-manager; together with the next patch, this
[modest] / src / widgets / modest-window-mgr.c
1 /* Copyright (c) 2006,2007 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 <string.h>
31 #include "modest-window-mgr.h"
32 #include "modest-runtime.h"
33 #include "modest-tny-folder.h"
34 #include "modest-ui-actions.h"
35 #include "modest-platform.h"
36 #include "widgets/modest-main-window.h"
37 #include "widgets/modest-msg-edit-window.h"
38 #include "widgets/modest-msg-view-window.h"
39 /* include other impl specific header files */
40
41 /* 'private'/'protected' functions */
42 static void modest_window_mgr_class_init (ModestWindowMgrClass *klass);
43 static void modest_window_mgr_init       (ModestWindowMgr *obj);
44 static void modest_window_mgr_finalize   (GObject *obj);
45
46 /* static void on_window_destroy            (ModestWindow *window,  */
47 /*                                        ModestWindowMgr *self); */
48 static gboolean on_window_destroy            (ModestWindow *window,
49                                               GdkEvent *event,
50                                               ModestWindowMgr *self);
51
52 /* list my signals  */
53 enum {
54         /* MY_SIGNAL_1, */
55         /* MY_SIGNAL_2, */
56         LAST_SIGNAL
57 };
58
59 typedef struct _ModestWindowMgrPrivate ModestWindowMgrPrivate;
60 struct _ModestWindowMgrPrivate {
61         GList        *window_list;
62         ModestWindow *main_window;
63         gboolean     fullscreen_mode;
64         gboolean     show_toolbars;
65         gboolean     show_toolbars_fullscreen;
66         
67         GSList       *windows_that_prevent_hibernation;
68         GSList       *preregistered_uids;
69         GHashTable   *destroy_handlers;
70 };
71 #define MODEST_WINDOW_MGR_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
72                                                MODEST_TYPE_WINDOW_MGR, \
73                                                ModestWindowMgrPrivate))
74 /* globals */
75 static GObjectClass *parent_class = NULL;
76
77 /* uncomment the following if you have defined any signals */
78 /* static guint signals[LAST_SIGNAL] = {0}; */
79
80 GType
81 modest_window_mgr_get_type (void)
82 {
83         static GType my_type = 0;
84         if (!my_type) {
85                 static const GTypeInfo my_info = {
86                         sizeof(ModestWindowMgrClass),
87                         NULL,           /* base init */
88                         NULL,           /* base finalize */
89                         (GClassInitFunc) modest_window_mgr_class_init,
90                         NULL,           /* class finalize */
91                         NULL,           /* class data */
92                         sizeof(ModestWindowMgr),
93                         1,              /* n_preallocs */
94                         (GInstanceInitFunc) modest_window_mgr_init,
95                         NULL
96                 };
97                 my_type = g_type_register_static (G_TYPE_OBJECT,
98                                                   "ModestWindowMgr",
99                                                   &my_info, 0);
100         }
101         return my_type;
102 }
103
104 static void
105 modest_window_mgr_class_init (ModestWindowMgrClass *klass)
106 {
107         GObjectClass *gobject_class;
108         gobject_class = (GObjectClass*) klass;
109
110         parent_class            = g_type_class_peek_parent (klass);
111         gobject_class->finalize = modest_window_mgr_finalize;
112
113         g_type_class_add_private (gobject_class, sizeof(ModestWindowMgrPrivate));
114 }
115
116 static void
117 modest_window_mgr_init (ModestWindowMgr *obj)
118 {
119         ModestWindowMgrPrivate *priv;
120
121         priv = MODEST_WINDOW_MGR_GET_PRIVATE(obj);
122         priv->window_list = NULL;
123         priv->main_window = NULL;
124         priv->fullscreen_mode = FALSE;
125
126         priv->preregistered_uids = NULL;
127
128         /* Could not initialize it from gconf, singletons are not
129            ready yet */
130         priv->show_toolbars = FALSE;
131         priv->show_toolbars_fullscreen = FALSE;
132         priv->destroy_handlers = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
133 }
134
135 static void
136 modest_window_mgr_finalize (GObject *obj)
137 {
138         ModestWindowMgrPrivate *priv = MODEST_WINDOW_MGR_GET_PRIVATE(obj);
139
140         if (priv->window_list) {
141                 GList *iter = priv->window_list;
142                 /* unregister pending windows */
143                 while (iter) {
144                         modest_window_mgr_unregister_window (MODEST_WINDOW_MGR (obj), 
145                                                              MODEST_WINDOW (iter->data));
146                         iter = g_list_next (iter);
147                 }
148                 g_list_free (priv->window_list);
149                 priv->window_list = NULL;
150         }
151
152         g_slist_foreach (priv->preregistered_uids, (GFunc)g_free, NULL);
153         g_slist_free (priv->preregistered_uids);
154
155         
156         /* Free the hash table with the handlers */
157         if (priv->destroy_handlers) {
158                 g_hash_table_destroy (priv->destroy_handlers);
159                 priv->destroy_handlers = NULL;
160         }
161
162         /* Do not unref priv->main_window because it does not hold a
163            new reference */
164
165         G_OBJECT_CLASS(parent_class)->finalize (obj);
166 }
167
168 ModestWindowMgr*
169 modest_window_mgr_new (void)
170 {
171         return MODEST_WINDOW_MGR(g_object_new(MODEST_TYPE_WINDOW_MGR, NULL));
172 }
173
174
175
176
177 /* do we have uid? */
178 static gboolean
179 has_uid (GSList *list, const gchar *uid)
180 {
181         GSList *cursor = list;
182
183         if (!uid)
184                 return FALSE;
185         
186         while (cursor) {
187                 if (cursor->data && strcmp (cursor->data, uid) == 0)
188                         return TRUE;
189                 cursor = g_slist_next (cursor);
190         }
191         return FALSE;
192 }
193
194
195 /* remove all from the list have have uid = uid */
196 static GSList*
197 remove_uid (GSList *list, const gchar *uid)
198 {
199         GSList *cursor = list, *start = list;
200         
201         if (!uid)
202                 return FALSE;
203         
204         while (cursor) {
205                 GSList *next = g_slist_next (cursor);
206                 if (cursor->data && strcmp (cursor->data, uid) == 0) {
207                         g_free (cursor->data);
208                         start = g_slist_delete_link (start, cursor);
209                 }
210                 cursor = next;
211         }
212         return start;
213 }
214
215
216 static GSList *
217 append_uid (GSList *list, const gchar *uid)
218 {
219         return g_slist_append (list, g_strdup(uid));
220 }
221
222
223
224 void 
225 modest_window_mgr_register_header (ModestWindowMgr *self,  TnyHeader *header)
226 {
227         ModestWindowMgrPrivate *priv;
228         gchar* uid;
229         
230         g_return_if_fail (MODEST_IS_WINDOW_MGR (self));
231         g_return_if_fail (TNY_IS_HEADER(header));
232                 
233         priv = MODEST_WINDOW_MGR_GET_PRIVATE (self);
234         uid = modest_tny_folder_get_header_unique_id (header);
235         
236         if (!has_uid (priv->preregistered_uids, uid))
237                 priv->preregistered_uids = append_uid (priv->preregistered_uids, uid);
238
239         g_free (uid);
240 }
241
242
243
244 static gint
245 compare_msguids (ModestWindow *win,
246                  const gchar *uid)
247 {
248         const gchar *msg_uid;
249
250         if (!MODEST_IS_MSG_VIEW_WINDOW (win))
251                 return 1;
252
253         /* Get message uid from msg window */
254         msg_uid = modest_msg_view_window_get_message_uid (MODEST_MSG_VIEW_WINDOW (win));
255         
256         if (msg_uid && uid &&!strcmp (msg_uid, uid))
257                 return 0;
258         else
259                 return 1;
260 }
261
262
263
264 gboolean
265 modest_window_mgr_find_registered_header (ModestWindowMgr *self, TnyHeader *header,
266                                           ModestWindow **win)
267 {
268         ModestWindowMgrPrivate *priv;
269         gchar* uid;
270         gboolean retval = FALSE;
271         GList *item = NULL;
272
273         g_return_val_if_fail (MODEST_IS_WINDOW_MGR (self), FALSE);
274         g_return_val_if_fail (TNY_IS_HEADER(header), FALSE);
275         
276         priv = MODEST_WINDOW_MGR_GET_PRIVATE (self);
277
278         uid = modest_tny_folder_get_header_unique_id (header);
279         
280         /* first, look for the window */
281         /* note, the UID cannot be in both the window list and the preregistered uid list */
282         if (priv->window_list) {
283                 item = g_list_find_custom (priv->window_list, 
284                                            uid, (GCompareFunc) compare_msguids);
285                 if (item) 
286                         retval = TRUE;
287                 if (win)
288                         *win = item ? MODEST_WINDOW(item->data) : NULL;
289         }
290         
291
292         /* IF It's not in the window list. maybe it's in our uid list... */
293         retval = retval || has_uid (priv->preregistered_uids, uid);
294
295         g_free (uid);
296
297         return retval;
298 }
299
300
301
302 void 
303 modest_window_mgr_register_window (ModestWindowMgr *self, 
304                                    ModestWindow *window)
305 {
306         static gboolean first_time = TRUE;
307         GList *win;
308         gboolean show;
309         ModestWindowMgrPrivate *priv;
310         gint *handler_id;
311
312         g_return_if_fail (MODEST_IS_WINDOW_MGR (self));
313         g_return_if_fail (MODEST_IS_WINDOW (window));
314
315         priv = MODEST_WINDOW_MGR_GET_PRIVATE (self);
316
317         win = g_list_find (priv->window_list, window);
318         if (win) {
319                 g_warning ("Trying to register an already registered window");
320                 return;
321         }
322
323         /* Check that it's not a second main window */
324         if (MODEST_IS_MAIN_WINDOW (window)) {
325                 if (priv->main_window) {
326                         g_warning ("Trying to register a second main window");
327                         return;
328                 } else {
329                         priv->main_window = window;
330                 }
331         }
332
333         /* remove from the list of pre-registered uids */
334         if (MODEST_IS_MSG_VIEW_WINDOW(window)) {
335                 priv->preregistered_uids = 
336                         remove_uid (priv->preregistered_uids,
337                                     modest_msg_view_window_get_message_uid
338                                     (MODEST_MSG_VIEW_WINDOW (window)));
339         }
340         
341         /* Add to list. Keep a reference to the window */
342         g_object_ref (window);
343         priv->window_list = g_list_prepend (priv->window_list, window);
344
345         /* Listen to object destruction */
346         handler_id = g_malloc0 (sizeof (gint));
347         *handler_id = g_signal_connect (window, "delete-event", G_CALLBACK (on_window_destroy), self);
348 /*      *handler_id = g_signal_connect (window, "destroy", G_CALLBACK (on_window_destroy), self); */
349         g_hash_table_insert (priv->destroy_handlers, window, handler_id);
350
351         /* Put into fullscreen if needed */
352         if (priv->fullscreen_mode)
353                 gtk_window_fullscreen (GTK_WINDOW (window));
354
355         /* Fill caches */
356         if (first_time) {
357                 ModestConf *conf = modest_runtime_get_conf ();
358                 priv->show_toolbars = 
359                         modest_conf_get_bool (conf, MODEST_CONF_SHOW_TOOLBAR, NULL);
360                 priv->show_toolbars_fullscreen = 
361                         modest_conf_get_bool (conf, MODEST_CONF_SHOW_TOOLBAR_FULLSCREEN, NULL);
362                 first_time = FALSE;
363         }
364
365         /* Show/hide toolbar */
366         if (priv->fullscreen_mode)
367                 show = priv->show_toolbars_fullscreen;
368         else
369                 show = priv->show_toolbars;
370         modest_window_show_toolbar (window, show);
371 }
372
373 /* static void */
374 /* on_window_destroy (ModestWindow *window, ModestWindowMgr *self) */
375 static gboolean
376 on_window_destroy (ModestWindow *window, 
377                    GdkEvent *event,
378                    ModestWindowMgr *self)
379 {
380         /* Specific stuff first */
381         if (MODEST_IS_MAIN_WINDOW (window)) {
382                 ModestWindowMgrPrivate *priv;
383                 priv = MODEST_WINDOW_MGR_GET_PRIVATE (self);
384
385                 /* If more than one window already opened */
386                 if (g_list_length (priv->window_list) > 1) {
387
388                         /* If the user wants to close all the windows */
389                         if (modest_main_window_close_all (MODEST_MAIN_WINDOW (window))) {
390                                 GList *iter = priv->window_list;
391                                 do {
392                                         if (iter->data != window) {
393                                                 GList *tmp = iter->next;
394                                                 on_window_destroy (MODEST_WINDOW (iter->data),
395                                                                    event,
396                                                                    self);
397                                                 iter = tmp;
398                                         } else {
399                                                 iter = g_list_next (iter);
400                                         }
401                                 } while (iter);
402                         }
403                 }
404         }
405         else {
406                 if (MODEST_IS_MSG_EDIT_WINDOW (window)) {
407                         gboolean sent = FALSE;
408                         gint response = GTK_RESPONSE_ACCEPT;
409                         sent = modest_msg_edit_window_get_sent (MODEST_MSG_EDIT_WINDOW (window));
410                         /* Save currently edited message to Drafts if it was not sent */
411                         if (!sent && modest_msg_edit_window_is_modified (MODEST_MSG_EDIT_WINDOW (window))) {
412                                 
413                                 response =
414                                         modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
415                                                                                  _("mcen_nc_no_email_message_modified_save_changes"));
416                                 /* Save to drafts */
417                                 if (response != GTK_RESPONSE_CANCEL)                            
418                                         modest_ui_actions_on_save_to_drafts (NULL, MODEST_MSG_EDIT_WINDOW (window));
419                                 
420                         }
421                 }
422         }
423
424         /* Unregister window */
425         modest_window_mgr_unregister_window (self, window);
426         
427         return FALSE;
428 }
429
430 void 
431 modest_window_mgr_unregister_window (ModestWindowMgr *self, 
432                                      ModestWindow *window)
433 {
434         GList *win;
435         ModestWindowMgrPrivate *priv;
436         gint *tmp, handler_id;
437
438         g_return_if_fail (MODEST_IS_WINDOW_MGR (self));
439         g_return_if_fail (MODEST_IS_WINDOW (window));
440
441         priv = MODEST_WINDOW_MGR_GET_PRIVATE (self);
442
443         win = g_list_find (priv->window_list, window);
444         if (!win) {
445                 g_warning ("Trying to unregister a window that has not being registered yet");
446                 return;
447         }
448
449         /* If it's the main window unset it */
450         if (priv->main_window == window)
451                 priv->main_window = NULL;
452
453         /* Save state */
454         modest_window_save_state (window);
455
456         /* Remove from list & hash table */
457         priv->window_list = g_list_remove_link (priv->window_list, win);
458         tmp = g_hash_table_lookup (priv->destroy_handlers, window);
459         handler_id = *tmp;
460         g_hash_table_remove (priv->destroy_handlers, window);
461
462         /* Remove the reference to the window. Disconnect also the
463            delete-event handler, we won't need it anymore */
464         g_signal_handler_disconnect (window, handler_id);
465         gtk_widget_destroy (win->data);
466
467         /* If there are no more windows registered then exit program */
468         if (priv->window_list == NULL) {
469                 ModestConf *conf = modest_runtime_get_conf ();
470
471                 /* Save show toolbar status */
472                 modest_conf_set_bool (conf, MODEST_CONF_SHOW_TOOLBAR_FULLSCREEN, 
473                                       priv->show_toolbars_fullscreen, NULL);
474                 modest_conf_set_bool (conf, MODEST_CONF_SHOW_TOOLBAR, 
475                                       priv->show_toolbars, NULL);
476
477                 /* Quit main loop */
478                 gtk_main_quit ();
479         }
480 }
481
482 void
483 modest_window_mgr_set_fullscreen_mode (ModestWindowMgr *self,
484                                        gboolean on)
485 {
486         ModestWindowMgrPrivate *priv;
487         GList *win = NULL;
488
489         g_return_if_fail (MODEST_IS_WINDOW_MGR (self));
490
491         priv = MODEST_WINDOW_MGR_GET_PRIVATE (self);
492
493         /* If there is no change do nothing */
494         if (priv->fullscreen_mode == on)
495                 return;
496
497         priv->fullscreen_mode = on;
498
499         /* Update windows */
500         win = priv->window_list;
501         while (win) {
502                 if (on) {
503                         gtk_window_fullscreen (GTK_WINDOW (win->data));
504                         modest_window_show_toolbar (MODEST_WINDOW (win->data), 
505                                                     priv->show_toolbars_fullscreen);
506                 } else {
507                         gtk_window_unfullscreen (GTK_WINDOW (win->data));
508                         modest_window_show_toolbar (MODEST_WINDOW (win->data), 
509                                                     priv->show_toolbars);
510                 }
511                 win = g_list_next (win);
512         }
513 }
514
515 gboolean
516 modest_window_mgr_get_fullscreen_mode (ModestWindowMgr *self)
517 {
518         ModestWindowMgrPrivate *priv;
519
520         g_return_val_if_fail (MODEST_IS_WINDOW_MGR (self), FALSE);
521
522         priv = MODEST_WINDOW_MGR_GET_PRIVATE (self);
523
524         return priv->fullscreen_mode;
525 }
526
527 void 
528 modest_window_mgr_show_toolbars (ModestWindowMgr *self,
529                                  gboolean show_toolbars,
530                                  gboolean fullscreen)
531 {
532         ModestWindowMgrPrivate *priv;
533
534         g_return_if_fail (MODEST_IS_WINDOW_MGR (self));
535
536         priv = MODEST_WINDOW_MGR_GET_PRIVATE (self);
537
538         /* If nothing changes then return. Otherwise cache it, do not
539            save to GConf for the moment, it will be done when all
540            windows become unregistered in order to avoid unnecessary
541            ModestConf calls */
542         if (fullscreen) {
543                 if (priv->show_toolbars_fullscreen == show_toolbars)
544                         return;
545                 else
546                         priv->show_toolbars_fullscreen = show_toolbars;
547         } else {
548                 if (priv->show_toolbars == show_toolbars)
549                         return;
550                 else
551                         priv->show_toolbars = show_toolbars;
552         }
553
554         /* Apply now if the view mode is the right one */
555         if ((fullscreen && priv->fullscreen_mode) ||
556             (!fullscreen && !priv->fullscreen_mode)) {
557
558                 GList *win = priv->window_list;
559
560                 while (win) {
561                         modest_window_show_toolbar (MODEST_WINDOW (win->data),
562                                                     show_toolbars);
563                         win = g_list_next (win);
564                 }
565         }
566 }
567
568 ModestWindow*  
569 modest_window_mgr_get_main_window (ModestWindowMgr *self)
570 {
571         ModestWindowMgrPrivate *priv;
572
573         g_return_val_if_fail (MODEST_IS_WINDOW_MGR (self), NULL);
574
575         priv = MODEST_WINDOW_MGR_GET_PRIVATE (self);
576
577         return priv->main_window;
578 }
579
580 static void
581 on_nonhibernating_window_hide(GtkWidget *widget, gpointer user_data)
582 {
583         ModestWindowMgr *self = MODEST_WINDOW_MGR (user_data);
584         ModestWindowMgrPrivate *priv = MODEST_WINDOW_MGR_GET_PRIVATE (self);
585         
586         /* Forget this window,
587          * so hibernation will be allowed again if no windows are remembered: */
588         priv->windows_that_prevent_hibernation =
589                 g_slist_remove (priv->windows_that_prevent_hibernation, GTK_WINDOW(widget));
590 }
591
592 static void
593 on_nonhibernating_window_show(GtkWidget *widget, gpointer user_data)
594 {
595         ModestWindowMgr *self = MODEST_WINDOW_MGR (user_data);
596         ModestWindowMgrPrivate *priv = MODEST_WINDOW_MGR_GET_PRIVATE (self);
597         
598         GtkWindow *window = GTK_WINDOW (widget);
599         
600         priv->windows_that_prevent_hibernation = 
601                         g_slist_append (priv->windows_that_prevent_hibernation, window);
602         
603         /* Allow hibernation again when the window has been hidden: */
604         g_signal_connect (window, "hide", 
605                 G_CALLBACK (on_nonhibernating_window_hide), self);
606 }
607
608 void modest_window_mgr_prevent_hibernation_while_window_is_shown (ModestWindowMgr *self,
609                                                                   GtkWindow *window)
610 {
611         g_return_if_fail (MODEST_IS_WINDOW_MGR (self));
612         
613         if (GTK_WIDGET_VISIBLE(window)) {
614                 on_nonhibernating_window_show (GTK_WIDGET (window), self);
615         } else {
616                 /* Wait for it to be shown: */
617                 g_signal_connect (window, "show", 
618                         G_CALLBACK (on_nonhibernating_window_show), self);      
619         }
620 }
621
622 gboolean modest_window_mgr_get_hibernation_is_prevented (ModestWindowMgr *self)
623 {
624         g_return_val_if_fail (MODEST_IS_WINDOW_MGR (self), FALSE);
625         
626         ModestWindowMgrPrivate *priv = MODEST_WINDOW_MGR_GET_PRIVATE (self);
627         
628         /* Prevent hibernation if any open windows are currently 
629          * preventing hibernation: */
630         return (g_slist_length (priv->windows_that_prevent_hibernation) > 0);
631 }
632
633
634 void modest_window_mgr_save_state_for_all_windows (ModestWindowMgr *self)
635 {
636         g_return_if_fail (MODEST_IS_WINDOW_MGR (self));
637         
638         ModestWindowMgrPrivate *priv = MODEST_WINDOW_MGR_GET_PRIVATE (self);
639
640         /* Iterate over all windows */
641         GList *win = priv->window_list;
642         while (win) {
643                 ModestWindow *window = MODEST_WINDOW (win->data);
644                 if (window) {
645                         /* This calls the vfunc, 
646                          * so each window can do its own thing: */
647                         modest_window_save_state (window);
648                 }       
649                 
650                 win = g_list_next (win);
651         }
652 }