2008-04-15 13:21:13 <timj@imendio.com>
[hildon] / src / hildon-banner.c
index 81db2e1..b2fa6c3 100644 (file)
@@ -1,14 +1,14 @@
 /*
  * This file is a part of hildon
  *
- * Copyright (C) 2005, 2006 Nokia Corporation, all rights reserved.
+ * Copyright (C) 2005, 2006, 2007 Nokia Corporation, all rights reserved.
  *
  * Contact: Michael Dominic Kostrzewa <michael.kostrzewa@nokia.com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public License
  * as published by the Free Software Foundation; version 2.1 of
- * the License.
+ * the License, or (at your option) any later version.
  *
  * This library is distributed in the hope that it will be useful, but
  * WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -130,9 +130,18 @@ hildon_banner_constructor                       (GType type,
                                                  guint n_construct_params,
                                                  GObjectConstructParam *construct_params);
 
+static void
+hildon_banner_finalize                          (GObject *object);
+
+static gboolean
+hildon_banner_button_press_event                (GtkWidget* widget,
+                                                GdkEventButton* event);
+
 static gboolean 
 hildon_banner_map_event                         (GtkWidget *widget, 
                                                  GdkEventAny *event);
+static void
+hildon_banner_reset_wrap_state                  (HildonBanner *banner);
 
 static void 
 force_to_wrap_truncated                         (HildonBanner *banner);
@@ -161,38 +170,7 @@ static HildonBanner*
 hildon_banner_get_instance_for_widget           (GtkWidget *widget, 
                                                  gboolean timed);
 
-static GtkWindowClass*                          parent_class = NULL;
-
-/**
- * hildon_banner_get_type:
- *
- * Initializes and returns the type of a hildon banner.
- *
- * @Returns: GType of #HildonBanner
- */
-GType G_GNUC_CONST 
-hildon_banner_get_type                          (void)
-{
-    static GType banner_type = 0;
-
-    if (! banner_type)
-    {
-        static const GTypeInfo banner_info = {
-            sizeof (HildonBannerClass),
-            NULL,       /* base_init */
-            NULL,       /* base_finalize */
-            (GClassInitFunc) hildon_banner_class_init,
-            NULL,       /* class_finalize */
-            NULL,       /* class_data */
-            sizeof (HildonBanner),
-            0,  /* n_preallocs */
-            (GInstanceInitFunc) hildon_banner_init,
-        };
-        banner_type = g_type_register_static (GTK_TYPE_WINDOW,
-                "HildonBanner", &banner_info, 0 );
-    }
-    return banner_type;
-}
+G_DEFINE_TYPE (HildonBanner, hildon_banner, GTK_TYPE_WINDOW)
 
 /* copy/paste from old infoprint implementation: Use matchbox 
    properties to find the topmost application window */
@@ -299,11 +277,31 @@ hildon_banner_bind_label_style                  (HildonBanner *self,
 }
 
 /* In timeout function we automatically destroy timed banners */
