thread safety
[hildon] / hildon-widgets / hildon-window.c
index 960e3ac..7ea7ff2 100644 (file)
@@ -45,6 +45,7 @@
 #include <gtk/gtkentry.h>
 #include <gtk/gtktextview.h>
 #include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtkmain.h>
 #include <gdk/gdkkeysyms.h>
 #include <gdk/gdk.h>
 
@@ -134,6 +135,9 @@ static gboolean
 hildon_window_key_press_event (GtkWidget         *widget,
                                GdkEventKey       *event);
 static gboolean
+hildon_window_key_release_event (GtkWidget       *widget, 
+                                 GdkEventKey     *event);
+static gboolean
 hildon_window_window_state_event (GtkWidget *widget, 
                                   GdkEventWindowState *event,
                                   gpointer null);
@@ -147,13 +151,24 @@ hildon_window_is_topmost_notify (GObject *self,
                                  GParamSpec *property_spec,
                                  gpointer null);
 
-static void
+static gboolean 
+hildon_window_vbox_expose_event (GtkWidget *vbox,
+                                 GdkEventExpose *event,
+                                 gpointer window);
+
+static gboolean
 hildon_window_toggle_menu (HildonWindow * self);
 
-static void get_client_area(GtkWidget * widget,
-                            GtkAllocation * allocation);
-static GdkFilterReturn hildon_window_event_filter( GdkXEvent *xevent, GdkEvent *event, gpointer data );
-static GdkFilterReturn hildon_window_root_window_event_filter( GdkXEvent *xevent, GdkEvent *event, gpointer data );
+static gboolean
+hildon_window_escape_timeout (gpointer data);
+
+static GdkFilterReturn
+hildon_window_event_filter (GdkXEvent *xevent, GdkEvent *event, gpointer data );
+
+static GdkFilterReturn
+hildon_window_root_window_event_filter (GdkXEvent *xevent, 
+                                        GdkEvent *event, 
+                                        gpointer data );
 
 static void
 hildon_window_get_borders (HildonWindow *window);
@@ -192,12 +207,11 @@ struct _HildonWindowPrivate
 
     GtkAllocation allocation;
 
-    guint fullscreen : 1;
-    guint is_topmost: 1;
-    /* For future expansion. We might use the below variables
-     * for disabling keyrepeat
-     * if we need it someday. */
+    guint fullscreen;
+    guint is_topmost;
+    guint escape_timeout;
     gint visible_toolbars;
+    gint previous_vbox_y;
 
     HildonProgram *program;
 };
@@ -250,6 +264,7 @@ hildon_window_class_init (HildonWindowClass * window_class)
     widget_class->realize = hildon_window_realize;
     widget_class->unrealize = hildon_window_unrealize;
     widget_class->key_press_event = hildon_window_key_press_event;
+    widget_class->key_release_event = hildon_window_key_release_event;
     
     /* now the object stuff */
     object_class->finalize = hildon_window_finalize;
@@ -300,6 +315,7 @@ hildon_window_init (HildonWindow * self)
     priv->is_topmost = FALSE;
     priv->borders = NULL;
     priv->toolbar_borders = NULL;
+    priv->escape_timeout = 0;
 
     priv->fullscreen = FALSE;
    
@@ -307,6 +323,11 @@ hildon_window_init (HildonWindow * self)
     gtk_widget_set_events (GTK_WIDGET(self), GDK_BUTTON_PRESS_MASK |
              GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK);
 
+    /* Handle the drawing of the vbox (add the pixmap borders) */
+    g_signal_connect (G_OBJECT (self->priv->vbox), "expose-event",
+                      G_CALLBACK (hildon_window_vbox_expose_event),
+                      self);
+
     /* Used to keep track of fullscreen / unfullscreen */
     g_signal_connect (G_OBJECT (self), "window_state_event",
             G_CALLBACK (hildon_window_window_state_event), self);
@@ -331,18 +352,11 @@ hildon_window_finalize (GObject * obj_self)
     HildonWindow *self;
     g_return_if_fail (HILDON_WINDOW (obj_self));
     self = HILDON_WINDOW (obj_self);
-
-    if (self->priv->program)
-    {
-        hildon_program_remove_window (self->priv->program, self);
-    }
     
-    gdk_window_remove_filter (gdk_get_default_root_window(), 
-                              hildon_window_root_window_event_filter,
-                              obj_self);
 
     if (G_OBJECT_CLASS (parent_class)->finalize)
         G_OBJECT_CLASS (parent_class)->finalize (obj_self);
+
 }
 
 static void
