Add HildonAppMenu::changed signal
[hildon] / hildon / hildon-app-menu.c
index 8b6974d..06e8165 100644 (file)
 
 /**
  * SECTION:hildon-app-menu
- * @short_description: Widget representing the application menu in the Hildon framework.
+ * @short_description: Application menu for Hildon applications.
  *
- * The #HildonAppMenu is a GTK widget which represents an application
- * menu in the Hildon framework.
+ * #HildonAppMenu is an application menu for applications in the Hildon
+ * framework.
  *
  * This menu opens from the top of the screen and contains a number of
  * entries (#GtkButton) organized in one or two columns, depending on
  * if the screen is resized). Entries are added left to right and top
  * to bottom.
  *
- * Besides that, the #HildonAppMenu can contain a group of filter buttons
- * (#GtkToggleButton or #GtkRadioButton).
+ * Besides that, #HildonAppMenu can contain a group of filter buttons
+ * (#GtkToggleButton or #GtkRadioButton). Filters are meant to change
+ * the way data is presented in the application, rather than change
+ * the layout of the menu itself. For example, a file manager can have
+ * filters to decide the order used to display a list of files (name,
+ * date, size, etc.).
  *
  * To use a #HildonAppMenu, add it to a #HildonWindow using
  * hildon_window_set_app_menu(). The menu will appear when the user
 #include                                        "hildon-app-menu-private.h"
 #include                                        "hildon-window.h"
 #include                                        "hildon-banner.h"
-
-static GdkWindow *
-grab_transfer_window_get                        (GtkWidget *widget);
+#include                                        "hildon-animation-actor.h"
 
 static void
 hildon_app_menu_repack_items                    (HildonAppMenu *menu,
@@ -131,10 +133,22 @@ remove_item_from_list                           (GList    **list,
                                                  gpointer   item);
 
 static void
+emit_menu_changed                               (HildonAppMenu *menu,
+                                                 gpointer item);
+
+static void
 hildon_app_menu_apply_style                     (GtkWidget *widget);
 
 G_DEFINE_TYPE (HildonAppMenu, hildon_app_menu, GTK_TYPE_WINDOW);
 
+enum
+{
+    CHANGED,
+    LAST_SIGNAL
+};
+
+static guint app_menu_signals[LAST_SIGNAL] = { 0 };
+
 /**
  * hildon_app_menu_new:
  *
@@ -192,6 +206,9 @@ hildon_app_menu_insert                          (HildonAppMenu *menu,
 
     /* Remove item from list when it is destroyed */
     g_object_weak_ref (G_OBJECT (item), (GWeakNotify) remove_item_from_list, &(priv->buttons));
+    g_object_weak_ref (G_OBJECT (item), (GWeakNotify) emit_menu_changed, menu);
+
+    g_signal_emit (menu, app_menu_signals[CHANGED], 0);
 }
 
 /**
@@ -299,6 +316,9 @@ hildon_app_menu_add_filter                      (HildonAppMenu *menu,
 
     /* Remove filter from list when it is destroyed */
     g_object_weak_ref (G_OBJECT (filter), (GWeakNotify) remove_item_from_list, &(priv->filters));
+    g_object_weak_ref (G_OBJECT (filter), (GWeakNotify) emit_menu_changed, menu);
+
+    g_signal_emit (menu, app_menu_signals[CHANGED], 0);
 }
 
 static void
@@ -426,6 +446,13 @@ remove_item_from_list                           (GList    **list,
 }
 
 static void
+emit_menu_changed                               (HildonAppMenu *menu,
+                                                 gpointer item)
+{
+    g_signal_emit (menu, app_menu_signals[CHANGED], 0);
+}
+
+static void
 hildon_app_menu_show_all                        (GtkWidget *widget)
 {
     HildonAppMenu *menu = HILDON_APP_MENU (widget);
@@ -492,7 +519,7 @@ hildon_app_menu_find_intruder                   (gpointer data)
                      * Yes, this is a hack. See NB#111027 */
                     if (HILDON_IS_BANNER (i->data)) {
                         gtk_widget_hide (i->data);
-                    } else {
+                    } else if (!HILDON_IS_ANIMATION_ACTOR (i->data)) {
                         intruder_found = TRUE;
                     }
                 }
