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