@@ -378,10 +392,6 @@ hildon_window_realize (GtkWidget *widget)
 
     XFree(old_atoms);
     g_free(new_atoms);
-
-   gdk_window_set_events (widget->window, 
-           gdk_window_get_events (widget->window) | GDK_SUBSTRUCTURE_MASK);
-
        
    /* rely on GDK to set the window group to its default */
    gdk_window_set_group (widget->window, NULL);
@@ -399,6 +409,9 @@ hildon_window_realize (GtkWidget *widget)
    active_window = hildon_window_get_active_window();
    hildon_window_update_topmost (HILDON_WINDOW (widget), active_window);
 
+   /* Update the window title */
+   hildon_window_update_title(HILDON_WINDOW (widget));
+
 }
 
 static void
@@ -468,10 +481,10 @@ hildon_window_get_borders (HildonWindow *window)
 }
 
 static void
-shown_children(gpointer data, gpointer user_data)
+visible_toolbars (gpointer data, gpointer user_data)
 {
     if (GTK_WIDGET_VISIBLE (GTK_WIDGET (((GtkBoxChild *)data)->widget)))
-        *((gboolean *)user_data) = TRUE;
+        (*((gint *)user_data)) ++;
 }
 
 static gboolean
@@ -483,8 +496,7 @@ hildon_window_expose (GtkWidget * widget, GdkEventExpose * event)
     GtkBorder *b = HILDON_WINDOW(widget)->priv->borders;
     GtkBorder *tb = HILDON_WINDOW(widget)->priv->toolbar_borders;
     gint tb_height = 0;