@@ -517,59 +544,8 @@ hildon_app_menu_map                             (GtkWidget *widget)
 
     GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->map (widget);
 
-    /* Grab pointer and keyboard */
-    if (priv->transfer_window == NULL) {
-        gboolean has_grab = FALSE;
-
-        priv->transfer_window = grab_transfer_window_get (widget);
-
-        if (gdk_pointer_grab (priv->transfer_window, TRUE,
-                              GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
-                              GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
-                              GDK_POINTER_MOTION_MASK, NULL, NULL,
-                              GDK_CURRENT_TIME) == GDK_GRAB_SUCCESS) {
-            if (gdk_keyboard_grab (priv->transfer_window, TRUE,
-                                   GDK_CURRENT_TIME) == GDK_GRAB_SUCCESS) {
-                has_grab = TRUE;
-            } else {
-                gdk_display_pointer_ungrab (gtk_widget_get_display (widget),
-                                            GDK_CURRENT_TIME);
-            }
-        }
-
-        if (has_grab) {
-            gtk_grab_add (widget);
-        } else {
-            gdk_window_destroy (priv->transfer_window);
-            priv->transfer_window = NULL;
-        }
-    }
-
-    /* Make the menu temporary when it's mapped, so it's closed if a
-     * new window appears */
-    gtk_window_set_is_temporary (GTK_WINDOW (widget), TRUE);
-
-    priv->find_intruder_idle_id = gdk_threads_add_idle (hildon_app_menu_find_intruder, widget);
-}
-
-static void
-hildon_app_menu_unmap                           (GtkWidget *widget)
-{
-    HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(widget);
-
-    /* Remove the grab */
-    if (priv->transfer_window != NULL) {
-        gdk_display_pointer_ungrab (gtk_widget_get_display (widget),
-                                    GDK_CURRENT_TIME);
-        gtk_grab_remove (widget);
-
-        gdk_window_destroy (priv->transfer_window);
-        priv->transfer_window = NULL;
-    }
-
-    GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->unmap (widget);
-
-    gtk_window_set_is_temporary (GTK_WINDOW (widget), FALSE);
+    if (priv->find_intruder_idle_id == 0)
+        priv->find_intruder_idle_id = gdk_threads_add_idle (hildon_app_menu_find_intruder, widget);
 }
 
 static void
@@ -651,56 +627,6 @@ hildon_app_menu_key_press                       (GtkWidget   *widget,
 }
 
 static gboolean