+static gboolean
+simulate_close (GtkWidget* widget)
+{
+    gboolean result = FALSE;
+
+    /* If the banner is currently visible (it normally should), 
+       we simulate clicking the close button of the window.
+       This allows applications to reuse the banner by prevent
+       closing it etc */
+    if (GTK_WIDGET_DRAWABLE (widget))
+    {
+        GdkEvent *event = gdk_event_new (GDK_DELETE);
+        event->any.window = g_object_ref (widget->window);
+        event->any.send_event = FALSE;
+        result = gtk_widget_event (widget, event);
+        gdk_event_free (event);
+    }
+
+    return result;
+}
+
 static gboolean 
 hildon_banner_timeout                           (gpointer data)
 {
     GtkWidget *widget;
-    GdkEvent *event;
     gboolean continue_timeout = FALSE;
 
     GDK_THREADS_ENTER ();
@@ -313,21 +311,13 @@ hildon_banner_timeout                           (gpointer data)
     widget = GTK_WIDGET (data);
     g_object_ref (widget);
 
-    /* If the banner is currently visible (it normally should), 
-       we simulate clicking the close button of the window.
-       This allows applications to reuse the banner by prevent
-       closing it etc */
-    if (GTK_WIDGET_DRAWABLE (widget))
-    {
-        event = gdk_event_new (GDK_DELETE);
-        event->any.window = g_object_ref (widget->window);
-        event->any.send_event = FALSE;
-        continue_timeout = gtk_widget_event (widget, event);
-        gdk_event_free (event);
-    }
+    continue_timeout = simulate_close (widget);
 
-    if (! continue_timeout)
+    if (! continue_timeout) {
+        HildonBannerPrivate *priv = HILDON_BANNER_GET_PRIVATE (data);
+        priv->timeout_id = 0;
         gtk_widget_destroy (widget);
+    }
 
     g_object_unref (widget);
 
@@ -396,12 +386,17 @@ hildon_banner_set_property                      (GObject *object,
 
         case PROP_PARENT_WINDOW:
             window = g_value_get_object (value);         
+            if (priv->parent) {
+                g_object_remove_weak_pointer(G_OBJECT (priv->parent), (gpointer) &priv->parent);
+            }
 
             gtk_window_set_transient_for (GTK_WINDOW (object), (GtkWindow *) window);
             priv->parent = (GtkWindow *) window;
 
-            if (window)
+            if (window) {
                 gtk_window_set_destroy_with_parent (GTK_WINDOW (object), TRUE);
+                g_object_add_weak_pointer(G_OBJECT (window), (gpointer) &priv->parent);
+            }
 
             break;
 
@@ -465,8 +460,8 @@ hildon_banner_destroy                           (GtkObject *object)
 
     (void) hildon_banner_clear_timeout (self);
 
-    if (GTK_OBJECT_CLASS (parent_class)->destroy)
-        GTK_OBJECT_CLASS (parent_class)->destroy (object);
+    if (GTK_OBJECT_CLASS (hildon_banner_parent_class)->destroy)
+        GTK_OBJECT_CLASS (hildon_banner_parent_class)->destroy (object);
 }
 
 /* Search a previous banner instance */
@@ -514,7 +509,7 @@ hildon_banner_constructor                       (GType type,
     if (! banner)
     {
         /* We have to create a new banner */
-        banner = G_OBJECT_CLASS (parent_class)->constructor (type, n_construct_params, construct_params);
+        banner = G_OBJECT_CLASS (hildon_banner_parent_class)->constructor (type, n_construct_params, construct_params);
 
         /* Store the newly created singleton instance either into parent 
            window data or into global variables. */
@@ -538,6 +533,7 @@ hildon_banner_constructor                       (GType type,
            assertion `nqueue->freeze_count > 0' failed */
 
         g_object_freeze_notify (banner);
+        hildon_banner_reset_wrap_state (HILDON_BANNER (banner));
     }
 
     /* We restart possible timeouts for each new timed banner request */
@@ -547,6 +543,33 @@ hildon_banner_constructor                       (GType type,
     return banner;
 }
 
+static void
+hildon_banner_finalize                          (GObject *object)
+{
+    HildonBannerPrivate *priv = HILDON_BANNER_GET_PRIVATE (object);
+
+    if (priv->parent) {
+        g_object_remove_weak_pointer(G_OBJECT (priv->parent), (gpointer) &priv->parent);
+    }
+
+    G_OBJECT_CLASS (hildon_banner_parent_class)->finalize (object);
+}
+
+static gboolean
+hildon_banner_button_press_event                (GtkWidget* widget,
+                                                GdkEventButton* event)
+{
+       gboolean result = simulate_close (widget);
+
+       if (!result) {
+               /* signal emission not stopped - basically behave like
+                * gtk_main_do_event() for a delete event */
+               gtk_widget_destroy (widget);
+       }
+
+       return result;
+}
+
 /* We start the timer for timed notifications after the window appears on screen */
 static gboolean 
 hildon_banner_map_event                         (GtkWidget *widget, 
@@ -554,14 +577,55 @@ hildon_banner_map_event                         (GtkWidget *widget,
 {
     gboolean result = FALSE;
 
-    if (GTK_WIDGET_CLASS (parent_class)->map_event)
-        result = GTK_WIDGET_CLASS (parent_class)->map_event (widget, event);
+    if (GTK_WIDGET_CLASS (hildon_banner_parent_class)->map_event)
+        result = GTK_WIDGET_CLASS (hildon_banner_parent_class)->map_event (widget, event);
 
     hildon_banner_ensure_timeout (HILDON_BANNER(widget));
 
     return result;
 }  
 
+#if defined(MAEMO_GTK)
+
+static GdkAtom atom_temporaries = GDK_NONE;
+
+/* Do nothing for _GTK_DELETE_TEMPORARIES */
+static gint
+hildon_banner_client_event                      (GtkWidget *widget,
+                                                 GdkEventClient  *event)
+{
+  gboolean handled = FALSE;
+
+  if (atom_temporaries == GDK_NONE)
+    atom_temporaries = gdk_atom_intern_static_string ("_GTK_DELETE_TEMPORARIES");
+
+  if (event->message_type == atom_temporaries)
+    {
+      handled = TRUE;
+    }
+
+  return handled;
+}
+#endif
+
+static void
+hildon_banner_reset_wrap_state (HildonBanner *banner)
+{
+    PangoLayout *layout;
+    HildonBannerPrivate *priv;
+
+    priv = HILDON_BANNER_GET_PRIVATE (banner);
+    g_assert (priv);
+
+    layout = gtk_label_get_layout (GTK_LABEL (priv->label));
+
+    pango_layout_set_width (layout, -1);
+    priv->has_been_wrapped = FALSE;
+    priv->has_been_truncated = FALSE;
+
+    gtk_widget_set_size_request (priv->label, -1, -1);
+    gtk_widget_set_size_request (GTK_WIDGET (banner), -1, -1);
+}
 
 /* force to wrap truncated label by setting explicit size request
  * see N#27000 and G#329646 */
@@ -572,30 +636,64 @@ force_to_wrap_truncated                         (HildonBanner *banner)
     PangoLayout *layout;
     int width_text, width_max;
     int width = -1;
+    int height = -1;
+    PangoRectangle logical;
+    GtkRequisition requisition;
     HildonBannerPrivate *priv = HILDON_BANNER_GET_PRIVATE (banner);
 
-    g_assert (priv);
+    g_return_if_fail (priv);
+
     label = GTK_LABEL (priv->label);
 
     layout = gtk_label_get_layout (label);
-    width_text  = PANGO_PIXELS(pango_layout_get_width (layout));
-    /* = width to which the lines of the PangoLayout should be wrapped */
+
+    pango_layout_get_extents (layout, NULL, &logical);
+    width_text = PANGO_PIXELS (logical.width);
 
     width_max = priv->is_timed ? HILDON_BANNER_LABEL_MAX_TIMED
         : HILDON_BANNER_LABEL_MAX_PROGRESS;
 
-    if (width_text >= width_max) {
-        /* explicitly request maximum size to force wrapping */
-        PangoRectangle logical;
+    /* If the width of the label is going to exceed the maximum allowed
+     * width, enforce the maximum allowed width now.
+     */
+    if (priv->has_been_wrapped
+        || width_text >= width_max) {
+        /* Force wrapping by setting the maximum size */
+        width = width_max;
 
-        pango_layout_set_width (layout, width_max * PANGO_SCALE);
-        pango_layout_get_extents (layout, NULL, &logical);
+        priv->has_been_wrapped = TRUE;
+    }
+
+    /* Make the label update its layout; and update our layout pointer
+     * because the layout will be cleared and refreshed.
+     */
+    gtk_widget_set_size_request (GTK_WIDGET (label), width, height);
+    gtk_widget_size_request (GTK_WIDGET (label), &requisition);
+
+    layout = gtk_label_get_layout (label);
+
+    /* If the layout has now been wrapped and exceeds 3 lines, we truncate
+     * the rest of the label according to spec.
+     */
+    if (priv->has_been_truncated
+        || (pango_layout_is_wrapped (layout)
+            && pango_layout_get_line_count (layout) > 3)) {
+        int lines;
 
-        width = PANGO_PIXELS (logical.width);
+        pango_layout_get_extents (layout, NULL, &logical);
+        lines = pango_layout_get_line_count (layout);
+
+        /* This calculation assumes that the same font is used
+         * throughout the banner -- this is usually the case on maemo
+         *
+         * FIXME: Pango >= 1.20 has pango_layout_set_height().
+         */
+        height = (PANGO_PIXELS (logical.height) * 3) / lines + 1;
+        priv->has_been_truncated = TRUE;
     }
 
-    /* use fixed width when wrapping or natural one otherwise */
-    gtk_widget_set_size_request (GTK_WIDGET (label), width, -1);
+    /* Set the new width/height if applicable */
+    gtk_widget_set_size_request (GTK_WIDGET (label), width, height);
 }
 
 
@@ -614,7 +712,11 @@ hildon_banner_check_position                    (GtkWidget *widget)
         return;
     }
 
-    x = gdk_screen_width() - HILDON_BANNER_WINDOW_X - req.width;
+    if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+        x = HILDON_BANNER_WINDOW_X;
+    else
+        x = gdk_screen_width() - HILDON_BANNER_WINDOW_X - req.width;
+
     y = check_fullscreen_state (get_current_app_window ()) ? 
         HILDON_BANNER_WINDOW_FULLSCREEN_Y : HILDON_BANNER_WINDOW_Y;
 
@@ -624,12 +726,16 @@ hildon_banner_check_position                    (GtkWidget *widget)
 static void
 hildon_banner_realize                           (GtkWidget *widget)
 {
+    HildonBannerPrivate *priv = HILDON_BANNER_GET_PRIVATE (widget);
+    g_assert (priv);
+
     /* We let the parent to init widget->window before we need it */
-    if (GTK_WIDGET_CLASS (parent_class)->realize)
-        GTK_WIDGET_CLASS (parent_class)->realize (widget);
+    if (GTK_WIDGET_CLASS (hildon_banner_parent_class)->realize)
+        GTK_WIDGET_CLASS (hildon_banner_parent_class)->realize (widget);
 
     /* We use special hint to turn the banner into information notification. */
-    gdk_window_set_type_hint (widget->window, GDK_WINDOW_TYPE_HINT_MESSAGE);
+    gdk_window_set_type_hint (widget->window, GDK_WINDOW_TYPE_HINT_NOTIFICATION);
+    gtk_window_set_transient_for (GTK_WINDOW (widget), (GtkWindow *) priv->parent);
 
     hildon_banner_check_position (widget);
 }
@@ -642,7 +748,6 @@ hildon_banner_class_init                        (HildonBannerClass *klass)
 
     object_class = G_OBJECT_CLASS (klass);
     widget_class = GTK_WIDGET_CLASS (klass);
-    parent_class = g_type_class_peek_parent (klass);
 
     /* Append private structure to class. This is more elegant than
        on g_new based approach */
@@ -650,11 +755,16 @@ hildon_banner_class_init                        (HildonBannerClass *klass)
 
     /* Override virtual methods */
     object_class->constructor = hildon_banner_constructor;
+    object_class->finalize = hildon_banner_finalize;
     object_class->set_property = hildon_banner_set_property;
     object_class->get_property = hildon_banner_get_property;
     GTK_OBJECT_CLASS (klass)->destroy = hildon_banner_destroy;
     widget_class->map_event = hildon_banner_map_event;
     widget_class->realize = hildon_banner_realize;
+    widget_class->button_press_event = hildon_banner_button_press_event;
+#if defined(MAEMO_GTK)
+    widget_class->client_event = hildon_banner_client_event;
+#endif
 
     /* Install properties.
        We need construct properties for singleton purposes */
@@ -714,12 +824,21 @@ hildon_banner_init                              (HildonBanner *self)
 
     priv->label = g_object_new (GTK_TYPE_LABEL, NULL);
     gtk_label_set_line_wrap (GTK_LABEL (priv->label), TRUE);
+    gtk_label_set_line_wrap_mode (GTK_LABEL (priv->label), PANGO_WRAP_WORD_CHAR);
 
     gtk_container_set_border_width (GTK_CONTAINER (priv->layout), HILDON_MARGIN_DEFAULT);
     gtk_container_add (GTK_CONTAINER (self), priv->layout);
     gtk_box_pack_start (GTK_BOX (priv->layout), priv->label, TRUE, TRUE, 0);
 
     gtk_window_set_accept_focus (GTK_WINDOW (self), FALSE);
+
+#if defined(MAEMO_GTK)
+    gtk_window_set_is_temporary (GTK_WINDOW (self), TRUE);
+#endif
+
+    hildon_banner_reset_wrap_state (self);
+
+    gtk_widget_add_events (GTK_WIDGET (self), GDK_BUTTON_PRESS_MASK);
 }
 
 /* Makes sure that icon/progress item contains the desired type
@@ -814,6 +933,7 @@ hildon_banner_show_information                  (GtkWidget *widget,
 
     /* Show the banner, since caller cannot do that */
     gtk_widget_show_all (GTK_WIDGET (banner));
+    gtk_window_present (GTK_WINDOW (banner));
 
     return (GtkWidget *) banner;
 }
@@ -892,6 +1012,7 @@ hildon_banner_show_information_with_markup      (GtkWidget *widget,
 
     /* Show the banner, since caller cannot do that */
     gtk_widget_show_all (GTK_WIDGET (banner));
+    gtk_window_present (GTK_WINDOW (banner));
 
     return (GtkWidget *) banner;
 }
@@ -964,6 +1085,7 @@ hildon_banner_show_animation                    (GtkWidget *widget,
 
     /* And show it */
     gtk_widget_show_all (GTK_WIDGET (banner));
+    gtk_window_present (GTK_WINDOW (banner));
 
     return (GtkWidget *) banner;
 }
@@ -1008,6 +1130,7 @@ hildon_banner_show_progress                     (GtkWidget *widget,
 
     /* Show the banner */
     gtk_widget_show_all (GTK_WIDGET (banner));
+    gtk_window_present (GTK_WINDOW (banner));
 
     return GTK_WIDGET (banner);   
 }
@@ -1026,6 +1149,7 @@ hildon_banner_set_text                          (HildonBanner *self,
 {
     GtkLabel *label;
     HildonBannerPrivate *priv;
+    const gchar *existing_text;
 
     g_return_if_fail (HILDON_IS_BANNER (self));
 
@@ -1033,7 +1157,14 @@ hildon_banner_set_text                          (HildonBanner *self,
     g_assert (priv);
 
     label = GTK_LABEL (priv->label);
-    gtk_label_set_text (label, text);
+    existing_text = gtk_label_get_text (label);
+
+    if (existing_text != NULL && 
+        text != NULL          &&
+        strcmp (existing_text, text) != 0) {
+            gtk_label_set_text (label, text);
+            hildon_banner_reset_wrap_state (self);
+    }
 
     hildon_banner_check_position (GTK_WIDGET (self));
 }
@@ -1061,6 +1192,8 @@ hildon_banner_set_markup                        (HildonBanner *self,
     label = GTK_LABEL (priv->label);
     gtk_label_set_markup (label, markup);
 
+    hildon_banner_reset_wrap_state (self);
+
     hildon_banner_check_position (GTK_WIDGET(self));
 }