-    gint height_decrement = 0;
-    gboolean draw_toolbar = FALSE;
+    gint currently_visible_toolbars = 0;
 
     if (!priv->borders)
     {
@@ -493,25 +505,55 @@ hildon_window_expose (GtkWidget * widget, GdkEventExpose * event)
 
     tb_height = bx->allocation.height + tb->top + tb->bottom;
 
-    g_list_foreach (box->children, shown_children, &draw_toolbar);
+    g_list_foreach (box->children, visible_toolbars, 
+            &currently_visible_toolbars);
 
-    if (HILDON_WINDOW (widget)->priv->fullscreen)
+    if (priv->previous_vbox_y != priv->vbox->allocation.y)
     {
-        if(draw_toolbar)
-        {
-            paint_toolbar (widget, box, event, TRUE);
-        }
+        gint draw_from_y = priv->previous_vbox_y < priv->vbox->allocation.y?
+            priv->previous_vbox_y - tb->top:
+            priv->vbox->allocation.y - tb->top;
+        
+        gtk_widget_queue_draw_area (widget, 0, draw_from_y, 
+                widget->allocation.width,
+                widget->allocation.height - draw_from_y);
+        
+        priv->previous_vbox_y = priv->vbox->allocation.y;
     }
-    else
+
+    if (!HILDON_WINDOW (widget)->priv->fullscreen)
     {
-        if (draw_toolbar)
+
+        /* Draw the left and right window border */
+        gint side_borders_height = widget->allocation.height - b->top;
+
+        if (currently_visible_toolbars)
+            side_borders_height -= tb_height;
+        else
+            side_borders_height -= b->bottom;
+        
+        if (b->left > 0)
         {
-            paint_toolbar (widget, box, event, FALSE);
-            height_decrement = tb_height;
+            gtk_paint_box (widget->style, widget->window,
+                    GTK_WIDGET_STATE(widget), GTK_SHADOW_OUT,
+                    &event->area, widget, "left-border",
+                    widget->allocation.x, widget->allocation.y +
+                    b->top, b->left, side_borders_height);
         } 
-        else if (b->bottom > 0)
+
+        if (b->right > 0)
+        {
+            gtk_paint_box (widget->style, widget->window,
+                    GTK_WIDGET_STATE(widget), GTK_SHADOW_OUT,
+                    &event->area, widget, "right-border",
+                    widget->allocation.x + widget->allocation.width -
+                    b->right, widget->allocation.y + b->top,
+                    b->right, side_borders_height);
+        }
+
+        /* If no toolbar, draw the bottom window border */
+        if (!currently_visible_toolbars &&b->bottom > 0)
         {
-            height_decrement = b->bottom;
             gtk_paint_box (widget->style, widget->window,
                     GTK_WIDGET_STATE(widget), GTK_SHADOW_OUT,
                     &event->area, widget, "bottom-border",
@@ -520,34 +562,17 @@ hildon_window_expose (GtkWidget * widget, GdkEventExpose * event)
                     widget->allocation.width, b->bottom);
         }
 
+        /* Draw the top border */
         if (b->top > 0)
         {
-            height_decrement += b->top;
             gtk_paint_box (widget->style, widget->window,
                     GTK_WIDGET_STATE(widget), GTK_SHADOW_OUT,
                     &event->area, widget, "top-border",
                     widget->allocation.x, widget->allocation.y,
                     widget->allocation.width, b->top);
         } 
-        if (b->left > 0)
-        {
-            gtk_paint_box (widget->style, widget->window,
-                    GTK_WIDGET_STATE(widget), GTK_SHADOW_OUT,
-                    &event->area, widget, "left-border",
-                    widget->allocation.x, widget->allocation.y +
-                    b->top, b->left, widget->allocation.height -
-                    height_decrement);
-        } 
-        if (b->right > 0)
-        {
-            gtk_paint_box (widget->style, widget->window,
-                    GTK_WIDGET_STATE(widget), GTK_SHADOW_OUT,
-                    &event->area, widget, "right-border",
-                    widget->allocation.x + widget->allocation.width -
-                    b->right, widget->allocation.y + b->top,
-                    b->right, widget->allocation.height -
-                    height_decrement);
-        }
+
+
     }
 
     /* don't draw the window stuff as it overwrites our borders with a blank
@@ -640,12 +665,14 @@ hildon_window_size_allocate (GtkWidget * widget, GtkAllocation * allocation)
             alloc.y += b->top;
 
             alloc.height -= b->top;
-            if (box_alloc.height <= 0)
-                alloc.height -= b->bottom;
-            else
-                alloc.height -= (tb->top + tb->bottom);
         }
 
+        if (box_alloc.height <= 0 && 
+                !(HILDON_WINDOW (widget)->priv->fullscreen))
+            alloc.height -= b->bottom;
+        else
+            alloc.height -= (tb->top + tb->bottom);
+
         gtk_widget_size_allocate (bin->child, &alloc);
     }
 
@@ -701,11 +728,11 @@ hildon_window_destroy (GtkObject *obj)
         
     }
 
-    menu_list = gtk_menu_get_for_attach_widget (GTK_WIDGET (obj));
+    menu_list = g_list_copy (gtk_menu_get_for_attach_widget (GTK_WIDGET (obj)));
 
     while (menu_list)
     {
-        if (menu_list->data)
+        if (GTK_IS_MENU(menu_list->data))
         {
             if (GTK_WIDGET_VISIBLE (GTK_WIDGET (menu_list->data)))
             {
@@ -717,6 +744,19 @@ hildon_window_destroy (GtkObject *obj)
         menu_list = menu_list->next;
     }
 
+    g_list_free (menu_list);
+    
+    if (self->priv->program)
+    {
+        hildon_program_remove_window (self->priv->program, self);
+    }
+    
+    gdk_window_remove_filter (gdk_get_default_root_window(), 
+                              hildon_window_root_window_event_filter,
+                              obj);
+    
+    gtk_widget_set_events (GTK_WIDGET(obj), 0);
+
     GTK_OBJECT_CLASS (parent_class)->destroy (obj);
 }
 
@@ -726,11 +766,51 @@ hildon_window_is_topmost_notify (GObject *self,
                                  gpointer null)
 {
     HildonWindow *window = HILDON_WINDOW (self);
-    
+
     if (window->priv->is_topmost)
     {
         hildon_window_take_common_toolbar (window);
     }
+
+    else
+    {
+        /* If the window lost focus while the user started to press
+         * the ESC key, we won't get the release event. We need to
+         * stop the timeout*/
+        if (window->priv->escape_timeout)
+        {
+            g_source_remove (window->priv->escape_timeout);
+            window->priv->escape_timeout = 0;
+        }
+    }
+}
+
+
+static gboolean 
+hildon_window_vbox_expose_event (GtkWidget *vbox,
+                                 GdkEventExpose *event,
+                                 gpointer window)
+{
+    HildonWindowPrivate *priv = HILDON_WINDOW (window)->priv;
+
+    hildon_window_get_borders (HILDON_WINDOW(window));
+
+    event->area.x -= priv->toolbar_borders->left;
+    event->area.y -= priv->toolbar_borders->top;
+    event->area.width += (priv->toolbar_borders->left + 
+                          priv->toolbar_borders->right);
+    event->area.height += (priv->toolbar_borders->top + 
+                           priv->toolbar_borders->bottom);
+
+    paint_toolbar (GTK_WIDGET (window), GTK_BOX (vbox), event, priv->fullscreen);
+    
+    GTK_WIDGET_CLASS (G_TYPE_INSTANCE_GET_CLASS (vbox, GTK_TYPE_VBOX, GtkVBox))
+            ->expose_event (vbox, event);
+    
+    event->area = GTK_WIDGET(window)->allocation;
+
+
+    return TRUE;
 }
 
 /* Utilities */