-hildon_app_menu_button_press                    (GtkWidget *widget,
-                                                 GdkEventButton *event)
-{
-    int x, y;
-    HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(widget);
-
-    gdk_window_get_position (widget->window, &x, &y);
-
-    /* Whether the button has been pressed outside the widget */
-    priv->pressed_outside = (event->x_root < x || event->x_root > x + widget->allocation.width ||
-                             event->y_root < y || event->y_root > y + widget->allocation.height);
-
-    if (GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->button_press_event) {
-        return GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->button_press_event (widget, event);
-    } else {
-        return FALSE;
-    }
-}
-
-static gboolean
-hildon_app_menu_button_release                  (GtkWidget *widget,
-                                                 GdkEventButton *event)
-{
-    HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(widget);
-
-    if (priv->pressed_outside) {
-        int x, y;
-        gboolean released_outside;
-
-        gdk_window_get_position (widget->window, &x, &y);
-
-        /* Whether the button has been released outside the widget */
-        released_outside = (event->x_root < x || event->x_root > x + widget->allocation.width ||
-                            event->y_root < y || event->y_root > y + widget->allocation.height);
-
-        if (released_outside) {
-            gtk_widget_hide (widget);
-        }
-
-        priv->pressed_outside = FALSE; /* Always reset pressed_outside to FALSE */
-    }
-
-    if (GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->button_release_event) {
-        return GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->button_release_event (widget, event);
-    } else {
-        return FALSE;
-    }
-}
-
-static gboolean
 hildon_app_menu_delete_event_handler            (GtkWidget   *widget,
                                                  GdkEventAny *event)
 {
@@ -709,34 +635,6 @@ hildon_app_menu_delete_event_handler            (GtkWidget   *widget,
     return TRUE;
 }
 
-/* Grab transfer window (based on the one from GtkMenu) */
-static GdkWindow *
-grab_transfer_window_get                        (GtkWidget *widget)
-{
-    GdkWindow *window;
-    GdkWindowAttr attributes;
-    gint attributes_mask;
-
-    attributes.x = 0;
-    attributes.y = 0;
-    attributes.width = 10;
-    attributes.height = 10;
-    attributes.window_type = GDK_WINDOW_TEMP;
-    attributes.wclass = GDK_INPUT_ONLY;
-    attributes.override_redirect = TRUE;
-    attributes.event_mask = 0;
-
-    attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_NOREDIR;
-
-    window = gdk_window_new (gtk_widget_get_root_window (widget),
-                                 &attributes, attributes_mask);
-    gdk_window_set_user_data (window, widget);
-
-    gdk_window_show (window);
-
-    return window;
-}
-
 static void
 hildon_app_menu_size_request                    (GtkWidget      *widget,
                                                  GtkRequisition *requisition)
@@ -790,6 +688,7 @@ static void
 hildon_app_menu_apply_style                     (GtkWidget *widget)
 {
     GdkScreen *screen;
+    gint filter_group_width;
     guint horizontal_spacing, vertical_spacing, filter_vertical_spacing;
     guint inner_border, external_border;
     HildonAppMenuPrivate *priv;
@@ -799,6 +698,7 @@ hildon_app_menu_apply_style                     (GtkWidget *widget)
     gtk_widget_style_get (widget,
                           "horizontal-spacing", &horizontal_spacing,
                           "vertical-spacing", &vertical_spacing,
+                          "filter-group-width", &filter_group_width,
                           "filter-vertical-spacing", &filter_vertical_spacing,
                           "inner-border", &inner_border,
                           "external-border", &external_border,
@@ -812,13 +712,20 @@ hildon_app_menu_apply_style                     (GtkWidget *widget)
     /* Set inner border */
     gtk_container_set_border_width (GTK_CONTAINER (widget), inner_border);
 
+    /* Set width of the group of filter buttons */
+    gtk_widget_set_size_request (GTK_WIDGET (priv->filters_hbox), filter_group_width, -1);
+
     /* Compute width request */
     screen = gtk_widget_get_screen (widget);
     if (gdk_screen_get_width (screen) < gdk_screen_get_height (screen)) {
         external_border = 0;
     }
     priv->width_request = gdk_screen_get_width (screen) - external_border * 2;
-    gtk_window_move (GTK_WINDOW (widget), external_border, 0);
+
+    if (widget->window)
+      gdk_window_move_resize (widget->window,
+                              external_border, 0, 1, 1);
+
     gtk_widget_queue_resize (widget);
 }
 
@@ -916,6 +823,39 @@ hildon_app_menu_repack_items                    (HildonAppMenu *menu,
             }
         }
     }
