2006-09-12 Michael Dominic Kostrzewa <michael.kostrzewa@nokia.com>
[hildon] / hildon-widgets / hildon-program.c
1 /*
2  * This file is part of hildon-libs
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 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  * @file hildon-program.c
27  *
28  * This file implements the HildonProgram object
29  *
30  */
31
32 #include "hildon-program.h"
33 #include "hildon-window-private.h"
34
35 /*FIXME*/
36 #include <X11/Xatom.h>
37
38
39 #define HILDON_PROGRAM_GET_PRIVATE(obj) \
40   (G_TYPE_INSTANCE_GET_PRIVATE ((obj), HILDON_TYPE_PROGRAM, HildonProgramPriv));
41
42
43 typedef struct _HildonProgramPriv HildonProgramPriv;
44
45 struct _HildonProgramPriv
46 {
47     gboolean killable;
48     gboolean is_topmost;
49     GdkWindow *group_leader;
50     guint window_count;
51     GtkWidget *common_menu;
52     GtkWidget *common_toolbar;
53     GSList *windows;
54     Window window_group;
55     gchar *name;
56 };
57
58 static void
59 hildon_program_init (HildonProgram *self);
60
61 static void
62 hildon_program_finalize (GObject *self);
63
64 static void
65 hildon_program_class_init (HildonProgramClass *self);
66
67 static void
68 hildon_program_get_property(GObject * object, guint property_id,
69                         GValue * value, GParamSpec * pspec);
70 static void
71 hildon_program_set_property (GObject * object, guint property_id,
72                              const GValue * value, GParamSpec * pspec);
73
74 enum
75 {
76     PROP_0,
77     PROP_IS_TOPMOST,
78     PROP_KILLABLE
79 };
80
81
82 GType
83 hildon_program_get_type (void)
84 {
85     static GType program_type = 0;
86
87     if (!program_type)
88     {
89         static const GTypeInfo program_info =
90         {
91             sizeof(HildonProgramClass),
92             NULL,       /* base_init */
93             NULL,       /* base_finalize */
94             (GClassInitFunc) hildon_program_class_init,
95             NULL,       /* class_finalize */
96             NULL,       /* class_data */
97             sizeof(HildonProgram),
98             0,  /* n_preallocs */
99             (GInstanceInitFunc) hildon_program_init,
100         };
101         program_type = g_type_register_static(G_TYPE_OBJECT,
102                 "HildonProgram", &program_info, 0);
103     }
104     return program_type;
105 }
106
107 static void
108 hildon_program_init (HildonProgram *self)
109 {
110     HildonProgramPriv *priv = HILDON_PROGRAM_GET_PRIVATE (self);
111
112     priv->killable = FALSE;
113     priv->window_count = 0;
114     priv->is_topmost = FALSE;
115     priv->window_group = GDK_WINDOW_XID (gdk_display_get_default_group
116                                   (gdk_display_get_default()));
117     priv->common_toolbar = NULL;
118     priv->name = NULL;
119 }
120
121 static void
122 hildon_program_finalize (GObject *self)
123 {
124     HildonProgramPriv *priv = HILDON_PROGRAM_GET_PRIVATE (HILDON_PROGRAM (self));
125     
126     if (priv->common_toolbar)
127     {
128         g_object_unref (priv->common_toolbar);
129         priv->common_toolbar = NULL;
130     }
131
132     if (priv->common_menu)
133     {
134         g_object_unref (priv->common_menu);
135         priv->common_menu = NULL;
136     }
137
138     g_free (priv->name);
139
140 }
141
142 static void
143 hildon_program_class_init (HildonProgramClass *self)
144 {
145     GObjectClass *object_class = G_OBJECT_CLASS(self);
146
147     g_type_class_add_private (self, sizeof(HildonProgramPriv));
148
149     /* Set up object virtual functions */
150     object_class->finalize = hildon_program_finalize;
151     object_class->set_property = hildon_program_set_property;
152     object_class->get_property = hildon_program_get_property;
153
154     /* Install properties */
155     g_object_class_install_property (object_class, PROP_IS_TOPMOST,
156                 g_param_spec_boolean ("is-topmost",
157                 "Is top-most",
158                 "Whether one of the program's window or dialog currently "
159                 "is activated by window manager",
160                 FALSE,
161                 G_PARAM_READABLE)); 
162     
163     g_object_class_install_property (object_class, PROP_KILLABLE,
164                 g_param_spec_boolean ("can-hibernate",
165                 "Can hibernate",
166                 "Whether the program should be set to hibernate by the Task "
167                 "Navigator in low memory situation",
168                 FALSE,
169                 G_PARAM_READWRITE)); 
170     return;
171 }
172
173
174 static void
175 hildon_program_set_property (GObject * object, guint property_id,
176                              const GValue * value, GParamSpec * pspec)
177 {
178     switch (property_id){
179         case PROP_KILLABLE:
180             hildon_program_set_can_hibernate (HILDON_PROGRAM (object),
181                                          g_value_get_boolean (value));
182             break;
183             
184         default:
185             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
186             break;
187     }
188
189 }
190
191 static void
192 hildon_program_get_property (GObject * object, guint property_id,
193                              GValue * value, GParamSpec * pspec)
194 {
195     HildonProgramPriv *priv = HILDON_PROGRAM_GET_PRIVATE (object);
196
197      switch (property_id)
198      {
199          case PROP_KILLABLE:
200                g_value_set_boolean (value, priv->killable);
201                break;
202          case PROP_IS_TOPMOST:
203                g_value_set_boolean (value, priv->is_topmost);
204                break;
205          default:
206                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
207                break;
208      }
209                
210 }
211
212 /* Utilities */
213 static gint 
214 hildon_program_window_list_compare (gconstpointer window_a, 
215                                     gconstpointer window_b)
216 {
217     g_return_val_if_fail (1, HILDON_IS_WINDOW(window_a) && 
218                              HILDON_IS_WINDOW(window_b));
219
220     return window_a != window_b;
221 }
222
223 /*
224  * foreach function, checks if a window is topmost and acts consequently
225  */
226 static void
227 hildon_program_window_list_is_is_topmost (gpointer data, gpointer window_id_)
228 {
229     if (data && HILDON_IS_WINDOW (data))
230     {
231         HildonWindow *window = HILDON_WINDOW (data);
232         Window window_id = * (Window*)window_id_;
233
234         hildon_window_update_topmost (window, window_id);
235     }
236 }
237
238 /*
239  * Check the _MB_CURRENT_APP_WINDOW on the root window, and update
240  * the top_most status accordingly
241  */
242 static void
243 hildon_program_update_top_most (HildonProgram *program)
244 {
245     XWMHints *wm_hints;
246     Window active_window;
247     HildonProgramPriv *priv;
248
249     priv = HILDON_PROGRAM_GET_PRIVATE (program);
250     
251     active_window = hildon_window_get_active_window();
252
253     if (active_window)
254     {
255       wm_hints = XGetWMHints (GDK_DISPLAY (), active_window);
256
257       if (wm_hints)
258       {
259
260           if (wm_hints->window_group == priv->window_group)
261           {
262               if (!priv->is_topmost)
263               {
264                   priv->is_topmost = TRUE;
265                   g_object_notify (G_OBJECT (program), "is-topmost");
266               }
267           }
268           else if (priv->is_topmost)
269           {
270             priv->is_topmost = FALSE;
271             g_object_notify (G_OBJECT (program), "is-topmost");
272           }
273       }
274       XFree (wm_hints);
275     }
276
277     /* Check each window if it was is_topmost */
278     g_slist_foreach (priv->windows, 
279             (GFunc)hildon_program_window_list_is_is_topmost, &active_window);
280 }
281
282
283 /* Event filter */
284
285 /*
286  * We keep track of the _MB_CURRENT_APP_WINDOW property on the root window,
287  * to detect when a window belonging to this program was is_topmost. This
288  * is based on the window group WM hint.
289  */
290 static GdkFilterReturn
291 hildon_program_root_window_event_filter (GdkXEvent *xevent,
292                                          GdkEvent *event,
293                                          gpointer data)
294 {
295     XAnyEvent *eventti = xevent;
296     HildonProgram *program = HILDON_PROGRAM (data);
297     Atom active_app_atom =
298             XInternAtom (GDK_DISPLAY (), "_MB_CURRENT_APP_WINDOW", False);
299
300     if (eventti->type == PropertyNotify)
301     {
302         XPropertyEvent *pevent = xevent;
303
304         if (pevent->atom == active_app_atom)
305         {
306             hildon_program_update_top_most (program);
307         }
308     }
309
310     return GDK_FILTER_CONTINUE;
311 }
312     
313
314 /**
315  * hildon_program_common_toolbar_topmost_window:
316  * @window: A @HildonWindow to be informed about its new common toolbar
317  * @data: Not used, it is here just to respect the API
318  *
319  * Checks if the window is the topmost window of the program and in
320  * that case forces the window to take the common toolbar.
321  **/
322 static void
323 hildon_program_common_toolbar_topmost_window (gpointer window, gpointer data)
324 {
325     if (HILDON_IS_WINDOW (window) && 
326             hildon_window_get_is_topmost (HILDON_WINDOW (window)))
327     {
328         hildon_window_take_common_toolbar (HILDON_WINDOW (window));
329     }
330 }
331
332 /* Public methods */
333
334 /**
335  * hildon_program_get_instance:
336  *
337  * Return value: Returns the #HildonProgram for the current process.
338  * The object is created on the first call.
339  **/
340 HildonProgram *
341 hildon_program_get_instance ()
342 {
343     static HildonProgram *program = NULL;
344
345     if (!program)
346     {
347         program = g_object_new (HILDON_TYPE_PROGRAM, NULL);
348     }
349
350     return program;
351 }
352
353 /**
354  * hildon_program_add_window:
355  * @program: The @HildonProgram to which the window should be registered
356  * @window: A @HildonWindow to be added
357  *
358  * Registers a @HildonWindow as belonging to a given @HildonProgram. This
359  * allows to apply program-wide settings as all the registered windows,
360  * such as hildon_program_set_common_menu() and
361  * hildon_pogram_set_common_toolbar()
362  **/
363 void
364 hildon_program_add_window (HildonProgram *self, HildonWindow *window)
365 {
366     HildonProgramPriv *priv;
367     
368     g_return_if_fail (self && HILDON_IS_PROGRAM (self));
369     
370     priv = HILDON_PROGRAM_GET_PRIVATE (self);
371
372     if (g_slist_find_custom (priv->windows, window,
373            hildon_program_window_list_compare) )
374     {
375         /* We already have that window */
376         return;
377     }
378
379     if (!priv->window_count)
380     {
381         hildon_program_update_top_most (self);
382         
383         /* Now that we have a window we should start keeping track of
384          * the root window */
385         gdk_window_set_events (gdk_get_default_root_window (),
386                           gdk_window_get_events (gdk_get_default_root_window ())                          | GDK_PROPERTY_CHANGE_MASK);
387         gdk_window_add_filter (gdk_get_default_root_window (),
388                 hildon_program_root_window_event_filter, self );
389     }
390     
391     hildon_window_set_can_hibernate_property (window, &priv->killable);
392
393     hildon_window_set_program (window, G_OBJECT (self));
394
395     priv->windows = g_slist_append (priv->windows, window);
396     priv->window_count ++;
397 }
398
399 /**
400  * hildon_program_remove_window:
401  * @self: The #HildonProgram to which the window should be unregistered
402  * @window: The @HildonWindow to unregister
403  *
404  * Used to unregister a window from the program. Subsequent calls to
405  * hildon_program_set_common_menu() and hildon_pogram_set_common_toolbar()
406  * will not affect the window
407  **/
408 void
409 hildon_program_remove_window (HildonProgram *self, HildonWindow *window)
410 {
411     HildonProgramPriv *priv;
412     
413     g_return_if_fail (self && HILDON_IS_PROGRAM (self));
414     
415     priv = HILDON_PROGRAM_GET_PRIVATE (self);
416     
417     hildon_window_unset_program (window);
418
419     priv->windows = g_slist_remove (priv->windows, window);
420
421     priv->window_count --;
422
423     if (!priv->window_count)
424     {
425         gdk_window_remove_filter (gdk_get_default_root_window(),
426                 hildon_program_root_window_event_filter,
427                 self);
428     }
429 }
430
431 /**
432  * hildon_program_set_can_hibernate:
433  * @self: The #HildonProgram which can hibernate or not
434  * @can_hibernate: whether or not the #HildonProgram can hibernate
435  *
436  * Used to set whether or not the Hildon task navigator should
437  * be able to set the program to hibernation in case of low memory
438  **/
439 void
440 hildon_program_set_can_hibernate (HildonProgram *self, gboolean killable)
441 {
442     HildonProgramPriv *priv;
443     
444     g_return_if_fail (self && HILDON_IS_PROGRAM (self));
445     
446     priv = HILDON_PROGRAM_GET_PRIVATE (self);
447
448     if (priv->killable != killable)
449     {
450         g_slist_foreach (priv->windows, 
451                 (GFunc)hildon_window_set_can_hibernate_property, &killable);
452     }
453
454     priv->killable = killable;
455
456 }
457
458 /**
459  * hildon_program_get_can_hibernate:
460  * @self: The #HildonProgram which can hibernate or not
461  * 
462  * Return value: Whether or not this #HildonProgram is set to be
463  * support hibernation from the Hildon task navigator
464  **/
465 gboolean
466 hildon_program_get_can_hibernate (HildonProgram *self)
467 {
468     HildonProgramPriv *priv;
469     
470     g_return_val_if_fail (self && HILDON_IS_PROGRAM (self), FALSE);
471    
472     priv = HILDON_PROGRAM_GET_PRIVATE (self);
473
474     return priv->killable;
475
476 }
477
478 /**
479  * hildon_program_set_common_menu:
480  * @self: The #HildonProgram in which the common menu should be used
481  * @menu: A GtkMenu to use as common menu for the program
482  *
483  * Sets a GtkMenu that will appear in all the @HildonWindow registered
484  * to the #HildonProgram. Only one common GtkMenu can be set, further
485  * call will detach the previous common GtkMenu. A @HildonWindow
486  * can use it's own GtkMenu with @hildon_window_set_menu
487  **/
488 void
489 hildon_program_set_common_menu (HildonProgram *self, GtkMenu *menu)
490 {
491     HildonProgramPriv *priv;
492
493     g_return_if_fail (self && HILDON_IS_PROGRAM (self));
494
495     priv = HILDON_PROGRAM_GET_PRIVATE (self);
496
497     if (priv->common_menu)
498     {
499         if (GTK_WIDGET_VISIBLE (priv->common_menu))
500         {
501             gtk_menu_popdown (GTK_MENU(priv->common_menu));
502             gtk_menu_shell_deactivate (GTK_MENU_SHELL (priv->common_menu));
503         }
504
505         if (gtk_menu_get_attach_widget (GTK_MENU (priv->common_menu)))
506         {
507             gtk_menu_detach (GTK_MENU (priv->common_menu));
508         }
509         else
510         {
511             g_object_unref (priv->common_menu);
512         }
513     }
514
515     priv->common_menu = GTK_WIDGET (menu);
516
517     if (priv->common_menu)
518     {
519         g_object_ref (menu);
520         gtk_object_sink (GTK_OBJECT (menu));
521         gtk_widget_show_all (GTK_WIDGET (menu));
522     }
523 }
524
525 /**
526  * hildon_program_get_common_menu:
527  * @self: The #HildonProgram from which to retrieve the common menu
528  *
529  * Return value: the GtkMenu that was set as common menu for this
530  * #HildonProgram, or NULL of no common menu was set.
531  **/
532 GtkMenu *
533 hildon_program_get_common_menu (HildonProgram *self)
534 {
535     HildonProgramPriv *priv;
536
537     g_return_val_if_fail (self && HILDON_IS_PROGRAM (self), NULL);
538
539     priv = HILDON_PROGRAM_GET_PRIVATE (self);
540
541     return GTK_MENU (priv->common_menu);
542 }
543
544 /**
545  * hildon_program_set_common_toolbar:
546  * @self: The #HildonProgram in which the common toolbar should be used
547  * @toolbar: A GtkToolbar to use as common toolbar for the program
548  *
549  * Sets a GtkToolbar that will appear in all the @HildonWindow registered
550  * to the #HildonProgram. Only one common GtkToolbar can be set, further
551  * call will detach the previous common GtkToolbar. A @HildonWindow
552  * can use its own GtkToolbar with @hildon_window_set_toolbar. Both
553  * #HildonProgram and @HildonWindow specific toolbars will be shown
554  **/
555 void
556 hildon_program_set_common_toolbar (HildonProgram *self, GtkToolbar *toolbar)
557 {
558     HildonProgramPriv *priv;
559
560     g_return_if_fail (self && HILDON_IS_PROGRAM (self));
561
562     priv = HILDON_PROGRAM_GET_PRIVATE (self);
563
564     if (priv->common_toolbar)
565     {
566         if (priv->common_toolbar->parent)
567         {
568             gtk_container_remove (GTK_CONTAINER (priv->common_toolbar->parent), 
569                                   priv->common_toolbar);
570         }
571         
572         g_object_unref (priv->common_toolbar);
573     }
574
575     priv->common_toolbar = GTK_WIDGET (toolbar);
576
577     if (priv->common_toolbar)
578     {
579         g_object_ref (priv->common_toolbar);
580         gtk_object_sink (GTK_OBJECT (priv->common_toolbar) );
581     }
582
583     /* if the program is the topmost we have to update the common
584        toolbar right now for the topmost window */
585     if (priv->is_topmost)
586       {
587         g_slist_foreach (priv->windows, 
588                          (GFunc) hildon_program_common_toolbar_topmost_window, NULL);
589       }
590 }
591
592 /**
593  * hildon_program_get_common_toolbar:
594  * @self: The #HildonProgram from which to retrieve the common toolbar
595  *
596  * Return value: the GtkToolbar that was set as common toolbar for this
597  * #HildonProgram, or NULL of no common menu was set.
598  **/
599 GtkToolbar *
600 hildon_program_get_common_toolbar (HildonProgram *self)
601 {
602     HildonProgramPriv *priv;
603
604     g_return_val_if_fail (self && HILDON_IS_PROGRAM (self), NULL);
605
606     priv = HILDON_PROGRAM_GET_PRIVATE (self);
607
608     return priv->common_toolbar ? GTK_TOOLBAR (priv->common_toolbar) : NULL;
609 }
610
611 /**
612  * hildon_program_get_is_topmost:
613  * @self: A #HildonWindow
614  *
615  * Return value: Whether or not one of the program's window or dialog is 
616  * currenltly activated by the window manager.
617  **/
618 gboolean
619 hildon_program_get_is_topmost (HildonProgram *self)
620 {
621     HildonProgramPriv *priv;
622
623     g_return_val_if_fail (self && HILDON_IS_PROGRAM (self), FALSE);
624     
625     priv = HILDON_PROGRAM_GET_PRIVATE (self);
626
627     return priv->is_topmost;
628 }
629
630