@@ -1011,12 +1091,44 @@ hildon_window_key_press_event (GtkWidget *widget, GdkEventKey *event)
     switch (event->keyval)
     {
         case HILDON_HARDKEY_MENU:
-            hildon_window_toggle_menu (HILDON_WINDOW (widget));
-            return TRUE;
+            if (hildon_window_toggle_menu (HILDON_WINDOW (widget)))
+                return TRUE;
+            break;
+        case HILDON_HARDKEY_ESC:
+            if (!priv->escape_timeout)
+            {
+                priv->escape_timeout = g_timeout_add 
+                    (HILDON_WINDOW_LONG_PRESS_TIME,
+                     hildon_window_escape_timeout, widget);
+            }
             break;
     }
 
-    return FALSE;
+    return GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event);
+
+}
+
+static gboolean
+hildon_window_key_release_event (GtkWidget *widget, GdkEventKey *event)
+{
+    HildonWindowPrivate *priv;
+
+    g_return_val_if_fail (HILDON_IS_WINDOW (widget),FALSE);
+
+    priv = HILDON_WINDOW (widget)->priv;
+
+    switch (event->keyval)
+    {
+        case HILDON_HARDKEY_ESC:
+            if (priv->escape_timeout)
+            {
+                g_source_remove (priv->escape_timeout);
+                priv->escape_timeout = 0;
+            }
+            break;
+    }
+
+    return GTK_WIDGET_CLASS (parent_class)->key_release_event (widget, event);
 
 }
 
@@ -1053,37 +1165,25 @@ hildon_window_title_notify (GObject *gobject,
 /*     General     */
 /*******************/
 
-/* TODO: Clean those ... */
-
-/*
- * queries a window for the root window coordinates and size of its
- * client area (i.e. minus the title borders etc.
- */
-static void
-get_client_area (GtkWidget * widget, GtkAllocation * allocation)
-{
-    GdkWindow *window = widget->window;
-    
-    if (window)
-        gdk_window_get_origin (window, &allocation->x, &allocation->y);
-    else
-        memset (allocation, 0, sizeof(GtkAllocation));
-}
-
 /*The menu popuping needs a menu popup-function*/
 static void
 hildon_window_menupopupfunc (GtkMenu *menu, gint *x, gint *y,
                              gboolean *push_in, GtkWidget *widget)
 {
-    GtkAllocation client_area = { 0, 0, 0, 0 };
-
-    get_client_area (GTK_WIDGET (widget), &client_area);
+    gint window_x = 0;
+    gint window_y = 0;
+    GdkWindow *window = GTK_WIDGET(widget)->window;
+    
+    if (window)
+    {
+        gdk_window_get_origin (window, &window_x, &window_y);
+    }
 
     gtk_widget_style_get (GTK_WIDGET (menu), "horizontal-offset", x,
             "vertical-offset", y, NULL);
 
-    *x += client_area.x;
-    *y += client_area.y;
+    *x += window_x;
+    *y += window_y;
   
 }
 
@@ -1148,6 +1248,8 @@ hildon_window_unset_program (HildonWindow *self)
         gdk_window_add_filter (gdk_get_default_root_window (),
                 hildon_window_root_window_event_filter, self );
     }
+
+    self->priv->program = NULL;
 }
 
 /*
@@ -1211,10 +1313,12 @@ hildon_window_take_common_toolbar (HildonWindow *self)
             }
 
             gtk_box_pack_end (GTK_BOX(self->priv->vbox), common_toolbar,
-                    TRUE, TRUE, 7);
+                    TRUE, TRUE, 0);
             g_object_unref (common_toolbar);
 
-            gtk_widget_show_all  (self->priv->vbox);
+            gtk_widget_set_size_request (common_toolbar, -1, TOOLBAR_HEIGHT);
+
+            gtk_widget_show  (self->priv->vbox);
 
         }
     }
@@ -1266,6 +1370,11 @@ hildon_window_update_title (HildonWindow *window)
     const gchar * application_name;
     g_return_if_fail (window && HILDON_IS_WINDOW (window));
 
+    if (!GTK_WIDGET_REALIZED (window))
+    {
+        return;
+    }
+
     application_name = g_get_application_name ();
 
     if (application_name && application_name[0])
@@ -1287,8 +1396,10 @@ detach_menu_func (GtkWidget *attach_widget, GtkMenu *menu)
 }
 /*
  * Toggles the display of the HildonWindow menu.
+ * Returns whether or not something was done (whether or not we had a menu
+ * to toggle)
  */