+
+    gtk_widget_queue_draw (GTK_WIDGET (menu));
+}
+
+/**
+ * hildon_app_menu_has_visible_children:
+ * @menu: a #HildonAppMenu
+ *
+ * Returns whether this menu has any visible items
+ * and/or filters. If this is %FALSE, the menu will
+ * not be popped up.
+ *
+ * Returns: whether there are visible items or filters
+ *
+ * Since: 2.2
+ **/
+gboolean
+hildon_app_menu_has_visible_children (HildonAppMenu *menu)
+{
+   HildonAppMenuPrivate *priv;
+   GList *i;
+   gboolean show_menu = FALSE;
+
+    priv = HILDON_APP_MENU_GET_PRIVATE (menu);
+
+    /* Don't show menu if it doesn't contain visible items */
+    for (i = priv->buttons; i && !show_menu; i = i->next)
+        show_menu = GTK_WIDGET_VISIBLE (i->data);
+
+    for (i = priv->filters; i && !show_menu; i = i->next)
+           show_menu = GTK_WIDGET_VISIBLE (i->data);
+
+    return show_menu;
 }
 
 /**
@@ -932,23 +872,10 @@ void
 hildon_app_menu_popup                           (HildonAppMenu *menu,
                                                  GtkWindow     *parent_window)
 {
-    HildonAppMenuPrivate *priv;
-    gboolean show_menu = FALSE;
-    GList *i;
-
     g_return_if_fail (HILDON_IS_APP_MENU (menu));
     g_return_if_fail (GTK_IS_WINDOW (parent_window));
 
-    priv = HILDON_APP_MENU_GET_PRIVATE (menu);
-
-    /* Don't show menu if it doesn't contain visible items */
-    for (i = priv->buttons; i && !show_menu; i = i->next)
-        show_menu = GTK_WIDGET_VISIBLE (i->data);
-
-    for (i = priv->filters; i && !show_menu; i = i->next)
-        show_menu = GTK_WIDGET_VISIBLE (i->data);
-
-    if (show_menu) {
+    if (hildon_app_menu_has_visible_children (menu)) {
         hildon_app_menu_set_parent_window (menu, parent_window);
         gtk_widget_show (GTK_WIDGET (menu));
     }
@@ -1011,6 +938,7 @@ hildon_app_menu_init                            (HildonAppMenu *menu)
     priv->transfer_window = NULL;
     priv->pressed_outside = FALSE;
     priv->inhibit_repack = FALSE;
+    priv->last_pressed_button = NULL;
     priv->buttons = NULL;
     priv->filters = NULL;
     priv->columns = 2;
@@ -1082,13 +1010,10 @@ hildon_app_menu_class_init                      (HildonAppMenuClass *klass)
     widget_class->show_all = hildon_app_menu_show_all;
     widget_class->hide_all = hildon_app_menu_hide_all;
     widget_class->map = hildon_app_menu_map;
-    widget_class->unmap = hildon_app_menu_unmap;
     widget_class->realize = hildon_app_menu_realize;
     widget_class->unrealize = hildon_app_menu_unrealize;
     widget_class->grab_notify = hildon_app_menu_grab_notify;
     widget_class->key_press_event = hildon_app_menu_key_press;
-    widget_class->button_press_event = hildon_app_menu_button_press;
-    widget_class->button_release_event = hildon_app_menu_button_release;
     widget_class->style_set = hildon_app_menu_style_set;
     widget_class->delete_event = hildon_app_menu_delete_event_handler;
     widget_class->size_request = hildon_app_menu_size_request;
@@ -1115,6 +1040,16 @@ hildon_app_menu_class_init                      (HildonAppMenuClass *klass)
 
     gtk_widget_class_install_style_property (
         widget_class,
+        g_param_spec_int (
+            "filter-group-width",
+            "Width of the group of filter buttons",
+            "Total width of the group of filter buttons, "
+            "or -1 to use the natural size request.",
+            -1, G_MAXINT, 444,
+            G_PARAM_READABLE));
+
+    gtk_widget_class_install_style_property (
+        widget_class,
         g_param_spec_uint (
             "filter-vertical-spacing",
             "Vertical spacing between filters and menu items",
@@ -1140,4 +1075,22 @@ hildon_app_menu_class_init                      (HildonAppMenuClass *klass)
             "the screen edges (in horizontal mode)",
             0, G_MAXUINT, 50,
             G_PARAM_READABLE));
+
+
+    /**
+     * HildonAppMenu::changed:
+     * @widget: the widget that received the signal
+     *
+     * The HildonAppMenu::changed signal is emitted whenever an
+     * item or filter is added or removed from the menu.
+     *
+     * Since: 2.2
+     */
+    app_menu_signals[CHANGED] =
+        g_signal_new ("changed",
+                      G_TYPE_FROM_CLASS (klass),
+                      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                      0,
+                      NULL, NULL,
+                      g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, NULL);
 }