/* GIMP - The GNU Image Manipulation Program Copyright (C) 1995 * Spencer Kimball and Peter Mattis * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include "libgimpbase/gimpbase.h" #include "libgimpmath/gimpmath.h" #include "libgimpwidgets/gimpwidgets.h" #include "display-types.h" #include "config/gimpdisplayconfig.h" #include "core/gimpimage.h" #include "core/gimpprogress.h" #include "widgets/gimpuimanager.h" #include "widgets/gimpwidgets-utils.h" #include "gimpdisplay.h" #include "gimpdisplayshell.h" #include "gimpdisplayshell-scale.h" #include "gimpimagewindow.h" #include "gimpscalecombobox.h" #include "gimpstatusbar.h" #include "gimp-intl.h" /* maximal width of the string holding the cursor-coordinates */ #define CURSOR_LEN 256 /* the spacing of the hbox */ #define HBOX_SPACING 1 /* spacing between the icon and the statusbar label */ #define ICON_SPACING 2 /* width/height of the statusbar icon rect */ #define ICON_SIZE 16 /* timeout (in milliseconds) for temporary statusbar messages */ #define MESSAGE_TIMEOUT 8000 /* minimal interval (in microseconds) between progress updates */ #define MIN_PROGRESS_UPDATE_INTERVAL 50000 typedef struct _GimpStatusbarMsg GimpStatusbarMsg; struct _GimpStatusbarMsg { guint context_id; gchar *icon_name; gchar *text; }; static void gimp_statusbar_progress_iface_init (GimpProgressInterface *iface); static void gimp_statusbar_dispose (GObject *object); static void gimp_statusbar_finalize (GObject *object); static void gimp_statusbar_screen_changed (GtkWidget *widget, GdkScreen *previous); static void gimp_statusbar_style_set (GtkWidget *widget, GtkStyle *prev_style); static void gimp_statusbar_hbox_size_request (GtkWidget *widget, GtkRequisition *requisition, GimpStatusbar *statusbar); static GimpProgress * gimp_statusbar_progress_start (GimpProgress *progress, gboolean cancellable, const gchar *message); static void gimp_statusbar_progress_end (GimpProgress *progress); static gboolean gimp_statusbar_progress_is_active (GimpProgress *progress); static void gimp_statusbar_progress_set_text (GimpProgress *progress, const gchar *message); static void gimp_statusbar_progress_set_value (GimpProgress *progress, gdouble percentage); static gdouble gimp_statusbar_progress_get_value (GimpProgress *progress); static void gimp_statusbar_progress_pulse (GimpProgress *progress); static gboolean gimp_statusbar_progress_message (GimpProgress *progress, Gimp *gimp, GimpMessageSeverity severity, const gchar *domain, const gchar *message); static void gimp_statusbar_progress_canceled (GtkWidget *button, GimpStatusbar *statusbar); static gboolean gimp_statusbar_label_expose (GtkWidget *widget, GdkEventExpose *event, GimpStatusbar *statusbar); static void gimp_statusbar_update (GimpStatusbar *statusbar); static void gimp_statusbar_unit_changed (GimpUnitComboBox *combo, GimpStatusbar *statusbar); static void gimp_statusbar_scale_changed (GimpScaleComboBox *combo, GimpStatusbar *statusbar); static void gimp_statusbar_scale_activated (GimpScaleComboBox *combo, GimpStatusbar *statusbar); static gboolean gimp_statusbar_rotate_pressed (GtkWidget *event_box, GdkEvent *event, GimpStatusbar *statusbar); static gboolean gimp_statusbar_horiz_flip_pressed (GtkWidget *event_box, GdkEvent *event, GimpStatusbar *statusbar); static gboolean gimp_statusbar_vert_flip_pressed (GtkWidget *event_box, GdkEvent *event, GimpStatusbar *statusbar); static void gimp_statusbar_shell_scaled (GimpDisplayShell *shell, GimpStatusbar *statusbar); static void gimp_statusbar_shell_rotated (GimpDisplayShell *shell, GimpStatusbar *statusbar); static void gimp_statusbar_shell_status_notify(GimpDisplayShell *shell, const GParamSpec *pspec, GimpStatusbar *statusbar); static guint gimp_statusbar_get_context_id (GimpStatusbar *statusbar, const gchar *context); static gboolean gimp_statusbar_temp_timeout (GimpStatusbar *statusbar); static void gimp_statusbar_add_message (GimpStatusbar *statusbar, guint context_id, const gchar *icon_name, const gchar *format, va_list args, gboolean move_to_front) G_GNUC_PRINTF (4, 0); static void gimp_statusbar_remove_message (GimpStatusbar *statusbar, guint context_id); static void gimp_statusbar_msg_free (GimpStatusbarMsg *msg); static gchar * gimp_statusbar_vprintf (const gchar *format, va_list args) G_GNUC_PRINTF (1, 0); static GdkPixbuf * gimp_statusbar_load_icon (GimpStatusbar *statusbar, const gchar *icon_name); G_DEFINE_TYPE_WITH_CODE (GimpStatusbar, gimp_statusbar, GTK_TYPE_STATUSBAR, G_IMPLEMENT_INTERFACE (GIMP_TYPE_PROGRESS, gimp_statusbar_progress_iface_init)) #define parent_class gimp_statusbar_parent_class static void gimp_statusbar_class_init (GimpStatusbarClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->dispose = gimp_statusbar_dispose; object_class->finalize = gimp_statusbar_finalize; widget_class->screen_changed = gimp_statusbar_screen_changed; widget_class->style_set = gimp_statusbar_style_set; } static void gimp_statusbar_progress_iface_init (GimpProgressInterface *iface) { iface->start = gimp_statusbar_progress_start; iface->end = gimp_statusbar_progress_end; iface->is_active = gimp_statusbar_progress_is_active; iface->set_text = gimp_statusbar_progress_set_text; iface->set_value = gimp_statusbar_progress_set_value; iface->get_value = gimp_statusbar_progress_get_value; iface->pulse = gimp_statusbar_progress_pulse; iface->message = gimp_statusbar_progress_message; } static void gimp_statusbar_init (GimpStatusbar *statusbar) { GtkWidget *hbox; GtkWidget *hbox2; GtkWidget *image; GtkWidget *label; GimpUnitStore *store; GList *children; statusbar->shell = NULL; statusbar->messages = NULL; statusbar->context_ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); statusbar->seq_context_id = 1; statusbar->temp_context_id = gimp_statusbar_get_context_id (statusbar, "gimp-statusbar-temp"); statusbar->cursor_format_str[0] = '\0'; statusbar->cursor_format_str_f[0] = '\0'; statusbar->length_format_str[0] = '\0'; statusbar->progress_active = FALSE; statusbar->progress_shown = FALSE; /* remove the message label from the message area */ hbox = gtk_statusbar_get_message_area (GTK_STATUSBAR (statusbar)); gtk_box_set_spacing (GTK_BOX (hbox), HBOX_SPACING); children = gtk_container_get_children (GTK_CONTAINER (hbox)); statusbar->label = g_object_ref (children->data); g_list_free (children); gtk_container_remove (GTK_CONTAINER (hbox), statusbar->label); g_signal_connect (hbox, "size-request", G_CALLBACK (gimp_statusbar_hbox_size_request), statusbar); statusbar->cursor_label = gtk_label_new ("8888, 8888"); gtk_box_pack_start (GTK_BOX (hbox), statusbar->cursor_label, FALSE, FALSE, 0); gtk_widget_show (statusbar->cursor_label); store = gimp_unit_store_new (2); statusbar->unit_combo = gimp_unit_combo_box_new_with_model (store); g_object_unref (store); /* see issue #2642 */ gtk_combo_box_set_wrap_width (GTK_COMBO_BOX (statusbar->unit_combo), 1); gtk_widget_set_can_focus (statusbar->unit_combo, FALSE); g_object_set (statusbar->unit_combo, "focus-on-click", FALSE, NULL); gtk_box_pack_start (GTK_BOX (hbox), statusbar->unit_combo, FALSE, FALSE, 0); gtk_widget_show (statusbar->unit_combo); g_signal_connect (statusbar->unit_combo, "changed", G_CALLBACK (gimp_statusbar_unit_changed), statusbar); statusbar->scale_combo = gimp_scale_combo_box_new (); gtk_widget_set_can_focus (statusbar->scale_combo, FALSE); g_object_set (statusbar->scale_combo, "focus-on-click", FALSE, NULL); gtk_box_pack_start (GTK_BOX (hbox), statusbar->scale_combo, FALSE, FALSE, 0); gtk_widget_show (statusbar->scale_combo); g_signal_connect (statusbar->scale_combo, "changed", G_CALLBACK (gimp_statusbar_scale_changed), statusbar); g_signal_connect (statusbar->scale_combo, "entry-activated", G_CALLBACK (gimp_statusbar_scale_activated), statusbar); /* Shell transform status */ statusbar->rotate_widget = gtk_event_box_new (); gtk_box_pack_start (GTK_BOX (hbox), statusbar->rotate_widget, FALSE, FALSE, 1); gtk_widget_show (statusbar->rotate_widget); statusbar->rotate_label = gtk_label_new (NULL); gtk_container_add (GTK_CONTAINER (statusbar->rotate_widget), statusbar->rotate_label); gtk_widget_show (statusbar->rotate_label); g_signal_connect (statusbar->rotate_widget, "button-press-event", G_CALLBACK (gimp_statusbar_rotate_pressed), statusbar); statusbar->horizontal_flip_icon = gtk_event_box_new (); gtk_box_pack_start (GTK_BOX (hbox), statusbar->horizontal_flip_icon, FALSE, FALSE, 1); gtk_widget_show (statusbar->horizontal_flip_icon); image = gtk_image_new_from_icon_name ("gimp-flip-horizontal", GTK_ICON_SIZE_MENU); gtk_container_add (GTK_CONTAINER (statusbar->horizontal_flip_icon), image); gtk_widget_show (image); g_signal_connect (statusbar->horizontal_flip_icon, "button-press-event", G_CALLBACK (gimp_statusbar_horiz_flip_pressed), statusbar); statusbar->vertical_flip_icon = gtk_event_box_new (); gtk_box_pack_start (GTK_BOX (hbox), statusbar->vertical_flip_icon, FALSE, FALSE, 1); gtk_widget_show (statusbar->vertical_flip_icon); image = gtk_image_new_from_icon_name ("gimp-flip-vertical", GTK_ICON_SIZE_MENU); gtk_container_add (GTK_CONTAINER (statusbar->vertical_flip_icon), image); gtk_widget_show (image); g_signal_connect (statusbar->vertical_flip_icon, "button-press-event", G_CALLBACK (gimp_statusbar_vert_flip_pressed), statusbar); /* put the label back into the message area */ gtk_box_pack_start (GTK_BOX (hbox), statusbar->label, TRUE, TRUE, 1); g_object_unref (statusbar->label); g_signal_connect_after (statusbar->label, "expose-event", G_CALLBACK (gimp_statusbar_label_expose), statusbar); statusbar->progressbar = g_object_new (GTK_TYPE_PROGRESS_BAR, "text-xalign", 0.0, "text-yalign", 0.5, "ellipsize", PANGO_ELLIPSIZE_END, NULL); gtk_box_pack_start (GTK_BOX (hbox), statusbar->progressbar, TRUE, TRUE, 0); /* don't show the progress bar */ /* construct the cancel button's contents manually because we * always want image and label regardless of settings, and we want * a menu size image. */ statusbar->cancel_button = gtk_button_new (); gtk_widget_set_can_focus (statusbar->cancel_button, FALSE); gtk_button_set_relief (GTK_BUTTON (statusbar->cancel_button), GTK_RELIEF_NONE); gtk_widget_set_sensitive (statusbar->cancel_button, FALSE); gtk_box_pack_end (GTK_BOX (hbox), statusbar->cancel_button, FALSE, FALSE, 0); /* don't show the cancel button */ hbox2 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_container_add (GTK_CONTAINER (statusbar->cancel_button), hbox2); gtk_widget_show (hbox2); image = gtk_image_new_from_icon_name ("gtk-cancel", GTK_ICON_SIZE_MENU); gtk_box_pack_start (GTK_BOX (hbox2), image, FALSE, FALSE, 2); gtk_widget_show (image); label = gtk_label_new ("Cancel"); gtk_box_pack_start (GTK_BOX (hbox2), label, FALSE, FALSE, 2); gtk_widget_show (label); g_signal_connect (statusbar->cancel_button, "clicked", G_CALLBACK (gimp_statusbar_progress_canceled), statusbar); } static void gimp_statusbar_dispose (GObject *object) { GimpStatusbar *statusbar = GIMP_STATUSBAR (object); if (statusbar->temp_timeout_id) { g_source_remove (statusbar->temp_timeout_id); statusbar->temp_timeout_id = 0; } G_OBJECT_CLASS (parent_class)->dispose (object); } static void gimp_statusbar_finalize (GObject *object) { GimpStatusbar *statusbar = GIMP_STATUSBAR (object); g_clear_object (&statusbar->icon); g_clear_pointer (&statusbar->icon_hash, g_hash_table_unref); g_slist_free_full (statusbar->messages, (GDestroyNotify) gimp_statusbar_msg_free); statusbar->messages = NULL; g_clear_pointer (&statusbar->context_ids, g_hash_table_destroy); G_OBJECT_CLASS (parent_class)->finalize (object); } static void gimp_statusbar_screen_changed (GtkWidget *widget, GdkScreen *previous) { GimpStatusbar *statusbar = GIMP_STATUSBAR (widget); if (GTK_WIDGET_CLASS (parent_class)->screen_changed) GTK_WIDGET_CLASS (parent_class)->screen_changed (widget, previous); g_clear_pointer (&statusbar->icon_hash, g_hash_table_unref); } static void gimp_statusbar_style_set (GtkWidget *widget, GtkStyle *prev_style) { GimpStatusbar *statusbar = GIMP_STATUSBAR (widget); GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); g_clear_pointer (&statusbar->icon_hash, g_hash_table_unref); } static void gimp_statusbar_hbox_size_request (GtkWidget *widget, GtkRequisition *requisition, GimpStatusbar *statusbar) { GtkRequisition child_requisition; gint width = 0; /* also consider the children which can be invisible */ gtk_widget_size_request (statusbar->cursor_label, &child_requisition); width += child_requisition.width; requisition->height = MAX (requisition->height, child_requisition.height); gtk_widget_size_request (statusbar->unit_combo, &child_requisition); width += child_requisition.width; requisition->height = MAX (requisition->height, child_requisition.height); gtk_widget_size_request (statusbar->scale_combo, &child_requisition); width += child_requisition.width; requisition->height = MAX (requisition->height, child_requisition.height); gtk_widget_size_request (statusbar->progressbar, &child_requisition); requisition->height = MAX (requisition->height, child_requisition.height); gtk_widget_size_request (statusbar->label, &child_requisition); requisition->height = MAX (requisition->height, child_requisition.height); gtk_widget_size_request (statusbar->cancel_button, &child_requisition); requisition->height = MAX (requisition->height, child_requisition.height); requisition->width = MAX (requisition->width, width + 32); } static GimpProgress * gimp_statusbar_progress_start (GimpProgress *progress, gboolean cancellable, const gchar *message) { GimpStatusbar *statusbar = GIMP_STATUSBAR (progress); if (! statusbar->progress_active) { GtkWidget *bar = statusbar->progressbar; GtkAllocation allocation; statusbar->progress_active = TRUE; statusbar->progress_value = 0.0; statusbar->progress_last_update_time = g_get_monotonic_time (); gimp_statusbar_push (statusbar, "progress", NULL, "%s", message); gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (bar), 0.0); gtk_widget_set_sensitive (statusbar->cancel_button, cancellable); if (cancellable) { if (message) { gchar *tooltip = g_strdup_printf (_("Cancel %s"), message); gimp_help_set_help_data_with_markup (statusbar->cancel_button, tooltip, NULL); g_free (tooltip); } gtk_widget_show (statusbar->cancel_button); } gtk_widget_get_allocation (statusbar->label, &allocation); gtk_widget_show (statusbar->progressbar); gtk_widget_hide (statusbar->label); /* This shit is needed so that the progress bar is drawn in the * correct place in the cases where we suck completely and run * an operation that blocks the GUI and doesn't let the main * loop run. */ gtk_container_resize_children (GTK_CONTAINER (statusbar)); gtk_widget_size_allocate (statusbar->progressbar, &allocation); if (! gtk_widget_get_visible (GTK_WIDGET (statusbar))) { gtk_widget_show (GTK_WIDGET (statusbar)); statusbar->progress_shown = TRUE; } gimp_widget_flush_expose (bar); gimp_statusbar_override_window_title (statusbar); return progress; } return NULL; } static void gimp_statusbar_progress_end (GimpProgress *progress) { GimpStatusbar *statusbar = GIMP_STATUSBAR (progress); if (statusbar->progress_active) { GtkWidget *bar = statusbar->progressbar; if (statusbar->progress_shown) { gtk_widget_hide (GTK_WIDGET (statusbar)); statusbar->progress_shown = FALSE; } statusbar->progress_active = FALSE; statusbar->progress_value = 0.0; gtk_widget_hide (bar); gtk_widget_show (statusbar->label); gimp_statusbar_pop (statusbar, "progress"); gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (bar), 0.0); gtk_widget_set_sensitive (statusbar->cancel_button, FALSE); gtk_widget_hide (statusbar->cancel_button); gimp_statusbar_restore_window_title (statusbar); } } static gboolean gimp_statusbar_progress_is_active (GimpProgress *progress) { GimpStatusbar *statusbar = GIMP_STATUSBAR (progress); return statusbar->progress_active; } static void gimp_statusbar_progress_set_text (GimpProgress *progress, const gchar *message) { GimpStatusbar *statusbar = GIMP_STATUSBAR (progress); if (statusbar->progress_active) { GtkWidget *bar = statusbar->progressbar; gimp_statusbar_replace (statusbar, "progress", NULL, "%s", message); gimp_widget_flush_expose (bar); gimp_statusbar_override_window_title (statusbar); } } static void gimp_statusbar_progress_set_value (GimpProgress *progress, gdouble percentage) { GimpStatusbar *statusbar = GIMP_STATUSBAR (progress); if (statusbar->progress_active) { guint64 time = g_get_monotonic_time (); if (time - statusbar->progress_last_update_time >= MIN_PROGRESS_UPDATE_INTERVAL) { GtkWidget *bar = statusbar->progressbar; GtkAllocation allocation; gdouble diff; gtk_widget_get_allocation (bar, &allocation); statusbar->progress_value = percentage; diff = fabs (percentage - gtk_progress_bar_get_fraction (GTK_PROGRESS_BAR (bar))); /* only update the progress bar if this causes a visible change */ if (allocation.width * diff >= 1.0) { statusbar->progress_last_update_time = time; gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (bar), percentage); gimp_widget_flush_expose (bar); } } } } static gdouble gimp_statusbar_progress_get_value (GimpProgress *progress) { GimpStatusbar *statusbar = GIMP_STATUSBAR (progress); if (statusbar->progress_active) return statusbar->progress_value; return 0.0; } static void gimp_statusbar_progress_pulse (GimpProgress *progress) { GimpStatusbar *statusbar = GIMP_STATUSBAR (progress); if (statusbar->progress_active) { guint64 time = g_get_monotonic_time (); if (time - statusbar->progress_last_update_time >= MIN_PROGRESS_UPDATE_INTERVAL) { GtkWidget *bar = statusbar->progressbar; statusbar->progress_last_update_time = time; gtk_progress_bar_pulse (GTK_PROGRESS_BAR (bar)); gimp_widget_flush_expose (bar); } } } static gboolean gimp_statusbar_progress_message (GimpProgress *progress, Gimp *gimp, GimpMessageSeverity severity, const gchar *domain, const gchar *message) { GimpStatusbar *statusbar = GIMP_STATUSBAR (progress); PangoLayout *layout; const gchar *icon_name; gboolean handle_msg = FALSE; /* don't accept a message if we are already displaying a more severe one */ if (statusbar->temp_timeout_id && statusbar->temp_severity > severity) return FALSE; /* we can only handle short one-liners */ layout = gtk_widget_create_pango_layout (statusbar->label, message); icon_name = gimp_get_message_icon_name (severity); if (pango_layout_get_line_count (layout) == 1) { GtkAllocation label_allocation; gint width; gtk_widget_get_allocation (statusbar->label, &label_allocation); pango_layout_get_pixel_size (layout, &width, NULL); if (width < label_allocation.width) { if (icon_name) { GdkPixbuf *pixbuf; pixbuf = gimp_statusbar_load_icon (statusbar, icon_name); width += ICON_SPACING + gdk_pixbuf_get_width (pixbuf); g_object_unref (pixbuf); handle_msg = (width < label_allocation.width); } else { handle_msg = TRUE; } } } g_object_unref (layout); if (handle_msg) gimp_statusbar_push_temp (statusbar, severity, icon_name, "%s", message); return handle_msg; } static void gimp_statusbar_progress_canceled (GtkWidget *button, GimpStatusbar *statusbar) { if (statusbar->progress_active) gimp_progress_cancel (GIMP_PROGRESS (statusbar)); } static void gimp_statusbar_set_text (GimpStatusbar *statusbar, const gchar *icon_name, const gchar *text) { if (statusbar->progress_active) { gtk_progress_bar_set_text (GTK_PROGRESS_BAR (statusbar->progressbar), text); } else { g_clear_object (&statusbar->icon); if (icon_name) statusbar->icon = gimp_statusbar_load_icon (statusbar, icon_name); if (statusbar->icon) { PangoAttrList *attrs; PangoAttribute *attr; PangoRectangle rect; gchar *tmp; tmp = g_strconcat (" ", text, NULL); gtk_label_set_text (GTK_LABEL (statusbar->label), tmp); g_free (tmp); rect.x = 0; rect.y = 0; rect.width = PANGO_SCALE * (gdk_pixbuf_get_width (statusbar->icon) + ICON_SPACING); rect.height = 0; attrs = pango_attr_list_new (); attr = pango_attr_shape_new (&rect, &rect); attr->start_index = 0; attr->end_index = 1; pango_attr_list_insert (attrs, attr); gtk_label_set_attributes (GTK_LABEL (statusbar->label), attrs); pango_attr_list_unref (attrs); } else { gtk_label_set_text (GTK_LABEL (statusbar->label), text); gtk_label_set_attributes (GTK_LABEL (statusbar->label), NULL); } } } static void gimp_statusbar_update (GimpStatusbar *statusbar) { GimpStatusbarMsg *msg = NULL; if (statusbar->messages) msg = statusbar->messages->data; if (msg && msg->text) { gimp_statusbar_set_text (statusbar, msg->icon_name, msg->text); } else { gimp_statusbar_set_text (statusbar, NULL, ""); } } /* public functions */ GtkWidget * gimp_statusbar_new (void) { return g_object_new (GIMP_TYPE_STATUSBAR, NULL); } void gimp_statusbar_set_shell (GimpStatusbar *statusbar, GimpDisplayShell *shell) { g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); if (shell == statusbar->shell) return; if (statusbar->shell) { g_signal_handlers_disconnect_by_func (statusbar->shell, gimp_statusbar_shell_scaled, statusbar); g_signal_handlers_disconnect_by_func (statusbar->shell, gimp_statusbar_shell_rotated, statusbar); g_signal_handlers_disconnect_by_func (statusbar->shell, gimp_statusbar_shell_status_notify, statusbar); } statusbar->shell = shell; g_signal_connect_object (statusbar->shell, "scaled", G_CALLBACK (gimp_statusbar_shell_scaled), statusbar, 0); g_signal_connect_object (statusbar->shell, "rotated", G_CALLBACK (gimp_statusbar_shell_rotated), statusbar, 0); g_signal_connect_object (statusbar->shell, "notify::status", G_CALLBACK (gimp_statusbar_shell_status_notify), statusbar, 0); gimp_statusbar_shell_rotated (shell, statusbar); } gboolean gimp_statusbar_get_visible (GimpStatusbar *statusbar) { g_return_val_if_fail (GIMP_IS_STATUSBAR (statusbar), FALSE); if (statusbar->progress_shown) return FALSE; return gtk_widget_get_visible (GTK_WIDGET (statusbar)); } void gimp_statusbar_set_visible (GimpStatusbar *statusbar, gboolean visible) { g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); if (statusbar->progress_shown) { if (visible) { statusbar->progress_shown = FALSE; return; } } gtk_widget_set_visible (GTK_WIDGET (statusbar), visible); } void gimp_statusbar_empty (GimpStatusbar *statusbar) { g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); gtk_widget_hide (statusbar->cursor_label); gtk_widget_hide (statusbar->unit_combo); gtk_widget_hide (statusbar->scale_combo); gtk_widget_hide (statusbar->rotate_widget); gtk_widget_hide (statusbar->horizontal_flip_icon); gtk_widget_hide (statusbar->vertical_flip_icon); } void gimp_statusbar_fill (GimpStatusbar *statusbar) { g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); gtk_widget_show (statusbar->cursor_label); gtk_widget_show (statusbar->unit_combo); gtk_widget_show (statusbar->scale_combo); gtk_widget_show (statusbar->rotate_widget); gimp_statusbar_shell_rotated (statusbar->shell, statusbar); } void gimp_statusbar_override_window_title (GimpStatusbar *statusbar) { GtkWidget *toplevel; g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); toplevel = gtk_widget_get_toplevel (GTK_WIDGET (statusbar)); if (gimp_image_window_is_iconified (GIMP_IMAGE_WINDOW (toplevel))) { const gchar *message = gimp_statusbar_peek (statusbar, "progress"); if (message) gtk_window_set_title (GTK_WINDOW (toplevel), message); } } void gimp_statusbar_restore_window_title (GimpStatusbar *statusbar) { GtkWidget *toplevel; g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); toplevel = gtk_widget_get_toplevel (GTK_WIDGET (statusbar)); if (gimp_image_window_is_iconified (GIMP_IMAGE_WINDOW (toplevel))) { g_object_notify (G_OBJECT (statusbar->shell), "title"); } } void gimp_statusbar_push (GimpStatusbar *statusbar, const gchar *context, const gchar *icon_name, const gchar *format, ...) { va_list args; g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); g_return_if_fail (context != NULL); g_return_if_fail (format != NULL); va_start (args, format); gimp_statusbar_push_valist (statusbar, context, icon_name, format, args); va_end (args); } void gimp_statusbar_push_valist (GimpStatusbar *statusbar, const gchar *context, const gchar *icon_name, const gchar *format, va_list args) { guint context_id; g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); g_return_if_fail (context != NULL); g_return_if_fail (format != NULL); context_id = gimp_statusbar_get_context_id (statusbar, context); gimp_statusbar_add_message (statusbar, context_id, icon_name, format, args, /* move_to_front = */ TRUE); } void gimp_statusbar_push_coords (GimpStatusbar *statusbar, const gchar *context, const gchar *icon_name, GimpCursorPrecision precision, const gchar *title, gdouble x, const gchar *separator, gdouble y, const gchar *help) { GimpDisplayShell *shell; g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); g_return_if_fail (title != NULL); g_return_if_fail (separator != NULL); if (help == NULL) help = ""; shell = statusbar->shell; switch (precision) { case GIMP_CURSOR_PRECISION_PIXEL_CENTER: x = (gint) x; y = (gint) y; break; case GIMP_CURSOR_PRECISION_PIXEL_BORDER: x = RINT (x); y = RINT (y); break; case GIMP_CURSOR_PRECISION_SUBPIXEL: break; } if (shell->unit == GIMP_UNIT_PIXEL) { if (precision == GIMP_CURSOR_PRECISION_SUBPIXEL) { gimp_statusbar_push (statusbar, context, icon_name, statusbar->cursor_format_str_f, title, x, separator, y, help); } else { gimp_statusbar_push (statusbar, context, icon_name, statusbar->cursor_format_str, title, (gint) RINT (x), separator, (gint) RINT (y), help); } } else /* show real world units */ { gdouble xres; gdouble yres; gimp_image_get_resolution (gimp_display_get_image (shell->display), &xres, &yres); gimp_statusbar_push (statusbar, context, icon_name, statusbar->cursor_format_str, title, gimp_pixels_to_units (x, shell->unit, xres), separator, gimp_pixels_to_units (y, shell->unit, yres), help); } } void gimp_statusbar_push_length (GimpStatusbar *statusbar, const gchar *context, const gchar *icon_name, const gchar *title, GimpOrientationType axis, gdouble value, const gchar *help) { GimpDisplayShell *shell; g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); g_return_if_fail (title != NULL); if (help == NULL) help = ""; shell = statusbar->shell; if (shell->unit == GIMP_UNIT_PIXEL) { gimp_statusbar_push (statusbar, context, icon_name, statusbar->length_format_str, title, (gint) RINT (value), help); } else /* show real world units */ { gdouble xres; gdouble yres; gdouble resolution; gimp_image_get_resolution (gimp_display_get_image (shell->display), &xres, &yres); switch (axis) { case GIMP_ORIENTATION_HORIZONTAL: resolution = xres; break; case GIMP_ORIENTATION_VERTICAL: resolution = yres; break; default: g_return_if_reached (); break; } gimp_statusbar_push (statusbar, context, icon_name, statusbar->length_format_str, title, gimp_pixels_to_units (value, shell->unit, resolution), help); } } void gimp_statusbar_replace (GimpStatusbar *statusbar, const gchar *context, const gchar *icon_name, const gchar *format, ...) { va_list args; g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); g_return_if_fail (context != NULL); g_return_if_fail (format != NULL); va_start (args, format); gimp_statusbar_replace_valist (statusbar, context, icon_name, format, args); va_end (args); } void gimp_statusbar_replace_valist (GimpStatusbar *statusbar, const gchar *context, const gchar *icon_name, const gchar *format, va_list args) { guint context_id; g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); g_return_if_fail (context != NULL); g_return_if_fail (format != NULL); context_id = gimp_statusbar_get_context_id (statusbar, context); gimp_statusbar_add_message (statusbar, context_id, icon_name, format, args, /* move_to_front = */ FALSE); } const gchar * gimp_statusbar_peek (GimpStatusbar *statusbar, const gchar *context) { GSList *list; guint context_id; g_return_val_if_fail (GIMP_IS_STATUSBAR (statusbar), NULL); g_return_val_if_fail (context != NULL, NULL); context_id = gimp_statusbar_get_context_id (statusbar, context); for (list = statusbar->messages; list; list = list->next) { GimpStatusbarMsg *msg = list->data; if (msg->context_id == context_id) { return msg->text; } } return NULL; } void gimp_statusbar_pop (GimpStatusbar *statusbar, const gchar *context) { guint context_id; g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); g_return_if_fail (context != NULL); context_id = gimp_statusbar_get_context_id (statusbar, context); gimp_statusbar_remove_message (statusbar, context_id); } void gimp_statusbar_push_temp (GimpStatusbar *statusbar, GimpMessageSeverity severity, const gchar *icon_name, const gchar *format, ...) { va_list args; va_start (args, format); gimp_statusbar_push_temp_valist (statusbar, severity, icon_name, format, args); va_end (args); } void gimp_statusbar_push_temp_valist (GimpStatusbar *statusbar, GimpMessageSeverity severity, const gchar *icon_name, const gchar *format, va_list args) { g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); g_return_if_fail (severity <= GIMP_MESSAGE_WARNING); g_return_if_fail (format != NULL); /* don't accept a message if we are already displaying a more severe one */ if (statusbar->temp_timeout_id && statusbar->temp_severity > severity) return; if (statusbar->temp_timeout_id) g_source_remove (statusbar->temp_timeout_id); statusbar->temp_timeout_id = g_timeout_add (MESSAGE_TIMEOUT, (GSourceFunc) gimp_statusbar_temp_timeout, statusbar); statusbar->temp_severity = severity; gimp_statusbar_add_message (statusbar, statusbar->temp_context_id, icon_name, format, args, /* move_to_front = */ TRUE); if (severity >= GIMP_MESSAGE_WARNING) gimp_widget_blink (statusbar->label); } void gimp_statusbar_pop_temp (GimpStatusbar *statusbar) { g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); if (statusbar->temp_timeout_id) { g_source_remove (statusbar->temp_timeout_id); statusbar->temp_timeout_id = 0; gimp_statusbar_remove_message (statusbar, statusbar->temp_context_id); } } void gimp_statusbar_update_cursor (GimpStatusbar *statusbar, GimpCursorPrecision precision, gdouble x, gdouble y) { GimpDisplayShell *shell; GimpImage *image; gchar buffer[CURSOR_LEN]; g_return_if_fail (GIMP_IS_STATUSBAR (statusbar)); shell = statusbar->shell; image = gimp_display_get_image (shell->display); if (! image || x < 0 || y < 0 || x >= gimp_image_get_width (image) || y >= gimp_image_get_height (image)) { gtk_widget_set_sensitive (statusbar->cursor_label, FALSE); } else { gtk_widget_set_sensitive (statusbar->cursor_label, TRUE); } switch (precision) { case GIMP_CURSOR_PRECISION_PIXEL_CENTER: x = (gint) x; y = (gint) y; break; case GIMP_CURSOR_PRECISION_PIXEL_BORDER: x = RINT (x); y = RINT (y); break; case GIMP_CURSOR_PRECISION_SUBPIXEL: break; } if (shell->unit == GIMP_UNIT_PIXEL) { if (precision == GIMP_CURSOR_PRECISION_SUBPIXEL) { g_snprintf (buffer, sizeof (buffer), statusbar->cursor_format_str_f, "", x, ", ", y, ""); } else { g_snprintf (buffer, sizeof (buffer), statusbar->cursor_format_str, "", (gint) RINT (x), ", ", (gint) RINT (y), ""); } } else /* show real world units */ { GtkTreeModel *model; GimpUnitStore *store; model = gtk_combo_box_get_model (GTK_COMBO_BOX (statusbar->unit_combo)); store = GIMP_UNIT_STORE (model); gimp_unit_store_set_pixel_values (store, x, y); gimp_unit_store_get_values (store, shell->unit, &x, &y); g_snprintf (buffer, sizeof (buffer), statusbar->cursor_format_str, "", x, ", ", y, ""); } gtk_label_set_text (GTK_LABEL (statusbar->cursor_label), buffer); } void gimp_statusbar_clear_cursor (GimpStatusbar *statusbar) { gtk_label_set_text (GTK_LABEL (statusbar->cursor_label), ""); gtk_widget_set_sensitive (statusbar->cursor_label, TRUE); } /* private functions */ static gboolean gimp_statusbar_label_expose (GtkWidget *widget, GdkEventExpose *event, GimpStatusbar *statusbar) { if (statusbar->icon) { cairo_t *cr; PangoRectangle rect; gint x, y; cr = gdk_cairo_create (event->window); gdk_cairo_region (cr, event->region); cairo_clip (cr); gtk_label_get_layout_offsets (GTK_LABEL (widget), &x, &y); pango_layout_index_to_pos (gtk_label_get_layout (GTK_LABEL (widget)), 0, &rect); /* the rectangle width is negative when rendering right-to-left */ x += PANGO_PIXELS (rect.x) + (rect.width < 0 ? PANGO_PIXELS (rect.width) : 0); y += PANGO_PIXELS (rect.y / ICON_SIZE); gdk_cairo_set_source_pixbuf (cr, statusbar->icon, x, y); cairo_paint (cr); cairo_destroy (cr); } return FALSE; } static void gimp_statusbar_shell_scaled (GimpDisplayShell *shell, GimpStatusbar *statusbar) { static PangoLayout *layout = NULL; GimpImage *image = gimp_display_get_image (shell->display); GtkTreeModel *model; const gchar *text; gint image_width; gint image_height; gdouble image_xres; gdouble image_yres; gint width; if (image) { image_width = gimp_image_get_width (image); image_height = gimp_image_get_height (image); gimp_image_get_resolution (image, &image_xres, &image_yres); } else { image_width = shell->disp_width; image_height = shell->disp_height; image_xres = shell->display->config->monitor_xres; image_yres = shell->display->config->monitor_yres; } g_signal_handlers_block_by_func (statusbar->scale_combo, gimp_statusbar_scale_changed, statusbar); gimp_scale_combo_box_set_scale (GIMP_SCALE_COMBO_BOX (statusbar->scale_combo), gimp_zoom_model_get_factor (shell->zoom)); g_signal_handlers_unblock_by_func (statusbar->scale_combo, gimp_statusbar_scale_changed, statusbar); model = gtk_combo_box_get_model (GTK_COMBO_BOX (statusbar->unit_combo)); gimp_unit_store_set_resolutions (GIMP_UNIT_STORE (model), image_xres, image_yres); g_signal_handlers_block_by_func (statusbar->unit_combo, gimp_statusbar_unit_changed, statusbar); gimp_unit_combo_box_set_active (GIMP_UNIT_COMBO_BOX (statusbar->unit_combo), shell->unit); g_signal_handlers_unblock_by_func (statusbar->unit_combo, gimp_statusbar_unit_changed, statusbar); if (shell->unit == GIMP_UNIT_PIXEL) { g_snprintf (statusbar->cursor_format_str, sizeof (statusbar->cursor_format_str), "%%s%%d%%s%%d%%s"); g_snprintf (statusbar->cursor_format_str_f, sizeof (statusbar->cursor_format_str_f), "%%s%%.1f%%s%%.1f%%s"); g_snprintf (statusbar->length_format_str, sizeof (statusbar->length_format_str), "%%s%%d%%s"); } else /* show real world units */ { gint w_digits; gint h_digits; w_digits = gimp_unit_get_scaled_digits (shell->unit, image_xres); h_digits = gimp_unit_get_scaled_digits (shell->unit, image_yres); g_snprintf (statusbar->cursor_format_str, sizeof (statusbar->cursor_format_str), "%%s%%.%df%%s%%.%df%%s", w_digits, h_digits); strcpy (statusbar->cursor_format_str_f, statusbar->cursor_format_str); g_snprintf (statusbar->length_format_str, sizeof (statusbar->length_format_str), "%%s%%.%df%%s", MAX (w_digits, h_digits)); } gimp_statusbar_update_cursor (statusbar, GIMP_CURSOR_PRECISION_SUBPIXEL, -image_width, -image_height); text = gtk_label_get_text (GTK_LABEL (statusbar->cursor_label)); /* one static layout for all displays should be fine */ if (! layout) layout = gtk_widget_create_pango_layout (statusbar->cursor_label, NULL); pango_layout_set_text (layout, text, -1); pango_layout_get_pixel_size (layout, &width, NULL); gtk_widget_set_size_request (statusbar->cursor_label, width, -1); gimp_statusbar_clear_cursor (statusbar); } static void gimp_statusbar_shell_rotated (GimpDisplayShell *shell, GimpStatusbar *statusbar) { if (shell->rotate_angle != 0.0) { /* Degree symbol U+00B0. There are no spaces between the value and the * unit for angular rotation. */ gchar *text = g_strdup_printf (" %.2f\xC2\xB0", shell->rotate_angle); gtk_label_set_text (GTK_LABEL (statusbar->rotate_label), text); g_free (text); gtk_widget_show (statusbar->rotate_widget); } else { gtk_widget_hide (statusbar->rotate_widget); } if (shell->flip_horizontally) gtk_widget_show (statusbar->horizontal_flip_icon); else gtk_widget_hide (statusbar->horizontal_flip_icon); if (shell->flip_vertically) gtk_widget_show (statusbar->vertical_flip_icon); else gtk_widget_hide (statusbar->vertical_flip_icon); } static void gimp_statusbar_shell_status_notify (GimpDisplayShell *shell, const GParamSpec *pspec, GimpStatusbar *statusbar) { gimp_statusbar_replace (statusbar, "title", NULL, "%s", shell->status); } static void gimp_statusbar_unit_changed (GimpUnitComboBox *combo, GimpStatusbar *statusbar) { gimp_display_shell_set_unit (statusbar->shell, gimp_unit_combo_box_get_active (combo)); } static void gimp_statusbar_scale_changed (GimpScaleComboBox *combo, GimpStatusbar *statusbar) { gimp_display_shell_scale (statusbar->shell, GIMP_ZOOM_TO, gimp_scale_combo_box_get_scale (combo), GIMP_ZOOM_FOCUS_BEST_GUESS); } static void gimp_statusbar_scale_activated (GimpScaleComboBox *combo, GimpStatusbar *statusbar) { gtk_widget_grab_focus (statusbar->shell->canvas); } static gboolean gimp_statusbar_rotate_pressed (GtkWidget *event_box, GdkEvent *event, GimpStatusbar *statusbar) { GimpImageWindow *window = gimp_display_shell_get_window (statusbar->shell); GimpUIManager *manager = gimp_image_window_get_ui_manager (window); gimp_ui_manager_activate_action (manager, "view", "view-rotate-other"); return FALSE; } static gboolean gimp_statusbar_horiz_flip_pressed (GtkWidget *event_box, GdkEvent *event, GimpStatusbar *statusbar) { GimpImageWindow *window = gimp_display_shell_get_window (statusbar->shell); GimpUIManager *manager = gimp_image_window_get_ui_manager (window); gimp_ui_manager_activate_action (manager, "view", "view-flip-horizontally"); return FALSE; } static gboolean gimp_statusbar_vert_flip_pressed (GtkWidget *event_box, GdkEvent *event, GimpStatusbar *statusbar) { GimpImageWindow *window = gimp_display_shell_get_window (statusbar->shell); GimpUIManager *manager = gimp_image_window_get_ui_manager (window); gimp_ui_manager_activate_action (manager, "view", "view-flip-vertically"); return FALSE; } static guint gimp_statusbar_get_context_id (GimpStatusbar *statusbar, const gchar *context) { guint id = GPOINTER_TO_UINT (g_hash_table_lookup (statusbar->context_ids, context)); if (! id) { id = statusbar->seq_context_id++; g_hash_table_insert (statusbar->context_ids, g_strdup (context), GUINT_TO_POINTER (id)); } return id; } static gboolean gimp_statusbar_temp_timeout (GimpStatusbar *statusbar) { gimp_statusbar_pop_temp (statusbar); return FALSE; } static void gimp_statusbar_add_message (GimpStatusbar *statusbar, guint context_id, const gchar *icon_name, const gchar *format, va_list args, gboolean move_to_front) { gchar *message; GSList *list; GimpStatusbarMsg *msg; gint position; message = gimp_statusbar_vprintf (format, args); for (list = statusbar->messages; list; list = g_slist_next (list)) { msg = list->data; if (msg->context_id == context_id) { gboolean is_front_message = (list == statusbar->messages); if ((is_front_message || ! move_to_front) && strcmp (msg->text, message) == 0 && g_strcmp0 (msg->icon_name, icon_name) == 0) { g_free (message); return; } if (move_to_front) { statusbar->messages = g_slist_remove (statusbar->messages, msg); gimp_statusbar_msg_free (msg); break; } else { g_free (msg->icon_name); msg->icon_name = g_strdup (icon_name); g_free (msg->text); msg->text = message; if (is_front_message) gimp_statusbar_update (statusbar); return; } } } msg = g_slice_new (GimpStatusbarMsg); msg->context_id = context_id; msg->icon_name = g_strdup (icon_name); msg->text = message; /* find the position at which to insert the new message */ position = 0; /* progress messages are always at the front of the list */ if (! (statusbar->progress_active && context_id == gimp_statusbar_get_context_id (statusbar, "progress"))) { if (statusbar->progress_active) position++; /* temporary messages are in front of all other non-progress messages */ if (statusbar->temp_timeout_id && context_id != statusbar->temp_context_id) position++; } statusbar->messages = g_slist_insert (statusbar->messages, msg, position); if (position == 0) gimp_statusbar_update (statusbar); } static void gimp_statusbar_remove_message (GimpStatusbar *statusbar, guint context_id) { GSList *list; gboolean needs_update = FALSE; for (list = statusbar->messages; list; list = g_slist_next (list)) { GimpStatusbarMsg *msg = list->data; if (msg->context_id == context_id) { needs_update = (list == statusbar->messages); statusbar->messages = g_slist_remove (statusbar->messages, msg); gimp_statusbar_msg_free (msg); break; } } if (needs_update) gimp_statusbar_update (statusbar); } static void gimp_statusbar_msg_free (GimpStatusbarMsg *msg) { g_free (msg->icon_name); g_free (msg->text); g_slice_free (GimpStatusbarMsg, msg); } static gchar * gimp_statusbar_vprintf (const gchar *format, va_list args) { gchar *message; gchar *newline; message = g_strdup_vprintf (format, args); /* guard us from multi-line strings */ newline = strchr (message, '\r'); if (newline) *newline = '\0'; newline = strchr (message, '\n'); if (newline) *newline = '\0'; return message; } static GdkPixbuf * gimp_statusbar_load_icon (GimpStatusbar *statusbar, const gchar *icon_name) { GdkPixbuf *icon; if (G_UNLIKELY (! statusbar->icon_hash)) { statusbar->icon_hash = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) g_object_unref); } icon = g_hash_table_lookup (statusbar->icon_hash, icon_name); if (icon) return g_object_ref (icon); icon = gimp_widget_load_icon (statusbar->label, icon_name, ICON_SIZE); /* this is not optimal but so what */ if (g_hash_table_size (statusbar->icon_hash) > 16) g_hash_table_remove_all (statusbar->icon_hash); g_hash_table_insert (statusbar->icon_hash, g_strdup (icon_name), g_object_ref (icon)); return icon; }