-static void
+static gboolean
 hildon_window_toggle_menu (HildonWindow * self)
 {
     GtkMenu *menu_to_use = NULL;
@@ -1321,7 +1432,7 @@ hildon_window_toggle_menu (HildonWindow * self)
 
     if (!menu_to_use)
     {
-        return;
+        return FALSE;
     }
     
 
@@ -1329,28 +1440,63 @@ hildon_window_toggle_menu (HildonWindow * self)
     {
         gtk_menu_popdown (menu_to_use);
         gtk_menu_shell_deactivate (GTK_MENU_SHELL (menu_to_use));
-        return;
+        return TRUE;
     }
 
     if (gtk_container_get_children (GTK_CONTAINER (menu_to_use)) != NULL)
     {
+        /* Apply right theming */
+        gtk_widget_set_name (GTK_WIDGET (menu_to_use),
+                "menu_force_with_corners");
+        
         if (self->priv->fullscreen) 
         {
             gtk_menu_popup (menu_to_use, NULL, NULL,
                            (GtkMenuPositionFunc)
                            hildon_window_menupopupfuncfull,
-                           self, 0, 0);
+                           self, 0, 
+                           gtk_get_current_event_time ());
         }
         else
         {
             gtk_menu_popup (menu_to_use, NULL, NULL,
                            (GtkMenuPositionFunc)
                            hildon_window_menupopupfunc,
-                           self, 0, 0);
+                           self, 0, 
+                           gtk_get_current_event_time ());
         }
         gtk_menu_shell_select_first (GTK_MENU_SHELL (menu_to_use), TRUE);
     }
 
+    return TRUE;
+}
+
+/*
+ * If the ESC key was not released when the timeout expires,
+ * close the window
+ */
+static gboolean
+hildon_window_escape_timeout (gpointer data)
+{
+    HildonWindowPrivate *priv;
+    GdkEvent *event;
+
+    GDK_THREADS_ENTER ();
+    
+    priv = HILDON_WINDOW(data)->priv;
+
+    /* Send fake event, simulation a situation that user
+       pressed 'x' from the corner */
+    event = gdk_event_new(GDK_DELETE);
+    ((GdkEventAny *)event)->window = GTK_WIDGET(data)->window;
+    gtk_main_do_event(event);
+    gdk_event_free(event);
+
+    priv->escape_timeout = 0;
+
+    GDK_THREADS_LEAVE ();
+    
+    return FALSE;
 }
 
 
@@ -1428,7 +1574,9 @@ hildon_window_add_toolbar (HildonWindow *self, GtkToolbar *toolbar)
 
     vbox = GTK_BOX (self->priv->vbox);
 
-    gtk_box_pack_start (vbox, GTK_WIDGET(toolbar), TRUE, TRUE, 7);
+    gtk_box_pack_start (vbox, GTK_WIDGET(toolbar), TRUE, TRUE, 0);
+    gtk_box_reorder_child (vbox, GTK_WIDGET(toolbar), 0);
+    gtk_widget_set_size_request (GTK_WIDGET (toolbar), -1, TOOLBAR_HEIGHT);
 
     gtk_widget_queue_resize (GTK_WIDGET(self));
 }
@@ -1448,6 +1596,11 @@ hildon_window_remove_toolbar (HildonWindow *self, GtkToolbar *toolbar)
     g_return_if_fail (self && HILDON_IS_WINDOW (self));
 
     gtk_container_remove (vbox, GTK_WIDGET(toolbar));
+    /* FIXME: As the toolbar border graphics go beyond the VBox, we
+     * need to trigger a manual redraw */
+    gtk_widget_queue_draw_area (GTK_WIDGET (self) , 0, 0, 
+                GTK_WIDGET(self)->allocation.width,
+                GTK_WIDGET(self)->allocation.height);
 }
 
 /**
@@ -1489,7 +1642,7 @@ hildon_window_set_menu (HildonWindow *self, GtkMenu *menu)
 
     self->priv->menu = GTK_WIDGET (menu);
     gtk_widget_set_name (GTK_WIDGET (self->priv->menu),
-            "menu_force_with_corners");
+                         "menu_force_with_corners");
     gtk_widget_show_all (self->priv->menu);
 
     gtk_menu_attach_to_widget (menu, GTK_WIDGET (self), &detach_menu_func);
@@ -1508,3 +1661,4 @@ hildon_window_get_is_topmost(HildonWindow *self){
     
     return self->priv->is_topmost;
 }
+