2008-11-25 Alberto Garcia <agarcia@igalia.com>
[hildon] / src / hildon-program.c
1 /*
2  * This file is a part of hildon
3  *
4  * Copyright (C) 2006 Nokia Corporation, all rights reserved.
5  *
6  * Contact: Michael Dominic Kostrzewa <michael.kostrzewa@nokia.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public License
10  * as published by the Free Software Foundation; version 2.1 of
11  * the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21  * 02110-1301 USA
22  *
23  */
24
25 /**
26  * SECTION:hildon-program
27  * @short_description: An object that represents an application running in the Hildon framework.
28  * @see_also: #HildonWindow, #HildonStackableWindow
29  *
30  * The #HildonProgram is an object used to represent an application running
31  * in the Hildon framework.
32  *
33  * Such an application is thought to have one or more #HildonWindow. These
34  * shall be registered to the #HildonProgram with hildon_program_add_window(),
35  * and can be unregistered similarly with hildon_program_remove_window().
36  *
37  * The #HildonProgram provides the programmer with commodities such
38  * as applying a common toolbar and menu to all the #HildonWindow
39  * registered to it. This is done with hildon_program_set_common_menu()
40  * and hildon_program_set_common_toolbar().
41  *
42  * The #HildonProgram is also used to apply program-wide properties that
43  * are specific to the Hildon framework. For instance
44  * hildon_program_set_can_hibernate() sets whether or not an application
45  * can be set to hibernate by the Hildon task navigator, in situations of
46  * low memory.
47  *
48  * <example>
49  * <programlisting>
50  * HildonProgram *program;
51  * HildonWindow *window1;
52  * HildonWindow *window2;
53  * GtkToolbar *common_toolbar, *window_specific_toolbar;
54  * GtkMenu *menu;
55  * <!-- -->
56  * program = HILDON_PROGRAM (hildon_program_get_instance ());
57  * <!-- -->
58  * window1 = HILDON_WINDOW (hildon_window_new ());
59  * window2 = HILDON_WINDOW (hildon_window_new ());
60  * <!-- -->
61  * common_toolbar = create_common_toolbar ();
62  * window_specific_toolbar = create_window_specific_toolbar ();
63  * <!-- -->
64  * menu = create_menu ();
65  * <!-- -->
66  * hildon_program_add_window (program, window1);
67  * hildon_program_add_window (program, window2);
68  * <!-- -->
69  * hildon_program_set_common_menu (program, menu);
70  * <!-- -->
71  * hildon_program_set_common_toolbar (program, common_toolbar);
72  * hildon_window_add_toolbar (window1, window_specific_toolbar);
73  * <!-- -->
74  * hildon_program_set_can_hibernate (program, TRUE);
75  * </programlisting>
76  * </example>
77  */
78
79 #undef                                          HILDON_DISABLE_DEPRECATED
80
81 #ifdef                                          HAVE_CONFIG_H
82 #include                                        <config.h>
83 #endif
84
85 #include                                        <X11/Xatom.h>
86
87 #include                                        "hildon-program.h"
88 #include                                        "hildon-program-private.h"
89 #include                                        "hildon-window-private.h"
90 #include                                        "hildon-window-stack.h"
91
92 static void
93 hildon_program_init                             (HildonProgram *self);
94
95 static void
96 hildon_program_finalize                         (GObject *self);
97
98 static void
99 hildon_program_class_init                       (HildonProgramClass *self);
100
101 static void
102 hildon_program_get_property                     (GObject *object, 
103                                                  guint property_id,
104                                                  GValue *value, 
105                                                  GParamSpec *pspec);
106 static void
107 hildon_program_set_property                     (GObject *object, 
108                                                  guint property_id,
109                                                  const GValue *value, 
110                                                  GParamSpec *pspec);
111
112 enum
113 {
114     PROP_0,
115     PROP_IS_TOPMOST,
116     PROP_KILLABLE
117 };
118
119 GType G_GNUC_CONST
120 hildon_program_get_type                         (void)
121 {
122     static GType program_type = 0;
123
124     if (! program_type)
125     {
126         static const GTypeInfo program_info =
127         {
128             sizeof (HildonProgramClass),
129             NULL,       /* base_init */
130             NULL,       /* base_finalize */
131             (GClassInitFunc) hildon_program_class_init,
132             NULL,       /* class_finalize */
133             NULL,       /* class_data */
134             sizeof (HildonProgram),
135             0,  /* n_preallocs */
136             (GInstanceInitFunc) hildon_program_init,
137         };
138         program_type = g_type_register_static(G_TYPE_OBJECT,
139                 "HildonProgram", &program_info, 0);
140     }
141     return program_type;
142 }
143
144 static void
145 hildon_program_init                             (HildonProgram *self)
146 {
147     HildonProgramPrivate *priv = HILDON_PROGRAM_GET_PRIVATE (self);
148     g_assert (priv);
149     
150     priv->killable = FALSE;
151     priv->window_count = 0;
152     priv->is_topmost = FALSE;
153     priv->window_group = GDK_WINDOW_XID (gdk_display_get_default_group (gdk_display_get_default()));
154     priv->common_toolbar = NULL;
155     priv->windows = NULL;
156 }
157
158 static void
159 hildon_program_finalize                         (GObject *self)
160 {
161     HildonProgramPrivate *priv = HILDON_PROGRAM_GET_PRIVATE (HILDON_PROGRAM (self));
162     g_assert (priv);
163     
164     if (priv->common_toolbar)
165     {
166         g_object_unref (priv->common_toolbar);
167         priv->common_toolbar = NULL;
168     }
169
170     if (priv->common_menu)
171     {
172         g_object_unref (priv->common_menu);
173         priv->common_menu = NULL;
174     }
175 }
176
177 static void
178 hildon_program_class_init                       (HildonProgramClass *self)
179 {
180     GObjectClass *object_class = G_OBJECT_CLASS (self);
181
182     g_type_class_add_private (self, sizeof (HildonProgramPrivate));
183
184     /* Set up object virtual functions */
185     object_class->finalize      = hildon_program_finalize;
186     object_class->set_property  = hildon_program_set_property;
187     object_class->get_property  = hildon_program_get_property;
188
189     /* Install properties */
190
191     /**
192      * HildonProgram:is-topmost:
193      *
194      * Whether one of the program's window or dialog currently
195      * is activated by window manager. 
196      */
197     g_object_class_install_property (object_class, PROP_IS_TOPMOST,
198                 g_param_spec_boolean ("is-topmost",
199                 "Is top-most",
200                 "Whether one of the program's window or dialog currently "
201                 "is activated by window manager",
202                 FALSE,
203                 G_PARAM_READABLE)); 
204
205     /**
206      * HildonProgram:can-hibernate:
207      *
208      * Whether the program should be set to hibernate by the Task
209      * Navigator in low memory situation.
210      */
211     g_object_class_install_property (object_class, PROP_KILLABLE,
212                 g_param_spec_boolean ("can-hibernate",
213                 "Can hibernate",
214                 "Whether the program should be set to hibernate by the Task "
215                 "Navigator in low memory situation",
216                 FALSE,
217                 G_PARAM_READWRITE)); 
218     return;
219 }
220
221 static void
222 hildon_program_set_property                     (GObject *object, 
223                                                  guint property_id,
224                                                  const GValue *value, 
225                                                  GParamSpec *pspec)
226 {
227     switch (property_id) {
228
229         case PROP_KILLABLE:
230             hildon_program_set_can_hibernate (HILDON_PROGRAM (object), g_value_get_boolean (value));
231             break;
232             
233         default:
234             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
235             break;
236     }
237
238 }
239
240 static void
241 hildon_program_get_property                     (GObject *object, 
242                                                  guint property_id,
243                                                  GValue *value, 
244                                                  GParamSpec *pspec)
245 {
246     HildonProgramPrivate *priv = HILDON_PROGRAM_GET_PRIVATE (object);
247     g_assert (priv);
248
249     switch (property_id)
250     {
251         case PROP_KILLABLE:
252             g_value_set_boolean (value, priv->killable);
253             break;
254
255         case PROP_IS_TOPMOST:
256             g_value_set_boolean (value, priv->is_topmost);
257             break;
258
259         default:
260             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
261             break;
262     }
263 }
264
265 /**
266  * hildon_program_pop_window_stack:
267  * @self: A #HildonProgram
268  *
269  * Deprecated: Use hildon_window_stack_pop() instead
270  *
271  * Returns: A #HildonStackableWindow, or %NULL
272  */
273 HildonStackableWindow *
274 hildon_program_pop_window_stack                 (HildonProgram *self)
275 {
276     HildonWindowStack *stack = hildon_window_stack_get_default ();
277     GtkWidget *win = hildon_window_stack_pop_1 (stack);
278     g_warning ("%s: this function is deprecated. Use hildon_window_stack_pop() instead", __FUNCTION__);
279     return win ? HILDON_STACKABLE_WINDOW (win) : NULL;
280 }
281
282 /**
283  * hildon_program_peek_window_stack:
284  * @self: A #HildonProgram
285  *
286  * Deprecated: Use hildon_window_stack_peek() instead
287  *
288  * Returns: A #HildonStackableWindow, or %NULL
289  */
290 HildonStackableWindow *
291 hildon_program_peek_window_stack                (HildonProgram *self)
292 {
293     HildonWindowStack *stack = hildon_window_stack_get_default ();
294     GtkWidget *win = hildon_window_stack_peek (stack);
295     g_warning ("%s: this function is deprecated. Use hildon_window_stack_peek() instead", __FUNCTION__);
296     return win ? HILDON_STACKABLE_WINDOW (win) : NULL;
297 }
298
299 /* Utilities */
300 static gint 
301 hildon_program_window_list_compare              (gconstpointer window_a, 
302                                                  gconstpointer window_b)
303 {
304     g_return_val_if_fail (HILDON_IS_WINDOW(window_a) && 
305                           HILDON_IS_WINDOW(window_b), 1);
306
307     return window_a != window_b;
308 }
309
310 /*
311  * foreach function, checks if a window is topmost and acts consequently
312  */
313 static void
314 hildon_program_window_list_is_is_topmost        (gpointer data, 
315                                                  gpointer window_id_)
316 {
317     if (data && HILDON_IS_WINDOW (data))
318     {
319         HildonWindow *window = HILDON_WINDOW (data);
320         Window window_id = * (Window*)window_id_;
321
322         hildon_window_update_topmost (window, window_id);
323     }
324 }
325
326 /*
327  * Check the _MB_CURRENT_APP_WINDOW on the root window, and update
328  * the top_most status accordingly
329  */
330 static void
331 hildon_program_update_top_most                  (HildonProgram *program)
332 {
333     XWMHints *wm_hints;
334     Window active_window;
335     HildonProgramPrivate *priv;
336
337     priv = HILDON_PROGRAM_GET_PRIVATE (program);
338     g_assert (priv);
339     
340     active_window = hildon_window_get_active_window();
341
342     if (active_window)
343     {
344       gint xerror;
345       
346       gdk_error_trap_push ();
347       wm_hints = XGetWMHints (GDK_DISPLAY (), active_window);
348       xerror = gdk_error_trap_pop ();
349       if (xerror)
350         return;
351
352       if (wm_hints)
353       {
354
355           if (wm_hints->window_group == priv->window_group)
356           {
357               if (!priv->is_topmost)
358               {
359                   priv->is_topmost = TRUE;
360                   g_object_notify (G_OBJECT (program), "is-topmost");
361               }
362           }
363           else if (priv->is_topmost)
364           {
365             priv->is_topmost = FALSE;
366             g_object_notify (G_OBJECT (program), "is-topmost");
367           }
368       }
369       XFree (wm_hints);
370     }
371
372     /* Check each window if it was is_topmost */
373     g_slist_foreach (priv->windows, 
374             (GFunc)hildon_program_window_list_is_is_topmost, &active_window);
375 }
376
377 /*
378  * We keep track of the _MB_CURRENT_APP_WINDOW property on the root window,
379  * to detect when a window belonging to this program was is_topmost. This
380  * is based on the window group WM hint.
381  */
382 static GdkFilterReturn
383 hildon_program_root_window_event_filter         (GdkXEvent *xevent,
384                                                  GdkEvent *event,
385                                                  gpointer data)
386 {
387     XAnyEvent *eventti = xevent;
388     HildonProgram *program = HILDON_PROGRAM (data);
389     Atom active_app_atom =
390             XInternAtom (GDK_DISPLAY (), "_MB_CURRENT_APP_WINDOW", False);
391
392     if (eventti->type == PropertyNotify)
393     {
394         XPropertyEvent *pevent = xevent;
395
396         if (pevent->atom == active_app_atom)
397         {
398             hildon_program_update_top_most (program);
399         }
400     }
401
402     return GDK_FILTER_CONTINUE;
403 }
404
405 /* 
406  * Checks if the window is the topmost window of the program and in
407  * that case forces the window to take the common toolbar.
408  */
409 static void
410 hildon_program_common_toolbar_topmost_window    (gpointer window, 
411                                                  gpointer data)
412 {
413     if (HILDON_IS_WINDOW (window) && hildon_window_get_is_topmost (HILDON_WINDOW (window)))
414         hildon_window_take_common_toolbar (HILDON_WINDOW (window));
415 }
416
417 /**
418  * hildon_program_get_instance:
419  *
420  * Return value: Returns the #HildonProgram for the current process.
421  * The object is created on the first call. Note that you're not supposed 
422  * to unref the returned object since it's not reffed in the first place.
423  **/
424 HildonProgram*
425 hildon_program_get_instance                     (void)
426 {
427     static HildonProgram *program = NULL;
428
429     if (! program)
430     {
431         program = g_object_new (HILDON_TYPE_PROGRAM, NULL);
432     }
433
434     return program;
435 }
436
437 /**
438  * hildon_program_add_window:
439  * @self: The #HildonProgram to which the window should be registered
440  * @window: A #HildonWindow to be added
441  *
442  * Registers a #HildonWindow as belonging to a given #HildonProgram. This
443  * allows to apply program-wide settings as all the registered windows,
444  * such as hildon_program_set_common_menu() and
445  * hildon_pogram_set_common_toolbar()
446  **/
447 void
448 hildon_program_add_window                       (HildonProgram *self, 
449                                                  HildonWindow *window)
450 {
451     HildonProgramPrivate *priv;
452     
453     g_return_if_fail (HILDON_IS_PROGRAM (self));
454     
455     priv = HILDON_PROGRAM_GET_PRIVATE (self);
456     g_assert (priv);
457
458     if (g_slist_find_custom (priv->windows, window,
459            hildon_program_window_list_compare) )
460     {
461         /* We already have that window */
462         return;
463     }
464
465     if (!priv->window_count)
466     {
467         hildon_program_update_top_most (self);
468         
469         /* Now that we have a window we should start keeping track of
470          * the root window */
471         gdk_window_set_events (gdk_get_default_root_window (),
472                 gdk_window_get_events (gdk_get_default_root_window ()) | GDK_PROPERTY_CHANGE_MASK);
473
474         gdk_window_add_filter (gdk_get_default_root_window (),
475                 hildon_program_root_window_event_filter, self );
476     }
477     
478     hildon_window_set_can_hibernate_property (window, &priv->killable);
479
480     hildon_window_set_program (window, G_OBJECT (self));
481
482     priv->windows = g_slist_append (priv->windows, window);
483     priv->window_count ++;
484 }
485
486 /**
487  * hildon_program_remove_window:
488  * @self: The #HildonProgram to which the window should be unregistered
489  * @window: The @HildonWindow to unregister
490  *
491  * Used to unregister a window from the program. Subsequent calls to
492  * hildon_program_set_common_menu() and hildon_pogram_set_common_toolbar()
493  * will not affect the window
494  **/
495 void
496 hildon_program_remove_window                    (HildonProgram *self, 
497                                                  HildonWindow *window)
498 {
499     HildonProgramPrivate *priv;
500     
501     g_return_if_fail (HILDON_IS_PROGRAM (self));
502     
503     priv = HILDON_PROGRAM_GET_PRIVATE (self);
504     g_assert (priv);
505     
506     hildon_window_unset_program (window);
507
508     priv->windows = g_slist_remove (priv->windows, window);
509
510     priv->window_count --;
511
512     if (! priv->window_count)
513         gdk_window_remove_filter (gdk_get_default_root_window(),
514                 hildon_program_root_window_event_filter,
515                 self);
516 }
517
518 /**
519  * hildon_program_set_can_hibernate:
520  * @self: The #HildonProgram which can hibernate or not
521  * @can_hibernate: whether or not the #HildonProgram can hibernate
522  *
523  * Used to set whether or not the Hildon task navigator should
524  * be able to set the program to hibernation in case of low memory
525  **/
526 void
527 hildon_program_set_can_hibernate                (HildonProgram *self, 
528                                                  gboolean can_hibernate)
529 {
530     HildonProgramPrivate *priv;
531     
532     g_return_if_fail (HILDON_IS_PROGRAM (self));
533     
534     priv = HILDON_PROGRAM_GET_PRIVATE (self);
535     g_assert (priv);
536
537     if (priv->killable != can_hibernate)
538     {
539         g_slist_foreach (priv->windows, 
540                 (GFunc) hildon_window_set_can_hibernate_property, &can_hibernate);
541     }
542
543     priv->killable = can_hibernate;
544 }
545
546 /**
547  * hildon_program_get_can_hibernate:
548  * @self: The #HildonProgram which can hibernate or not
549  * 
550  * Return value: Whether or not this #HildonProgram is set to be
551  * support hibernation from the Hildon task navigator
552  **/
553 gboolean
554 hildon_program_get_can_hibernate                (HildonProgram *self)
555 {
556     HildonProgramPrivate *priv;
557     
558     g_return_val_if_fail (HILDON_IS_PROGRAM (self), FALSE);
559    
560     priv = HILDON_PROGRAM_GET_PRIVATE (self);
561     g_assert (priv);
562
563     return priv->killable;
564 }
565
566 /**
567  * hildon_program_set_common_menu:
568  * @self: The #HildonProgram in which the common menu should be used
569  * @menu: A GtkMenu to use as common menu for the program
570  *
571  * Sets a GtkMenu that will appear in all the #HildonWindow registered
572  * with the #HildonProgram. Only one common GtkMenu can be set, further
573  * calls will detach the previous common GtkMenu. A #HildonWindow
574  * can use it's own GtkMenu with hildon_window_set_menu()
575  *
576  * This method does not support #HildonAppMenu objects.
577  **/
578 void
579 hildon_program_set_common_menu                  (HildonProgram *self, 
580                                                  GtkMenu *menu)
581 {
582     HildonProgramPrivate *priv;
583
584     g_return_if_fail (HILDON_IS_PROGRAM (self));
585
586     priv = HILDON_PROGRAM_GET_PRIVATE (self);
587     g_assert (priv);
588
589     if (priv->common_menu)
590     {
591         if (GTK_WIDGET_VISIBLE (priv->common_menu))
592         {
593             gtk_menu_popdown (GTK_MENU (priv->common_menu));
594             gtk_menu_shell_deactivate (GTK_MENU_SHELL (priv->common_menu));
595         }
596
597         if (gtk_menu_get_attach_widget (GTK_MENU (priv->common_menu)))
598         {
599             gtk_menu_detach (GTK_MENU (priv->common_menu));
600         }
601         else
602         {
603             g_object_unref (priv->common_menu);
604         }
605     }
606
607     priv->common_menu = GTK_WIDGET (menu);
608
609     if (priv->common_menu)
610     {
611         g_object_ref (menu);
612         gtk_object_sink (GTK_OBJECT (menu));
613         gtk_widget_show_all (GTK_WIDGET (menu));
614     }
615 }
616
617 /**
618  * hildon_program_get_common_menu:
619  * @self: The #HildonProgram from which to retrieve the common menu
620  *
621  * Return value: the GtkMenu that was set as common menu for this
622  * #HildonProgram, or %NULL of no common menu was set.
623  **/
624 GtkMenu*
625 hildon_program_get_common_menu                  (HildonProgram *self)
626 {
627     HildonProgramPrivate *priv;
628
629     g_return_val_if_fail (HILDON_IS_PROGRAM (self), NULL);
630
631     priv = HILDON_PROGRAM_GET_PRIVATE (self);
632     g_assert (priv);
633
634     return GTK_MENU (priv->common_menu);
635 }
636
637 /**
638  * hildon_program_set_common_toolbar:
639  * @self: The #HildonProgram in which the common toolbar should be used
640  * @toolbar: A GtkToolbar to use as common toolbar for the program
641  *
642  * Sets a GtkToolbar that will appear in all the #HildonWindow registered
643  * to the #HildonProgram. Only one common GtkToolbar can be set, further
644  * call will detach the previous common GtkToolbar. A #HildonWindow
645  * can use its own GtkToolbar with hildon_window_add_toolbar(). Both
646  * #HildonProgram and #HildonWindow specific toolbars will be shown
647  **/
648 void
649 hildon_program_set_common_toolbar               (HildonProgram *self, 
650                                                  GtkToolbar *toolbar)
651 {
652     HildonProgramPrivate *priv;
653
654     g_return_if_fail (HILDON_IS_PROGRAM (self));
655
656     priv = HILDON_PROGRAM_GET_PRIVATE (self);
657     g_assert (priv);
658
659     if (priv->common_toolbar)
660     {
661         if (priv->common_toolbar->parent)
662         {
663             gtk_container_remove (GTK_CONTAINER (priv->common_toolbar->parent), 
664                                   priv->common_toolbar);
665         }
666         
667         g_object_unref (priv->common_toolbar);
668     }
669
670     priv->common_toolbar = GTK_WIDGET (toolbar);
671
672     if (priv->common_toolbar)
673     {
674         g_object_ref (priv->common_toolbar);
675         gtk_object_sink (GTK_OBJECT (priv->common_toolbar) );
676     }
677
678     /* if the program is the topmost we have to update the common
679        toolbar right now for the topmost window */
680     if (priv->is_topmost)
681       {
682         g_slist_foreach (priv->windows, 
683                          (GFunc) hildon_program_common_toolbar_topmost_window, NULL);
684       }
685 }
686
687 /**
688  * hildon_program_get_common_toolbar:
689  * @self: The #HildonProgram from which to retrieve the common toolbar
690  *
691  * Return value: the GtkToolbar that was set as common toolbar for this
692  * #HildonProgram, or %NULL of no common menu was set.
693  **/
694 GtkToolbar*
695 hildon_program_get_common_toolbar               (HildonProgram *self)
696 {
697     HildonProgramPrivate *priv;
698
699     g_return_val_if_fail (HILDON_IS_PROGRAM (self), NULL);
700
701     priv = HILDON_PROGRAM_GET_PRIVATE (self);
702     g_assert (priv);
703
704     return priv->common_toolbar ? GTK_TOOLBAR (priv->common_toolbar) : NULL;
705 }
706
707 /**
708  * hildon_program_get_is_topmost:
709  * @self: A #HildonWindow
710  *
711  * Return value: Whether or not one of the program's window or dialog is 
712  * currenltly activated by the window manager.
713  **/
714 gboolean
715 hildon_program_get_is_topmost                   (HildonProgram *self)
716 {
717     HildonProgramPrivate *priv;
718
719     g_return_val_if_fail (HILDON_IS_PROGRAM (self), FALSE);
720     
721     priv = HILDON_PROGRAM_GET_PRIVATE (self);
722     g_assert (priv);
723
724     return priv->is_topmost;
725 }
726
727 /**
728  * hildon_program_go_to_root_window:
729  * @self: A #HildonProgram
730  *
731  * Deprecated: See #HildonWindowStack
732  */
733 void
734 hildon_program_go_to_root_window                (HildonProgram *self)
735 {
736     HildonWindowStack *stack = hildon_window_stack_get_default ();
737     gint n = hildon_window_stack_size (stack);
738     g_warning ("%s: this function is deprecated. Use hildon_window_stack_pop() instead.", __FUNCTION__);
739     if (n > 1) {
740         hildon_window_stack_pop (stack, n-1, NULL);
741     }
742 }