/* GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * GtkStatusbar Copyright (C) 1998 Shawn T. Amundson * * 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; either * version 2 of 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 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /* * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS * file for a list of people on the GTK+ Team. See the ChangeLog * files for a list of changes. These files are distributed with * GTK+ at ftp://ftp.gtk.org/pub/gtk/. */ #include "config.h" #include "gtkframe.h" #include "gtklabel.h" #include "gtkmarshalers.h" #include "gtkstatusbar.h" #include "gtkwindow.h" #include "gtkprivate.h" #include "gtkintl.h" #include "gtkbuildable.h" /** * SECTION:gtkstatusbar * @title: GtkStatusbar * @short_description: Report messages of minor importance to the user * * A #GtkStatusbar is usually placed along the bottom of an application's * main #GtkWindow. It may provide a regular commentary of the application's * status (as is usually the case in a web browser, for example), or may be * used to simply output a message when the status changes, (when an upload * is complete in an FTP client, for example). * It may also have a resize grip (a triangular area in the lower right * corner) which can be clicked on to resize the window containing the * statusbar. * * Status bars in GTK+ maintain a stack of messages. The message at * the top of the each bar's stack is the one that will currently be displayed. * * Any messages added to a statusbar's stack must specify a * context id that is used to uniquely identify * the source of a message. This context id can be generated by * gtk_statusbar_get_context_id(), given a message and the statusbar that * it will be added to. Note that messages are stored in a stack, and when * choosing which message to display, the stack structure is adhered to, * regardless of the context identifier of a message. * * One could say that a statusbar maintains one stack of messages for * display purposes, but allows multiple message producers to maintain * sub-stacks of the messages they produced (via context ids). * * Status bars are created using gtk_statusbar_new(). * * Messages are added to the bar's stack with gtk_statusbar_push(). * * The message at the top of the stack can be removed using * gtk_statusbar_pop(). A message can be removed from anywhere in the * stack if its message id was recorded at the time it was added. This * is done using gtk_statusbar_remove(). */ typedef struct _GtkStatusbarMsg GtkStatusbarMsg; struct _GtkStatusbarPrivate { GtkWidget *frame; GtkWidget *label; GdkWindow *grip_window; GSList *messages; GSList *keys; guint seq_context_id; guint seq_message_id; guint has_resize_grip : 1; }; struct _GtkStatusbarMsg { gchar *text; guint context_id; guint message_id; }; enum { SIGNAL_TEXT_PUSHED, SIGNAL_TEXT_POPPED, SIGNAL_LAST }; enum { PROP_0, PROP_HAS_RESIZE_GRIP }; static void gtk_statusbar_buildable_interface_init (GtkBuildableIface *iface); static GObject *gtk_statusbar_buildable_get_internal_child (GtkBuildable *buildable, GtkBuilder *builder, const gchar *childname); static void gtk_statusbar_destroy (GtkObject *object); static void gtk_statusbar_update (GtkStatusbar *statusbar, guint context_id, const gchar *text); static void gtk_statusbar_size_allocate (GtkWidget *widget, GtkAllocation *allocation); static void gtk_statusbar_realize (GtkWidget *widget); static void gtk_statusbar_unrealize (GtkWidget *widget); static void gtk_statusbar_map (GtkWidget *widget); static void gtk_statusbar_unmap (GtkWidget *widget); static gboolean gtk_statusbar_button_press (GtkWidget *widget, GdkEventButton *event); static gboolean gtk_statusbar_draw (GtkWidget *widget, cairo_t *cr); static void gtk_statusbar_size_request (GtkWidget *widget, GtkRequisition *requisition); static void gtk_statusbar_size_allocate (GtkWidget *widget, GtkAllocation *allocation); static void gtk_statusbar_direction_changed (GtkWidget *widget, GtkTextDirection prev_dir); static void gtk_statusbar_state_changed (GtkWidget *widget, GtkStateType previous_state); static void gtk_statusbar_create_window (GtkStatusbar *statusbar); static void gtk_statusbar_destroy_window (GtkStatusbar *statusbar); static void gtk_statusbar_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void gtk_statusbar_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void label_selectable_changed (GtkWidget *label, GParamSpec *pspec, gpointer data); static guint statusbar_signals[SIGNAL_LAST] = { 0 }; G_DEFINE_TYPE_WITH_CODE (GtkStatusbar, gtk_statusbar, GTK_TYPE_HBOX, G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_statusbar_buildable_interface_init)); static void gtk_statusbar_class_init (GtkStatusbarClass *class) { GObjectClass *gobject_class; GtkObjectClass *object_class; GtkWidgetClass *widget_class; gobject_class = (GObjectClass *) class; object_class = (GtkObjectClass *) class; widget_class = (GtkWidgetClass *) class; gobject_class->set_property = gtk_statusbar_set_property; gobject_class->get_property = gtk_statusbar_get_property; object_class->destroy = gtk_statusbar_destroy; widget_class->realize = gtk_statusbar_realize; widget_class->unrealize = gtk_statusbar_unrealize; widget_class->map = gtk_statusbar_map; widget_class->unmap = gtk_statusbar_unmap; widget_class->button_press_event = gtk_statusbar_button_press; widget_class->draw = gtk_statusbar_draw; widget_class->size_request = gtk_statusbar_size_request; widget_class->size_allocate = gtk_statusbar_size_allocate; widget_class->direction_changed = gtk_statusbar_direction_changed; widget_class->state_changed = gtk_statusbar_state_changed; class->text_pushed = gtk_statusbar_update; class->text_popped = gtk_statusbar_update; /** * GtkStatusbar:has-resize-grip: * * Whether the statusbar has a grip for resizing the toplevel window. * * Since: 2.4 */ g_object_class_install_property (gobject_class, PROP_HAS_RESIZE_GRIP, g_param_spec_boolean ("has-resize-grip", P_("Has Resize Grip"), P_("Whether the statusbar has a grip for resizing the toplevel"), TRUE, GTK_PARAM_READWRITE)); /** * GtkStatusbar::text-pushed: * @statusbar: the object which received the signal. * @context_id: the context id of the relevant message/statusbar. * @text: the message that was pushed. * * Is emitted whenever a new message gets pushed onto a statusbar's stack. */ statusbar_signals[SIGNAL_TEXT_PUSHED] = g_signal_new (I_("text-pushed"), G_OBJECT_CLASS_TYPE (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GtkStatusbarClass, text_pushed), NULL, NULL, _gtk_marshal_VOID__UINT_STRING, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING); /** * GtkStatusbar::text-popped: * @statusbar: the object which received the signal. * @context_id: the context id of the relevant message/statusbar. * @text: the message that was just popped. * * Is emitted whenever a new message is popped off a statusbar's stack. */ statusbar_signals[SIGNAL_TEXT_POPPED] = g_signal_new (I_("text-popped"), G_OBJECT_CLASS_TYPE (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GtkStatusbarClass, text_popped), NULL, NULL, _gtk_marshal_VOID__UINT_STRING, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING); gtk_widget_class_install_style_property (widget_class, g_param_spec_enum ("shadow-type", P_("Shadow type"), P_("Style of bevel around the statusbar text"), GTK_TYPE_SHADOW_TYPE, GTK_SHADOW_IN, GTK_PARAM_READABLE)); g_type_class_add_private (class, sizeof (GtkStatusbarPrivate)); } static void gtk_statusbar_init (GtkStatusbar *statusbar) { GtkStatusbarPrivate *priv; GtkBox *box = GTK_BOX (statusbar); GtkWidget *message_area; GtkShadowType shadow_type; statusbar->priv = G_TYPE_INSTANCE_GET_PRIVATE (statusbar, GTK_TYPE_STATUSBAR, GtkStatusbarPrivate); priv = statusbar->priv; gtk_widget_set_redraw_on_allocate (GTK_WIDGET (box), TRUE); gtk_box_set_spacing (box, 2); gtk_box_set_homogeneous (box, FALSE); priv->has_resize_grip = TRUE; gtk_widget_style_get (GTK_WIDGET (statusbar), "shadow-type", &shadow_type, NULL); priv->frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (priv->frame), shadow_type); gtk_box_pack_start (box, priv->frame, TRUE, TRUE, 0); gtk_widget_show (priv->frame); message_area = gtk_hbox_new (FALSE, 4); gtk_container_add (GTK_CONTAINER (priv->frame), message_area); gtk_widget_show (message_area); priv->label = gtk_label_new (""); gtk_label_set_single_line_mode (GTK_LABEL (priv->label), TRUE); gtk_misc_set_alignment (GTK_MISC (priv->label), 0.0, 0.5); g_signal_connect (priv->label, "notify::selectable", G_CALLBACK (label_selectable_changed), statusbar); gtk_label_set_ellipsize (GTK_LABEL (priv->label), PANGO_ELLIPSIZE_END); gtk_container_add (GTK_CONTAINER (message_area), priv->label); gtk_widget_show (priv->label); priv->seq_context_id = 1; priv->seq_message_id = 1; priv->messages = NULL; priv->keys = NULL; } static GtkBuildableIface *parent_buildable_iface; static void gtk_statusbar_buildable_interface_init (GtkBuildableIface *iface) { parent_buildable_iface = g_type_interface_peek_parent (iface); iface->get_internal_child = gtk_statusbar_buildable_get_internal_child; } static GObject * gtk_statusbar_buildable_get_internal_child (GtkBuildable *buildable, GtkBuilder *builder, const gchar *childname) { GtkStatusbar *statusbar = GTK_STATUSBAR (buildable); GtkStatusbarPrivate *priv = statusbar->priv; if (strcmp (childname, "message_area") == 0) return G_OBJECT (gtk_bin_get_child (GTK_BIN (priv->frame))); return parent_buildable_iface->get_internal_child (buildable, builder, childname); } /** * gtk_statusbar_new: * * Creates a new #GtkStatusbar ready for messages. * * Returns: the new #GtkStatusbar */ GtkWidget* gtk_statusbar_new (void) { return g_object_new (GTK_TYPE_STATUSBAR, NULL); } static void gtk_statusbar_update (GtkStatusbar *statusbar, guint context_id, const gchar *text) { GtkStatusbarPrivate *priv; g_return_if_fail (GTK_IS_STATUSBAR (statusbar)); priv = statusbar->priv; if (!text) text = ""; gtk_label_set_text (GTK_LABEL (priv->label), text); } /** * gtk_statusbar_get_context_id: * @statusbar: a #GtkStatusbar * @context_description: textual description of what context * the new message is being used in * * Returns a new context identifier, given a description * of the actual context. Note that the description is * not shown in the UI. * * Returns: an integer id */ guint gtk_statusbar_get_context_id (GtkStatusbar *statusbar, const gchar *context_description) { GtkStatusbarPrivate *priv; gchar *string; guint id; g_return_val_if_fail (GTK_IS_STATUSBAR (statusbar), 0); g_return_val_if_fail (context_description != NULL, 0); priv = statusbar->priv; /* we need to preserve namespaces on object datas */ string = g_strconcat ("gtk-status-bar-context:", context_description, NULL); id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (statusbar), string)); if (id == 0) { id = priv->seq_context_id++; g_object_set_data_full (G_OBJECT (statusbar), string, GUINT_TO_POINTER (id), NULL); priv->keys = g_slist_prepend (priv->keys, string); } else g_free (string); return id; } /** * gtk_statusbar_push: * @statusbar: a #GtkStatusbar * @context_id: the message's context id, as returned by * gtk_statusbar_get_context_id() * @text: the message to add to the statusbar * * Pushes a new message onto a statusbar's stack. * * Returns: a message id that can be used with * gtk_statusbar_remove(). */ guint gtk_statusbar_push (GtkStatusbar *statusbar, guint context_id, const gchar *text) { GtkStatusbarPrivate *priv; GtkStatusbarMsg *msg; g_return_val_if_fail (GTK_IS_STATUSBAR (statusbar), 0); g_return_val_if_fail (text != NULL, 0); priv = statusbar->priv; msg = g_slice_new (GtkStatusbarMsg); msg->text = g_strdup (text); msg->context_id = context_id; msg->message_id = priv->seq_message_id++; priv->messages = g_slist_prepend (priv->messages, msg); g_signal_emit (statusbar, statusbar_signals[SIGNAL_TEXT_PUSHED], 0, msg->context_id, msg->text); return msg->message_id; } /** * gtk_statusbar_pop: * @statusbar: a #GtkStatusBar * @context_id: a context identifier * * Removes the first message in the #GtkStatusBar's stack * with the given context id. * * Note that this may not change the displayed message, if * the message at the top of the stack has a different * context id. */ void gtk_statusbar_pop (GtkStatusbar *statusbar, guint context_id) { GtkStatusbarPrivate *priv; GtkStatusbarMsg *msg; g_return_if_fail (GTK_IS_STATUSBAR (statusbar)); priv = statusbar->priv; if (priv->messages) { GSList *list; for (list = priv->messages; list; list = list->next) { msg = list->data; if (msg->context_id == context_id) { priv->messages = g_slist_remove_link (priv->messages, list); g_free (msg->text); g_slice_free (GtkStatusbarMsg, msg); g_slist_free_1 (list); break; } } } msg = priv->messages ? priv->messages->data : NULL; g_signal_emit (statusbar, statusbar_signals[SIGNAL_TEXT_POPPED], 0, (guint) (msg ? msg->context_id : 0), msg ? msg->text : NULL); } /** * gtk_statusbar_remove: * @statusbar: a #GtkStatusBar * @context_id: a context identifier * @message_id: a message identifier, as returned by gtk_statusbar_push() * * Forces the removal of a message from a statusbar's stack. * The exact @context_id and @message_id must be specified. */ void gtk_statusbar_remove (GtkStatusbar *statusbar, guint context_id, guint message_id) { GtkStatusbarPrivate *priv; GtkStatusbarMsg *msg; g_return_if_fail (GTK_IS_STATUSBAR (statusbar)); g_return_if_fail (message_id > 0); priv = statusbar->priv; msg = priv->messages ? priv->messages->data : NULL; if (msg) { GSList *list; /* care about signal emission if the topmost item is removed */ if (msg->context_id == context_id && msg->message_id == message_id) { gtk_statusbar_pop (statusbar, context_id); return; } for (list = priv->messages; list; list = list->next) { msg = list->data; if (msg->context_id == context_id && msg->message_id == message_id) { priv->messages = g_slist_remove_link (priv->messages, list); g_free (msg->text); g_slice_free (GtkStatusbarMsg, msg); g_slist_free_1 (list); break; } } } } /** * gtk_statusbar_remove_all: * @statusbar: a #GtkStatusBar * @context_id: a context identifier * * Forces the removal of all messages from a statusbar's * stack with the exact @context_id. * * Since: 2.22 */ void gtk_statusbar_remove_all (GtkStatusbar *statusbar, guint context_id) { GtkStatusbarPrivate *priv; GtkStatusbarMsg *msg; GSList *prev, *list; g_return_if_fail (GTK_IS_STATUSBAR (statusbar)); priv = statusbar->priv; if (priv->messages == NULL) return; msg = priv->messages->data; /* care about signal emission if the topmost item is removed */ if (msg->context_id == context_id) { gtk_statusbar_pop (statusbar, context_id); prev = NULL; list = priv->messages; } else { prev = priv->messages; list = prev->next; } while (list != NULL) { msg = list->data; if (msg->context_id == context_id) { if (prev == NULL) priv->messages = list->next; else prev->next = list->next; g_free (msg->text); g_slice_free (GtkStatusbarMsg, msg); g_slist_free_1 (list); if (prev == NULL) prev = priv->messages; list = prev->next; } else { prev = list; list = prev->next; } } } /** * gtk_statusbar_set_has_resize_grip: * @statusbar: a #GtkStatusBar * @setting: %TRUE to have a resize grip * * Sets whether the statusbar has a resize grip. * %TRUE by default. */ void gtk_statusbar_set_has_resize_grip (GtkStatusbar *statusbar, gboolean setting) { GtkStatusbarPrivate *priv; g_return_if_fail (GTK_IS_STATUSBAR (statusbar)); priv = statusbar->priv; setting = setting != FALSE; if (setting != priv->has_resize_grip) { priv->has_resize_grip = setting; gtk_widget_queue_resize (priv->label); gtk_widget_queue_draw (GTK_WIDGET (statusbar)); if (gtk_widget_get_realized (GTK_WIDGET (statusbar))) { if (priv->has_resize_grip && priv->grip_window == NULL) { gtk_statusbar_create_window (statusbar); if (gtk_widget_get_mapped (GTK_WIDGET (statusbar))) gdk_window_show (priv->grip_window); } else if (!priv->has_resize_grip && priv->grip_window != NULL) gtk_statusbar_destroy_window (statusbar); } g_object_notify (G_OBJECT (statusbar), "has-resize-grip"); } } /** * gtk_statusbar_get_has_resize_grip: * @statusbar: a #GtkStatusBar * * Returns whether the statusbar has a resize grip. * * Returns: %TRUE if the statusbar has a resize grip. */ gboolean gtk_statusbar_get_has_resize_grip (GtkStatusbar *statusbar) { g_return_val_if_fail (GTK_IS_STATUSBAR (statusbar), FALSE); return statusbar->priv->has_resize_grip; } /** * gtk_statusbar_get_message_area: * @statusbar: a #GtkStatusBar * * Retrieves the box containing the label widget. * * Returns: (transfer none): a #GtkBox * * Since: 2.20 */ GtkWidget* gtk_statusbar_get_message_area (GtkStatusbar *statusbar) { GtkStatusbarPrivate *priv; g_return_val_if_fail (GTK_IS_STATUSBAR (statusbar), NULL); priv = statusbar->priv; return gtk_bin_get_child (GTK_BIN (priv->frame)); } static void gtk_statusbar_destroy (GtkObject *object) { GtkStatusbar *statusbar = GTK_STATUSBAR (object); GtkStatusbarPrivate *priv = statusbar->priv; GSList *list; for (list = priv->messages; list; list = list->next) { GtkStatusbarMsg *msg; msg = list->data; g_free (msg->text); g_slice_free (GtkStatusbarMsg, msg); } g_slist_free (priv->messages); priv->messages = NULL; for (list = priv->keys; list; list = list->next) g_free (list->data); g_slist_free (priv->keys); priv->keys = NULL; GTK_OBJECT_CLASS (gtk_statusbar_parent_class)->destroy (object); } static void gtk_statusbar_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GtkStatusbar *statusbar = GTK_STATUSBAR (object); switch (prop_id) { case PROP_HAS_RESIZE_GRIP: gtk_statusbar_set_has_resize_grip (statusbar, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gtk_statusbar_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GtkStatusbar *statusbar = GTK_STATUSBAR (object); GtkStatusbarPrivate *priv = statusbar->priv; switch (prop_id) { case PROP_HAS_RESIZE_GRIP: g_value_set_boolean (value, priv->has_resize_grip); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static GdkWindowEdge get_grip_edge (GtkStatusbar *statusbar) { GtkWidget *widget = GTK_WIDGET (statusbar); if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) return GDK_WINDOW_EDGE_SOUTH_EAST; else return GDK_WINDOW_EDGE_SOUTH_WEST; } static void get_grip_rect (GtkStatusbar *statusbar, gboolean include_allocation_offset, GdkRectangle *rect) { GtkAllocation allocation; GtkStyle *style; GtkWidget *widget = GTK_WIDGET (statusbar); gint w, h; gtk_widget_get_allocation (widget, &allocation); style = gtk_widget_get_style (widget); /* These are in effect the max/default size of the grip. */ w = 18; h = 18; if (w > allocation.width) w = allocation.width; if (h > allocation.height - style->ythickness) h = allocation.height - style->ythickness; rect->width = w; rect->height = h; rect->y = allocation.height - h; if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) rect->x = allocation.width - w; else rect->x = style->xthickness; if (include_allocation_offset) { rect->x += allocation.x; rect->y += allocation.y; } } static void set_grip_cursor (GtkStatusbar *statusbar) { GtkStatusbarPrivate *priv = statusbar->priv; if (priv->has_resize_grip && priv->grip_window != NULL) { GtkWidget *widget = GTK_WIDGET (statusbar); GdkDisplay *display = gtk_widget_get_display (widget); GdkCursorType cursor_type; GdkCursor *cursor; if (gtk_widget_is_sensitive (widget)) { if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) cursor_type = GDK_BOTTOM_RIGHT_CORNER; else cursor_type = GDK_BOTTOM_LEFT_CORNER; cursor = gdk_cursor_new_for_display (display, cursor_type); gdk_window_set_cursor (priv->grip_window, cursor); gdk_cursor_unref (cursor); } else gdk_window_set_cursor (priv->grip_window, NULL); } } static void gtk_statusbar_create_window (GtkStatusbar *statusbar) { GtkWidget *widget; GtkStatusbarPrivate *priv = statusbar->priv; GdkWindowAttr attributes; gint attributes_mask; GdkRectangle rect; widget = GTK_WIDGET (statusbar); g_return_if_fail (gtk_widget_get_realized (widget)); g_return_if_fail (priv->has_resize_grip); get_grip_rect (statusbar, TRUE, &rect); attributes.x = rect.x; attributes.y = rect.y; attributes.width = rect.width; attributes.height = rect.height; attributes.window_type = GDK_WINDOW_CHILD; attributes.wclass = GDK_INPUT_ONLY; attributes.event_mask = gtk_widget_get_events (widget) | GDK_BUTTON_PRESS_MASK; attributes_mask = GDK_WA_X | GDK_WA_Y; priv->grip_window = gdk_window_new (gtk_widget_get_window (widget), &attributes, attributes_mask); gdk_window_set_user_data (priv->grip_window, widget); gdk_window_raise (priv->grip_window); set_grip_cursor (statusbar); } static void gtk_statusbar_direction_changed (GtkWidget *widget, GtkTextDirection prev_dir) { GtkStatusbar *statusbar = GTK_STATUSBAR (widget); set_grip_cursor (statusbar); } static void gtk_statusbar_state_changed (GtkWidget *widget, GtkStateType previous_state) { GtkStatusbar *statusbar = GTK_STATUSBAR (widget); set_grip_cursor (statusbar); } static void gtk_statusbar_destroy_window (GtkStatusbar *statusbar) { GtkStatusbarPrivate *priv = statusbar->priv; gdk_window_set_user_data (priv->grip_window, NULL); gdk_window_destroy (priv->grip_window); priv->grip_window = NULL; } static void gtk_statusbar_realize (GtkWidget *widget) { GtkStatusbar *statusbar = GTK_STATUSBAR (widget); GtkStatusbarPrivate *priv = statusbar->priv; GTK_WIDGET_CLASS (gtk_statusbar_parent_class)->realize (widget); if (priv->has_resize_grip) gtk_statusbar_create_window (statusbar); } static void gtk_statusbar_unrealize (GtkWidget *widget) { GtkStatusbar *statusbar = GTK_STATUSBAR (widget); GtkStatusbarPrivate *priv = statusbar->priv; if (priv->grip_window) gtk_statusbar_destroy_window (statusbar); GTK_WIDGET_CLASS (gtk_statusbar_parent_class)->unrealize (widget); } static void gtk_statusbar_map (GtkWidget *widget) { GtkStatusbar *statusbar = GTK_STATUSBAR (widget); GtkStatusbarPrivate *priv = statusbar->priv; GTK_WIDGET_CLASS (gtk_statusbar_parent_class)->map (widget); if (priv->grip_window) gdk_window_show (priv->grip_window); } static void gtk_statusbar_unmap (GtkWidget *widget) { GtkStatusbar *statusbar = GTK_STATUSBAR (widget); GtkStatusbarPrivate *priv = statusbar->priv; if (priv->grip_window) gdk_window_hide (priv->grip_window); GTK_WIDGET_CLASS (gtk_statusbar_parent_class)->unmap (widget); } static gboolean gtk_statusbar_button_press (GtkWidget *widget, GdkEventButton *event) { GtkStatusbar *statusbar = GTK_STATUSBAR (widget); GtkStatusbarPrivate *priv = statusbar->priv; GtkWidget *ancestor; GdkWindowEdge edge; if (!priv->has_resize_grip || event->type != GDK_BUTTON_PRESS || event->window != priv->grip_window) return FALSE; ancestor = gtk_widget_get_toplevel (widget); if (!GTK_IS_WINDOW (ancestor)) return FALSE; edge = get_grip_edge (statusbar); if (event->button == 1) gtk_window_begin_resize_drag (GTK_WINDOW (ancestor), edge, event->button, event->x_root, event->y_root, event->time); else if (event->button == 2) gtk_window_begin_move_drag (GTK_WINDOW (ancestor), event->button, event->x_root, event->y_root, event->time); else return FALSE; return TRUE; } static gboolean gtk_statusbar_draw (GtkWidget *widget, cairo_t *cr) { GtkStatusbar *statusbar = GTK_STATUSBAR (widget); GtkStatusbarPrivate *priv = statusbar->priv; GtkStyle *style; GdkRectangle rect; GTK_WIDGET_CLASS (gtk_statusbar_parent_class)->draw (widget, cr); if (priv->has_resize_grip) { GdkWindowEdge edge; edge = get_grip_edge (statusbar); get_grip_rect (statusbar, FALSE, &rect); style = gtk_widget_get_style (widget); gtk_cairo_paint_resize_grip (style, cr, gtk_widget_get_state (widget), widget, "statusbar", edge, rect.x, rect.y, /* don't draw grip over the frame, though you * can click on the frame. */ rect.width - style->xthickness, rect.height - style->ythickness); } return FALSE; } static void gtk_statusbar_size_request (GtkWidget *widget, GtkRequisition *requisition) { GtkStatusbar *statusbar = GTK_STATUSBAR (widget); GtkStatusbarPrivate *priv = statusbar->priv; GtkShadowType shadow_type; gtk_widget_style_get (GTK_WIDGET (statusbar), "shadow-type", &shadow_type, NULL); gtk_frame_set_shadow_type (GTK_FRAME (priv->frame), shadow_type); GTK_WIDGET_CLASS (gtk_statusbar_parent_class)->size_request (widget, requisition); } /* look for extra children between the frame containing * the label and where we want to draw the resize grip */ static gboolean has_extra_children (GtkStatusbar *statusbar) { GtkStatusbarPrivate *priv = statusbar->priv; GtkPackType child_pack_type, frame_pack_type; GtkWidget *child, *frame; GList *l, *children; gboolean retval = FALSE; /* If the internal frame has been modified assume we have extra children */ if (gtk_bin_get_child (GTK_BIN (priv->frame)) != priv->label) return TRUE; frame = NULL; children = _gtk_box_get_children (GTK_BOX (statusbar)); for (l = children; l; l = l->next) { frame = l->data; if (frame == priv->frame) break; } gtk_box_query_child_packing (GTK_BOX (statusbar), frame, NULL, NULL, NULL, &frame_pack_type); for (l = l->next; l; l = l->next) { child = l->data; if (!gtk_widget_get_visible (child)) continue; gtk_box_query_child_packing (GTK_BOX (statusbar), frame, NULL, NULL, NULL, &child_pack_type); if (frame_pack_type == GTK_PACK_START || child_pack_type == GTK_PACK_END) { retval = TRUE; break; } } g_list_free (children); return retval; } static void gtk_statusbar_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { GtkStatusbar *statusbar = GTK_STATUSBAR (widget); GtkStatusbarPrivate *priv = statusbar->priv; gboolean extra_children = FALSE; GdkRectangle rect; if (priv->has_resize_grip) { get_grip_rect (statusbar, TRUE, &rect); extra_children = has_extra_children (statusbar); /* If there are extra children, we don't want them to occupy * the space where we draw the resize grip, so we temporarily * shrink the allocation. * If there are no extra children, we want the frame to get * the full allocation, and we fix up the allocation of the * label afterwards to make room for the grip. */ if (extra_children) { allocation->width -= rect.width; if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) allocation->x += rect.width; } } /* chain up normally */ GTK_WIDGET_CLASS (gtk_statusbar_parent_class)->size_allocate (widget, allocation); if (priv->has_resize_grip) { if (extra_children) { allocation->width += rect.width; if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) allocation->x -= rect.width; gtk_widget_set_allocation (widget, allocation); } else { GtkAllocation child_allocation, frame_allocation; GtkWidget *child; /* Use the frame's child instead of priv->label directly, in case * the label has been replaced by a container as the frame's child * (and the label reparented into that container). */ child = gtk_bin_get_child (GTK_BIN (priv->frame)); gtk_widget_get_allocation (child, &child_allocation); gtk_widget_get_allocation (priv->frame, &frame_allocation); if (child_allocation.width + rect.width > frame_allocation.width) { /* shrink the label to make room for the grip */ *allocation = child_allocation; allocation->width = MAX (1, allocation->width - rect.width); if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) allocation->x += child_allocation.width - allocation->width; gtk_widget_size_allocate (child, allocation); } } if (priv->grip_window) { get_grip_rect (statusbar, TRUE, &rect); gdk_window_raise (priv->grip_window); gdk_window_move_resize (priv->grip_window, rect.x, rect.y, rect.width, rect.height); } } } static void label_selectable_changed (GtkWidget *label, GParamSpec *pspec, gpointer data) { GtkStatusbar *statusbar = GTK_STATUSBAR (data); GtkStatusbarPrivate *priv = statusbar->priv; if (statusbar && priv->has_resize_grip && priv->grip_window) gdk_window_raise (priv->grip_window); }