1 /* GIMP - The GNU Image Manipulation Program Copyright (C) 1995
2  * Spencer Kimball and Peter Mattis
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
16  */
17 
18 #include "config.h"
19 
20 #include <string.h>
21 
22 #include <gegl.h>
23 #include <gtk/gtk.h>
24 
25 #include "libgimpbase/gimpbase.h"
26 #include "libgimpmath/gimpmath.h"
27 #include "libgimpwidgets/gimpwidgets.h"
28 
29 #include "display-types.h"
30 
31 #include "config/gimpdisplayconfig.h"
32 
33 #include "core/gimpimage.h"
34 #include "core/gimpprogress.h"
35 
36 #include "widgets/gimpuimanager.h"
37 #include "widgets/gimpwidgets-utils.h"
38 
39 #include "gimpdisplay.h"
40 #include "gimpdisplayshell.h"
41 #include "gimpdisplayshell-scale.h"
42 #include "gimpimagewindow.h"
43 #include "gimpscalecombobox.h"
44 #include "gimpstatusbar.h"
45 
46 #include "gimp-intl.h"
47 
48 
49 /*  maximal width of the string holding the cursor-coordinates   */
50 #define CURSOR_LEN                      256
51 
52 /*  the spacing of the hbox                                      */
53 #define HBOX_SPACING                      1
54 
55 /*  spacing between the icon and the statusbar label             */
56 #define ICON_SPACING                      2
57 
58 /*  timeout (in milliseconds) for temporary statusbar messages   */
59 #define MESSAGE_TIMEOUT                8000
60 
61 /*  minimal interval (in microseconds) between progress updates  */
62 #define MIN_PROGRESS_UPDATE_INTERVAL  50000
63 
64 
65 typedef struct _GimpStatusbarMsg GimpStatusbarMsg;
66 
67 struct _GimpStatusbarMsg
68 {
69   guint  context_id;
70   gchar *icon_name;
71   gchar *text;
72 };
73 
74 
75 static void     gimp_statusbar_progress_iface_init (GimpProgressInterface *iface);
76 
77 static void     gimp_statusbar_dispose            (GObject           *object);
78 static void     gimp_statusbar_finalize           (GObject           *object);
79 
80 static void     gimp_statusbar_screen_changed     (GtkWidget         *widget,
81                                                    GdkScreen         *previous);
82 static void     gimp_statusbar_style_set          (GtkWidget         *widget,
83                                                    GtkStyle          *prev_style);
84 
85 static void     gimp_statusbar_hbox_size_request  (GtkWidget         *widget,
86                                                    GtkRequisition    *requisition,
87                                                    GimpStatusbar     *statusbar);
88 
89 static GimpProgress *
90                 gimp_statusbar_progress_start     (GimpProgress      *progress,
91                                                    gboolean           cancellable,
92                                                    const gchar       *message);
93 static void     gimp_statusbar_progress_end       (GimpProgress      *progress);
94 static gboolean gimp_statusbar_progress_is_active (GimpProgress      *progress);
95 static void     gimp_statusbar_progress_set_text  (GimpProgress      *progress,
96                                                    const gchar       *message);
97 static void     gimp_statusbar_progress_set_value (GimpProgress      *progress,
98                                                    gdouble            percentage);
99 static gdouble  gimp_statusbar_progress_get_value (GimpProgress      *progress);
100 static void     gimp_statusbar_progress_pulse     (GimpProgress      *progress);
101 static gboolean gimp_statusbar_progress_message   (GimpProgress      *progress,
102                                                    Gimp              *gimp,
103                                                    GimpMessageSeverity severity,
104                                                    const gchar       *domain,
105                                                    const gchar       *message);
106 static void     gimp_statusbar_progress_canceled  (GtkWidget         *button,
107                                                    GimpStatusbar     *statusbar);
108 
109 static gboolean gimp_statusbar_label_expose       (GtkWidget         *widget,
110                                                    GdkEventExpose    *event,
111                                                    GimpStatusbar     *statusbar);
112 
113 static void     gimp_statusbar_update             (GimpStatusbar     *statusbar);
114 static void     gimp_statusbar_unit_changed       (GimpUnitComboBox  *combo,
115                                                    GimpStatusbar     *statusbar);
116 static void     gimp_statusbar_scale_changed      (GimpScaleComboBox *combo,
117                                                    GimpStatusbar     *statusbar);
118 static void     gimp_statusbar_scale_activated    (GimpScaleComboBox *combo,
119                                                    GimpStatusbar     *statusbar);
120 static gboolean gimp_statusbar_rotate_pressed     (GtkWidget         *event_box,
121                                                    GdkEvent          *event,
122                                                    GimpStatusbar     *statusbar);
123 static gboolean gimp_statusbar_horiz_flip_pressed (GtkWidget         *event_box,
124                                                    GdkEvent          *event,
125                                                    GimpStatusbar     *statusbar);
126 static gboolean gimp_statusbar_vert_flip_pressed  (GtkWidget         *event_box,
127                                                    GdkEvent          *event,
128                                                    GimpStatusbar     *statusbar);
129 static void     gimp_statusbar_shell_scaled       (GimpDisplayShell  *shell,
130                                                    GimpStatusbar     *statusbar);
131 static void     gimp_statusbar_shell_rotated      (GimpDisplayShell  *shell,
132                                                    GimpStatusbar     *statusbar);
133 static void     gimp_statusbar_shell_status_notify(GimpDisplayShell  *shell,
134                                                    const GParamSpec  *pspec,
135                                                    GimpStatusbar     *statusbar);
136 static guint    gimp_statusbar_get_context_id     (GimpStatusbar     *statusbar,
137                                                    const gchar       *context);
138 static gboolean gimp_statusbar_temp_timeout       (GimpStatusbar     *statusbar);
139 
140 static void     gimp_statusbar_add_message        (GimpStatusbar     *statusbar,
141                                                    guint              context_id,
142                                                    const gchar       *icon_name,
143                                                    const gchar       *format,
144                                                    va_list            args,
145                                                    gboolean           move_to_front) G_GNUC_PRINTF (4, 0);
146 static void     gimp_statusbar_remove_message     (GimpStatusbar     *statusbar,
147                                                    guint              context_id);
148 static void     gimp_statusbar_msg_free           (GimpStatusbarMsg  *msg);
149 
150 static gchar *  gimp_statusbar_vprintf            (const gchar       *format,
151                                                    va_list            args) G_GNUC_PRINTF (1, 0);
152 
153 static GdkPixbuf * gimp_statusbar_load_icon       (GimpStatusbar     *statusbar,
154                                                    const gchar       *icon_name);
155 
156 
G_DEFINE_TYPE_WITH_CODE(GimpStatusbar,gimp_statusbar,GTK_TYPE_STATUSBAR,G_IMPLEMENT_INTERFACE (GIMP_TYPE_PROGRESS,gimp_statusbar_progress_iface_init))157 G_DEFINE_TYPE_WITH_CODE (GimpStatusbar, gimp_statusbar, GTK_TYPE_STATUSBAR,
158                          G_IMPLEMENT_INTERFACE (GIMP_TYPE_PROGRESS,
159                                                 gimp_statusbar_progress_iface_init))
160 
161 #define parent_class gimp_statusbar_parent_class
162 
163 
164 static void
165 gimp_statusbar_class_init (GimpStatusbarClass *klass)
166 {
167   GObjectClass   *object_class = G_OBJECT_CLASS (klass);
168   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
169 
170   object_class->dispose        = gimp_statusbar_dispose;
171   object_class->finalize       = gimp_statusbar_finalize;
172 
173   widget_class->screen_changed = gimp_statusbar_screen_changed;
174   widget_class->style_set      = gimp_statusbar_style_set;
175 }
176 
177 static void
gimp_statusbar_progress_iface_init(GimpProgressInterface * iface)178 gimp_statusbar_progress_iface_init (GimpProgressInterface *iface)
179 {
180   iface->start     = gimp_statusbar_progress_start;
181   iface->end       = gimp_statusbar_progress_end;
182   iface->is_active = gimp_statusbar_progress_is_active;
183   iface->set_text  = gimp_statusbar_progress_set_text;
184   iface->set_value = gimp_statusbar_progress_set_value;
185   iface->get_value = gimp_statusbar_progress_get_value;
186   iface->pulse     = gimp_statusbar_progress_pulse;
187   iface->message   = gimp_statusbar_progress_message;
188 }
189 
190 static void
gimp_statusbar_init(GimpStatusbar * statusbar)191 gimp_statusbar_init (GimpStatusbar *statusbar)
192 {
193   GtkWidget     *hbox;
194   GtkWidget     *hbox2;
195   GtkWidget     *image;
196   GtkWidget     *label;
197   GimpUnitStore *store;
198   GList         *children;
199 
200   statusbar->shell          = NULL;
201   statusbar->messages       = NULL;
202   statusbar->context_ids    = g_hash_table_new_full (g_str_hash, g_str_equal,
203                                                      g_free, NULL);
204   statusbar->seq_context_id = 1;
205 
206   statusbar->temp_context_id =
207     gimp_statusbar_get_context_id (statusbar, "gimp-statusbar-temp");
208 
209   statusbar->cursor_format_str[0]   = '\0';
210   statusbar->cursor_format_str_f[0] = '\0';
211   statusbar->length_format_str[0]   = '\0';
212 
213   statusbar->progress_active      = FALSE;
214   statusbar->progress_shown       = FALSE;
215 
216   /*  remove the message label from the message area  */
217   hbox = gtk_statusbar_get_message_area (GTK_STATUSBAR (statusbar));
218   gtk_box_set_spacing (GTK_BOX (hbox), HBOX_SPACING);
219 
220   children = gtk_container_get_children (GTK_CONTAINER (hbox));
221   statusbar->label = g_object_ref (children->data);
222   g_list_free (children);
223 
224   gtk_container_remove (GTK_CONTAINER (hbox), statusbar->label);
225 
226   g_signal_connect (hbox, "size-request",
227                     G_CALLBACK (gimp_statusbar_hbox_size_request),
228                     statusbar);
229 
230   statusbar->cursor_label = gtk_label_new ("8888, 8888");
231   gtk_box_pack_start (GTK_BOX (hbox), statusbar->cursor_label, FALSE, FALSE, 0);
232   gtk_widget_show (statusbar->cursor_label);
233 
234   store = gimp_unit_store_new (2);
235   statusbar->unit_combo = gimp_unit_combo_box_new_with_model (store);
236   g_object_unref (store);
237 
238   /* see issue #2642 */
239   gtk_combo_box_set_wrap_width (GTK_COMBO_BOX (statusbar->unit_combo), 1);
240 
241   gtk_widget_set_can_focus (statusbar->unit_combo, FALSE);
242   g_object_set (statusbar->unit_combo, "focus-on-click", FALSE, NULL);
243   gtk_box_pack_start (GTK_BOX (hbox), statusbar->unit_combo, FALSE, FALSE, 0);
244   gtk_widget_show (statusbar->unit_combo);
245 
246   g_signal_connect (statusbar->unit_combo, "changed",
247                     G_CALLBACK (gimp_statusbar_unit_changed),
248                     statusbar);
249 
250   statusbar->scale_combo = gimp_scale_combo_box_new ();
251   gtk_widget_set_can_focus (statusbar->scale_combo, FALSE);
252   g_object_set (statusbar->scale_combo, "focus-on-click", FALSE, NULL);
253   gtk_box_pack_start (GTK_BOX (hbox), statusbar->scale_combo, FALSE, FALSE, 0);
254   gtk_widget_show (statusbar->scale_combo);
255 
256   g_signal_connect (statusbar->scale_combo, "changed",
257                     G_CALLBACK (gimp_statusbar_scale_changed),
258                     statusbar);
259 
260   g_signal_connect (statusbar->scale_combo, "entry-activated",
261                     G_CALLBACK (gimp_statusbar_scale_activated),
262                     statusbar);
263 
264   /* Shell transform status */
265   statusbar->rotate_widget = gtk_event_box_new ();
266   gtk_box_pack_start (GTK_BOX (hbox), statusbar->rotate_widget,
267                       FALSE, FALSE, 1);
268   gtk_widget_show (statusbar->rotate_widget);
269 
270   statusbar->rotate_label = gtk_label_new (NULL);
271   gtk_container_add (GTK_CONTAINER (statusbar->rotate_widget),
272                      statusbar->rotate_label);
273   gtk_widget_show (statusbar->rotate_label);
274 
275   g_signal_connect (statusbar->rotate_widget, "button-press-event",
276                     G_CALLBACK (gimp_statusbar_rotate_pressed),
277                     statusbar);
278 
279   statusbar->horizontal_flip_icon = gtk_event_box_new ();
280   gtk_box_pack_start (GTK_BOX (hbox), statusbar->horizontal_flip_icon,
281                       FALSE, FALSE, 1);
282   gtk_widget_show (statusbar->horizontal_flip_icon);
283 
284   image = gtk_image_new_from_icon_name ("gimp-flip-horizontal",
285                                         GTK_ICON_SIZE_MENU);
286   gtk_container_add (GTK_CONTAINER (statusbar->horizontal_flip_icon), image);
287   gtk_widget_show (image);
288 
289   g_signal_connect (statusbar->horizontal_flip_icon, "button-press-event",
290                     G_CALLBACK (gimp_statusbar_horiz_flip_pressed),
291                     statusbar);
292 
293   statusbar->vertical_flip_icon = gtk_event_box_new ();
294   gtk_box_pack_start (GTK_BOX (hbox), statusbar->vertical_flip_icon,
295                       FALSE, FALSE, 1);
296   gtk_widget_show (statusbar->vertical_flip_icon);
297 
298   image = gtk_image_new_from_icon_name ("gimp-flip-vertical",
299                                         GTK_ICON_SIZE_MENU);
300   gtk_container_add (GTK_CONTAINER (statusbar->vertical_flip_icon), image);
301   gtk_widget_show (image);
302 
303   g_signal_connect (statusbar->vertical_flip_icon, "button-press-event",
304                     G_CALLBACK (gimp_statusbar_vert_flip_pressed),
305                     statusbar);
306 
307   /*  put the label back into the message area  */
308   gtk_box_pack_start (GTK_BOX (hbox), statusbar->label, TRUE, TRUE, 1);
309 
310   g_object_unref (statusbar->label);
311 
312   g_signal_connect_after (statusbar->label, "expose-event",
313                           G_CALLBACK (gimp_statusbar_label_expose),
314                           statusbar);
315 
316   statusbar->progressbar = g_object_new (GTK_TYPE_PROGRESS_BAR,
317                                          "text-xalign", 0.0,
318                                          "text-yalign", 0.5,
319                                          "ellipsize",   PANGO_ELLIPSIZE_END,
320                                          NULL);
321   gtk_box_pack_start (GTK_BOX (hbox), statusbar->progressbar, TRUE, TRUE, 0);
322   /*  don't show the progress bar  */
323 
324   /*  construct the cancel button's contents manually because we
325    *  always want image and label regardless of settings, and we want
326    *  a menu size image.
327    */
328   statusbar->cancel_button = gtk_button_new ();
329   gtk_widget_set_can_focus (statusbar->cancel_button, FALSE);
330   gtk_button_set_relief (GTK_BUTTON (statusbar->cancel_button),
331                          GTK_RELIEF_NONE);
332   gtk_widget_set_sensitive (statusbar->cancel_button, FALSE);
333   gtk_box_pack_end (GTK_BOX (hbox),
334                     statusbar->cancel_button, FALSE, FALSE, 0);
335   /*  don't show the cancel button  */
336 
337   hbox2 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
338   gtk_container_add (GTK_CONTAINER (statusbar->cancel_button), hbox2);
339   gtk_widget_show (hbox2);
340 
341   image = gtk_image_new_from_icon_name ("gtk-cancel", GTK_ICON_SIZE_MENU);
342   gtk_box_pack_start (GTK_BOX (hbox2), image, FALSE, FALSE, 2);
343   gtk_widget_show (image);
344 
345   label = gtk_label_new ("Cancel");
346   gtk_box_pack_start (GTK_BOX (hbox2), label, FALSE, FALSE, 2);
347   gtk_widget_show (label);
348 
349   g_signal_connect (statusbar->cancel_button, "clicked",
350                     G_CALLBACK (gimp_statusbar_progress_canceled),
351                     statusbar);
352 }
353 
354 static void
gimp_statusbar_dispose(GObject * object)355 gimp_statusbar_dispose (GObject *object)
356 {
357   GimpStatusbar *statusbar = GIMP_STATUSBAR (object);
358 
359   if (statusbar->temp_timeout_id)
360     {
361       g_source_remove (statusbar->temp_timeout_id);
362       statusbar->temp_timeout_id = 0;
363     }
364 
365   G_OBJECT_CLASS (parent_class)->dispose (object);
366 }
367 
368 static void
gimp_statusbar_finalize(GObject * object)369 gimp_statusbar_finalize (GObject *object)
370 {
371   GimpStatusbar *statusbar = GIMP_STATUSBAR (object);
372 
373   g_clear_object (&statusbar->icon);
374   g_clear_pointer (&statusbar->icon_hash, g_hash_table_unref);
375 
376   g_slist_free_full (statusbar->messages,
377                      (GDestroyNotify) gimp_statusbar_msg_free);
378   statusbar->messages = NULL;
379 
380   g_clear_pointer (&statusbar->context_ids, g_hash_table_destroy);
381 
382   G_OBJECT_CLASS (parent_class)->finalize (object);
383 }
384 
385 static void
gimp_statusbar_screen_changed(GtkWidget * widget,GdkScreen * previous)386 gimp_statusbar_screen_changed (GtkWidget *widget,
387                                GdkScreen *previous)
388 {
389   GimpStatusbar *statusbar = GIMP_STATUSBAR (widget);
390 
391   if (GTK_WIDGET_CLASS (parent_class)->screen_changed)
392     GTK_WIDGET_CLASS (parent_class)->screen_changed (widget, previous);
393 
394   g_clear_pointer (&statusbar->icon_hash, g_hash_table_unref);
395 }
396 
397 static void
gimp_statusbar_style_set(GtkWidget * widget,GtkStyle * prev_style)398 gimp_statusbar_style_set (GtkWidget *widget,
399                           GtkStyle  *prev_style)
400 {
401   GimpStatusbar *statusbar = GIMP_STATUSBAR (widget);
402 
403   GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style);
404 
405   g_clear_pointer (&statusbar->icon_hash, g_hash_table_unref);
406 }
407 
408 static void
gimp_statusbar_hbox_size_request(GtkWidget * widget,GtkRequisition * requisition,GimpStatusbar * statusbar)409 gimp_statusbar_hbox_size_request (GtkWidget      *widget,
410                                   GtkRequisition *requisition,
411                                   GimpStatusbar  *statusbar)
412 {
413   GtkRequisition child_requisition;
414   gint           width = 0;
415 
416   /*  also consider the children which can be invisible  */
417 
418   gtk_widget_size_request (statusbar->cursor_label, &child_requisition);
419   width += child_requisition.width;
420   requisition->height = MAX (requisition->height,
421                              child_requisition.height);
422 
423   gtk_widget_size_request (statusbar->unit_combo, &child_requisition);
424   width += child_requisition.width;
425   requisition->height = MAX (requisition->height,
426                              child_requisition.height);
427 
428   gtk_widget_size_request (statusbar->scale_combo, &child_requisition);
429   width += child_requisition.width;
430   requisition->height = MAX (requisition->height,
431                              child_requisition.height);
432 
433   gtk_widget_size_request (statusbar->progressbar, &child_requisition);
434   requisition->height = MAX (requisition->height,
435                              child_requisition.height);
436 
437   gtk_widget_size_request (statusbar->label, &child_requisition);
438   requisition->height = MAX (requisition->height,
439                              child_requisition.height);
440 
441   gtk_widget_size_request (statusbar->cancel_button, &child_requisition);
442   requisition->height = MAX (requisition->height,
443                              child_requisition.height);
444 
445   requisition->width = MAX (requisition->width, width + 32);
446 }
447 
448 static GimpProgress *
gimp_statusbar_progress_start(GimpProgress * progress,gboolean cancellable,const gchar * message)449 gimp_statusbar_progress_start (GimpProgress *progress,
450                                gboolean      cancellable,
451                                const gchar  *message)
452 {
453   GimpStatusbar *statusbar = GIMP_STATUSBAR (progress);
454 
455   if (! statusbar->progress_active)
456     {
457       GtkWidget     *bar = statusbar->progressbar;
458       GtkAllocation  allocation;
459 
460       statusbar->progress_active           = TRUE;
461       statusbar->progress_value            = 0.0;
462       statusbar->progress_last_update_time = g_get_monotonic_time ();
463 
464       gimp_statusbar_push (statusbar, "progress", NULL, "%s", message);
465       gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (bar), 0.0);
466       gtk_widget_set_sensitive (statusbar->cancel_button, cancellable);
467 
468       if (cancellable)
469         {
470           if (message)
471             {
472               gchar *tooltip = g_strdup_printf (_("Cancel <i>%s</i>"), message);
473 
474               gimp_help_set_help_data_with_markup (statusbar->cancel_button,
475                                                    tooltip, NULL);
476               g_free (tooltip);
477             }
478 
479           gtk_widget_show (statusbar->cancel_button);
480         }
481 
482       gtk_widget_get_allocation (statusbar->label, &allocation);
483 
484       gtk_widget_show (statusbar->progressbar);
485       gtk_widget_hide (statusbar->label);
486 
487       /*  This shit is needed so that the progress bar is drawn in the
488        *  correct place in the cases where we suck completely and run
489        *  an operation that blocks the GUI and doesn't let the main
490        *  loop run.
491        */
492       gtk_container_resize_children (GTK_CONTAINER (statusbar));
493       gtk_widget_size_allocate (statusbar->progressbar, &allocation);
494 
495       if (! gtk_widget_get_visible (GTK_WIDGET (statusbar)))
496         {
497           gtk_widget_show (GTK_WIDGET (statusbar));
498           statusbar->progress_shown = TRUE;
499         }
500 
501       gimp_widget_flush_expose (bar);
502 
503       gimp_statusbar_override_window_title (statusbar);
504 
505       return progress;
506     }
507 
508   return NULL;
509 }
510 
511 static void
gimp_statusbar_progress_end(GimpProgress * progress)512 gimp_statusbar_progress_end (GimpProgress *progress)
513 {
514   GimpStatusbar *statusbar = GIMP_STATUSBAR (progress);
515 
516   if (statusbar->progress_active)
517     {
518       GtkWidget *bar = statusbar->progressbar;
519 
520       if (statusbar->progress_shown)
521         {
522           gtk_widget_hide (GTK_WIDGET (statusbar));
523           statusbar->progress_shown = FALSE;
524         }
525 
526       statusbar->progress_active = FALSE;
527       statusbar->progress_value  = 0.0;
528 
529       gtk_widget_hide (bar);
530       gtk_widget_show (statusbar->label);
531 
532       gimp_statusbar_pop (statusbar, "progress");
533 
534       gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (bar), 0.0);
535       gtk_widget_set_sensitive (statusbar->cancel_button, FALSE);
536       gtk_widget_hide (statusbar->cancel_button);
537 
538       gimp_statusbar_restore_window_title (statusbar);
539     }
540 }
541 
542 static gboolean
gimp_statusbar_progress_is_active(GimpProgress * progress)543 gimp_statusbar_progress_is_active (GimpProgress *progress)
544 {
545   GimpStatusbar *statusbar = GIMP_STATUSBAR (progress);
546 
547   return statusbar->progress_active;
548 }
549 
550 static void
gimp_statusbar_progress_set_text(GimpProgress * progress,const gchar * message)551 gimp_statusbar_progress_set_text (GimpProgress *progress,
552                                   const gchar  *message)
553 {
554   GimpStatusbar *statusbar = GIMP_STATUSBAR (progress);
555 
556   if (statusbar->progress_active)
557     {
558       GtkWidget *bar = statusbar->progressbar;
559 
560       gimp_statusbar_replace (statusbar, "progress", NULL, "%s", message);
561 
562       gimp_widget_flush_expose (bar);
563 
564       gimp_statusbar_override_window_title (statusbar);
565     }
566 }
567 
568 static void
gimp_statusbar_progress_set_value(GimpProgress * progress,gdouble percentage)569 gimp_statusbar_progress_set_value (GimpProgress *progress,
570                                    gdouble       percentage)
571 {
572   GimpStatusbar *statusbar = GIMP_STATUSBAR (progress);
573 
574   if (statusbar->progress_active)
575     {
576       guint64 time = g_get_monotonic_time ();
577 
578       if (time - statusbar->progress_last_update_time >=
579           MIN_PROGRESS_UPDATE_INTERVAL)
580         {
581           GtkWidget     *bar = statusbar->progressbar;
582           GtkAllocation  allocation;
583           gdouble        diff;
584 
585           gtk_widget_get_allocation (bar, &allocation);
586 
587           statusbar->progress_value = percentage;
588 
589           diff = fabs (percentage -
590                        gtk_progress_bar_get_fraction (GTK_PROGRESS_BAR (bar)));
591 
592           /* only update the progress bar if this causes a visible change */
593           if (allocation.width * diff >= 1.0)
594             {
595               statusbar->progress_last_update_time = time;
596 
597               gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (bar),
598                                              percentage);
599 
600               gimp_widget_flush_expose (bar);
601             }
602         }
603     }
604 }
605 
606 static gdouble
gimp_statusbar_progress_get_value(GimpProgress * progress)607 gimp_statusbar_progress_get_value (GimpProgress *progress)
608 {
609   GimpStatusbar *statusbar = GIMP_STATUSBAR (progress);
610 
611   if (statusbar->progress_active)
612     return statusbar->progress_value;
613 
614   return 0.0;
615 }
616 
617 static void
gimp_statusbar_progress_pulse(GimpProgress * progress)618 gimp_statusbar_progress_pulse (GimpProgress *progress)
619 {
620   GimpStatusbar *statusbar = GIMP_STATUSBAR (progress);
621 
622   if (statusbar->progress_active)
623     {
624       guint64 time = g_get_monotonic_time ();
625 
626       if (time - statusbar->progress_last_update_time >=
627           MIN_PROGRESS_UPDATE_INTERVAL)
628         {
629           GtkWidget *bar = statusbar->progressbar;
630 
631           statusbar->progress_last_update_time = time;
632 
633           gtk_progress_bar_pulse (GTK_PROGRESS_BAR (bar));
634 
635           gimp_widget_flush_expose (bar);
636         }
637     }
638 }
639 
640 static gboolean
gimp_statusbar_progress_message(GimpProgress * progress,Gimp * gimp,GimpMessageSeverity severity,const gchar * domain,const gchar * message)641 gimp_statusbar_progress_message (GimpProgress        *progress,
642                                  Gimp                *gimp,
643                                  GimpMessageSeverity  severity,
644                                  const gchar         *domain,
645                                  const gchar         *message)
646 {
647   GimpStatusbar *statusbar  = GIMP_STATUSBAR (progress);
648   PangoLayout   *layout;
649   const gchar   *icon_name;
650   gboolean       handle_msg = FALSE;
651 
652   /*  don't accept a message if we are already displaying a more severe one  */
653   if (statusbar->temp_timeout_id && statusbar->temp_severity > severity)
654     return FALSE;
655 
656   /*  we can only handle short one-liners  */
657   layout = gtk_widget_create_pango_layout (statusbar->label, message);
658 
659   icon_name = gimp_get_message_icon_name (severity);
660 
661   if (pango_layout_get_line_count (layout) == 1)
662     {
663       GtkAllocation label_allocation;
664       gint          width;
665 
666       gtk_widget_get_allocation (statusbar->label, &label_allocation);
667 
668       pango_layout_get_pixel_size (layout, &width, NULL);
669 
670       if (width < label_allocation.width)
671         {
672           if (icon_name)
673             {
674               GdkPixbuf *pixbuf;
675 
676               pixbuf = gimp_statusbar_load_icon (statusbar, icon_name);
677 
678               width += ICON_SPACING + gdk_pixbuf_get_width (pixbuf);
679 
680               g_object_unref (pixbuf);
681 
682               handle_msg = (width < label_allocation.width);
683             }
684           else
685             {
686               handle_msg = TRUE;
687             }
688         }
689     }
690 
691   g_object_unref (layout);
692 
693   if (handle_msg)
694     gimp_statusbar_push_temp (statusbar, severity, icon_name, "%s", message);
695 
696   return handle_msg;
697 }
698 
699 static void
gimp_statusbar_progress_canceled(GtkWidget * button,GimpStatusbar * statusbar)700 gimp_statusbar_progress_canceled (GtkWidget     *button,
701                                   GimpStatusbar *statusbar)
702 {
703   if (statusbar->progress_active)
704     gimp_progress_cancel (GIMP_PROGRESS (statusbar));
705 }
706 
707 static void
gimp_statusbar_set_text(GimpStatusbar * statusbar,const gchar * icon_name,const gchar * text)708 gimp_statusbar_set_text (GimpStatusbar *statusbar,
709                          const gchar   *icon_name,
710                          const gchar   *text)
711 {
712   if (statusbar->progress_active)
713     {
714       gtk_progress_bar_set_text (GTK_PROGRESS_BAR (statusbar->progressbar),
715                                  text);
716     }
717   else
718     {
719       g_clear_object (&statusbar->icon);
720 
721       if (icon_name)
722         statusbar->icon = gimp_statusbar_load_icon (statusbar, icon_name);
723 
724       if (statusbar->icon)
725         {
726           PangoAttrList  *attrs;
727           PangoAttribute *attr;
728           PangoRectangle  rect;
729           gchar          *tmp;
730 
731           tmp = g_strconcat (" ", text, NULL);
732           gtk_label_set_text (GTK_LABEL (statusbar->label), tmp);
733           g_free (tmp);
734 
735           rect.x      = 0;
736           rect.y      = 0;
737           rect.width  = PANGO_SCALE * (gdk_pixbuf_get_width (statusbar->icon) +
738                                        ICON_SPACING);
739           rect.height = 0;
740 
741           attrs = pango_attr_list_new ();
742 
743           attr = pango_attr_shape_new (&rect, &rect);
744           attr->start_index = 0;
745           attr->end_index   = 1;
746           pango_attr_list_insert (attrs, attr);
747 
748           gtk_label_set_attributes (GTK_LABEL (statusbar->label), attrs);
749           pango_attr_list_unref (attrs);
750         }
751       else
752         {
753           gtk_label_set_text (GTK_LABEL (statusbar->label), text);
754           gtk_label_set_attributes (GTK_LABEL (statusbar->label), NULL);
755         }
756     }
757 }
758 
759 static void
gimp_statusbar_update(GimpStatusbar * statusbar)760 gimp_statusbar_update (GimpStatusbar *statusbar)
761 {
762   GimpStatusbarMsg *msg = NULL;
763 
764   if (statusbar->messages)
765     msg = statusbar->messages->data;
766 
767   if (msg && msg->text)
768     {
769       gimp_statusbar_set_text (statusbar, msg->icon_name, msg->text);
770     }
771   else
772     {
773       gimp_statusbar_set_text (statusbar, NULL, "");
774     }
775 }
776 
777 
778 /*  public functions  */
779 
780 GtkWidget *
gimp_statusbar_new(void)781 gimp_statusbar_new (void)
782 {
783   return g_object_new (GIMP_TYPE_STATUSBAR, NULL);
784 }
785 
786 void
gimp_statusbar_set_shell(GimpStatusbar * statusbar,GimpDisplayShell * shell)787 gimp_statusbar_set_shell (GimpStatusbar    *statusbar,
788                           GimpDisplayShell *shell)
789 {
790   g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
791   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
792 
793   if (shell == statusbar->shell)
794     return;
795 
796   if (statusbar->shell)
797     {
798       g_signal_handlers_disconnect_by_func (statusbar->shell,
799                                             gimp_statusbar_shell_scaled,
800                                             statusbar);
801       g_signal_handlers_disconnect_by_func (statusbar->shell,
802                                             gimp_statusbar_shell_rotated,
803                                             statusbar);
804       g_signal_handlers_disconnect_by_func (statusbar->shell,
805                                             gimp_statusbar_shell_status_notify,
806                                             statusbar);
807     }
808 
809   statusbar->shell = shell;
810 
811   g_signal_connect_object (statusbar->shell, "scaled",
812                            G_CALLBACK (gimp_statusbar_shell_scaled),
813                            statusbar, 0);
814   g_signal_connect_object (statusbar->shell, "rotated",
815                            G_CALLBACK (gimp_statusbar_shell_rotated),
816                            statusbar, 0);
817   g_signal_connect_object (statusbar->shell, "notify::status",
818                            G_CALLBACK (gimp_statusbar_shell_status_notify),
819                            statusbar, 0);
820   gimp_statusbar_shell_rotated (shell, statusbar);
821 }
822 
823 gboolean
gimp_statusbar_get_visible(GimpStatusbar * statusbar)824 gimp_statusbar_get_visible (GimpStatusbar *statusbar)
825 {
826   g_return_val_if_fail (GIMP_IS_STATUSBAR (statusbar), FALSE);
827 
828   if (statusbar->progress_shown)
829     return FALSE;
830 
831   return gtk_widget_get_visible (GTK_WIDGET (statusbar));
832 }
833 
834 void
gimp_statusbar_set_visible(GimpStatusbar * statusbar,gboolean visible)835 gimp_statusbar_set_visible (GimpStatusbar *statusbar,
836                             gboolean       visible)
837 {
838   g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
839 
840   if (statusbar->progress_shown)
841     {
842       if (visible)
843         {
844           statusbar->progress_shown = FALSE;
845           return;
846         }
847     }
848 
849   gtk_widget_set_visible (GTK_WIDGET (statusbar), visible);
850 }
851 
852 void
gimp_statusbar_empty(GimpStatusbar * statusbar)853 gimp_statusbar_empty (GimpStatusbar *statusbar)
854 {
855   g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
856 
857   gtk_widget_hide (statusbar->cursor_label);
858   gtk_widget_hide (statusbar->unit_combo);
859   gtk_widget_hide (statusbar->scale_combo);
860   gtk_widget_hide (statusbar->rotate_widget);
861   gtk_widget_hide (statusbar->horizontal_flip_icon);
862   gtk_widget_hide (statusbar->vertical_flip_icon);
863 }
864 
865 void
gimp_statusbar_fill(GimpStatusbar * statusbar)866 gimp_statusbar_fill (GimpStatusbar *statusbar)
867 {
868   g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
869 
870   gtk_widget_show (statusbar->cursor_label);
871   gtk_widget_show (statusbar->unit_combo);
872   gtk_widget_show (statusbar->scale_combo);
873   gtk_widget_show (statusbar->rotate_widget);
874   gimp_statusbar_shell_rotated (statusbar->shell, statusbar);
875 }
876 
877 void
gimp_statusbar_override_window_title(GimpStatusbar * statusbar)878 gimp_statusbar_override_window_title (GimpStatusbar *statusbar)
879 {
880   GtkWidget *toplevel;
881 
882   g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
883 
884   toplevel = gtk_widget_get_toplevel (GTK_WIDGET (statusbar));
885 
886   if (gimp_image_window_is_iconified (GIMP_IMAGE_WINDOW (toplevel)))
887     {
888       const gchar *message = gimp_statusbar_peek (statusbar, "progress");
889 
890       if (message)
891         gtk_window_set_title (GTK_WINDOW (toplevel), message);
892     }
893 }
894 
895 void
gimp_statusbar_restore_window_title(GimpStatusbar * statusbar)896 gimp_statusbar_restore_window_title (GimpStatusbar *statusbar)
897 {
898   GtkWidget *toplevel;
899 
900   g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
901 
902   toplevel = gtk_widget_get_toplevel (GTK_WIDGET (statusbar));
903 
904   if (gimp_image_window_is_iconified (GIMP_IMAGE_WINDOW (toplevel)))
905     {
906       g_object_notify (G_OBJECT (statusbar->shell), "title");
907     }
908 }
909 
910 void
gimp_statusbar_push(GimpStatusbar * statusbar,const gchar * context,const gchar * icon_name,const gchar * format,...)911 gimp_statusbar_push (GimpStatusbar *statusbar,
912                      const gchar   *context,
913                      const gchar   *icon_name,
914                      const gchar   *format,
915                      ...)
916 {
917   va_list args;
918 
919   g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
920   g_return_if_fail (context != NULL);
921   g_return_if_fail (format != NULL);
922 
923   va_start (args, format);
924   gimp_statusbar_push_valist (statusbar, context, icon_name, format, args);
925   va_end (args);
926 }
927 
928 void
gimp_statusbar_push_valist(GimpStatusbar * statusbar,const gchar * context,const gchar * icon_name,const gchar * format,va_list args)929 gimp_statusbar_push_valist (GimpStatusbar *statusbar,
930                             const gchar   *context,
931                             const gchar   *icon_name,
932                             const gchar   *format,
933                             va_list        args)
934 {
935   guint context_id;
936 
937   g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
938   g_return_if_fail (context != NULL);
939   g_return_if_fail (format != NULL);
940 
941   context_id = gimp_statusbar_get_context_id (statusbar, context);
942 
943   gimp_statusbar_add_message (statusbar,
944                               context_id,
945                               icon_name, format, args,
946                               /*  move_to_front =  */ TRUE);
947 }
948 
949 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)950 gimp_statusbar_push_coords (GimpStatusbar       *statusbar,
951                             const gchar         *context,
952                             const gchar         *icon_name,
953                             GimpCursorPrecision  precision,
954                             const gchar         *title,
955                             gdouble              x,
956                             const gchar         *separator,
957                             gdouble              y,
958                             const gchar         *help)
959 {
960   GimpDisplayShell *shell;
961 
962   g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
963   g_return_if_fail (title != NULL);
964   g_return_if_fail (separator != NULL);
965 
966   if (help == NULL)
967     help = "";
968 
969   shell = statusbar->shell;
970 
971   switch (precision)
972     {
973     case GIMP_CURSOR_PRECISION_PIXEL_CENTER:
974       x = (gint) x;
975       y = (gint) y;
976       break;
977 
978     case GIMP_CURSOR_PRECISION_PIXEL_BORDER:
979       x = RINT (x);
980       y = RINT (y);
981       break;
982 
983     case GIMP_CURSOR_PRECISION_SUBPIXEL:
984       break;
985     }
986 
987   if (shell->unit == GIMP_UNIT_PIXEL)
988     {
989       if (precision == GIMP_CURSOR_PRECISION_SUBPIXEL)
990         {
991           gimp_statusbar_push (statusbar, context,
992                                icon_name,
993                                statusbar->cursor_format_str_f,
994                                title,
995                                x,
996                                separator,
997                                y,
998                                help);
999         }
1000       else
1001         {
1002           gimp_statusbar_push (statusbar, context,
1003                                icon_name,
1004                                statusbar->cursor_format_str,
1005                                title,
1006                                (gint) RINT (x),
1007                                separator,
1008                                (gint) RINT (y),
1009                                help);
1010         }
1011     }
1012   else /* show real world units */
1013     {
1014       gdouble xres;
1015       gdouble yres;
1016 
1017       gimp_image_get_resolution (gimp_display_get_image (shell->display),
1018                                  &xres, &yres);
1019 
1020       gimp_statusbar_push (statusbar, context,
1021                            icon_name,
1022                            statusbar->cursor_format_str,
1023                            title,
1024                            gimp_pixels_to_units (x, shell->unit, xres),
1025                            separator,
1026                            gimp_pixels_to_units (y, shell->unit, yres),
1027                            help);
1028     }
1029 }
1030 
1031 void
gimp_statusbar_push_length(GimpStatusbar * statusbar,const gchar * context,const gchar * icon_name,const gchar * title,GimpOrientationType axis,gdouble value,const gchar * help)1032 gimp_statusbar_push_length (GimpStatusbar       *statusbar,
1033                             const gchar         *context,
1034                             const gchar         *icon_name,
1035                             const gchar         *title,
1036                             GimpOrientationType  axis,
1037                             gdouble              value,
1038                             const gchar         *help)
1039 {
1040   GimpDisplayShell *shell;
1041 
1042   g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
1043   g_return_if_fail (title != NULL);
1044 
1045   if (help == NULL)
1046     help = "";
1047 
1048   shell = statusbar->shell;
1049 
1050   if (shell->unit == GIMP_UNIT_PIXEL)
1051     {
1052       gimp_statusbar_push (statusbar, context,
1053                            icon_name,
1054                            statusbar->length_format_str,
1055                            title,
1056                            (gint) RINT (value),
1057                            help);
1058     }
1059   else /* show real world units */
1060     {
1061       gdouble xres;
1062       gdouble yres;
1063       gdouble resolution;
1064 
1065       gimp_image_get_resolution (gimp_display_get_image (shell->display),
1066                                  &xres, &yres);
1067 
1068       switch (axis)
1069         {
1070         case GIMP_ORIENTATION_HORIZONTAL:
1071           resolution = xres;
1072           break;
1073 
1074         case GIMP_ORIENTATION_VERTICAL:
1075           resolution = yres;
1076           break;
1077 
1078         default:
1079           g_return_if_reached ();
1080           break;
1081         }
1082 
1083       gimp_statusbar_push (statusbar, context,
1084                            icon_name,
1085                            statusbar->length_format_str,
1086                            title,
1087                            gimp_pixels_to_units (value, shell->unit, resolution),
1088                            help);
1089     }
1090 }
1091 
1092 void
gimp_statusbar_replace(GimpStatusbar * statusbar,const gchar * context,const gchar * icon_name,const gchar * format,...)1093 gimp_statusbar_replace (GimpStatusbar *statusbar,
1094                         const gchar   *context,
1095                         const gchar   *icon_name,
1096                         const gchar   *format,
1097                         ...)
1098 {
1099   va_list args;
1100 
1101   g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
1102   g_return_if_fail (context != NULL);
1103   g_return_if_fail (format != NULL);
1104 
1105   va_start (args, format);
1106   gimp_statusbar_replace_valist (statusbar, context, icon_name, format, args);
1107   va_end (args);
1108 }
1109 
1110 void
gimp_statusbar_replace_valist(GimpStatusbar * statusbar,const gchar * context,const gchar * icon_name,const gchar * format,va_list args)1111 gimp_statusbar_replace_valist (GimpStatusbar *statusbar,
1112                                const gchar   *context,
1113                                const gchar   *icon_name,
1114                                const gchar   *format,
1115                                va_list        args)
1116 {
1117   guint context_id;
1118 
1119   g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
1120   g_return_if_fail (context != NULL);
1121   g_return_if_fail (format != NULL);
1122 
1123   context_id = gimp_statusbar_get_context_id (statusbar, context);
1124 
1125   gimp_statusbar_add_message (statusbar,
1126                               context_id,
1127                               icon_name, format, args,
1128                               /*  move_to_front =  */ FALSE);
1129 }
1130 
1131 const gchar *
gimp_statusbar_peek(GimpStatusbar * statusbar,const gchar * context)1132 gimp_statusbar_peek (GimpStatusbar *statusbar,
1133                      const gchar   *context)
1134 {
1135   GSList *list;
1136   guint   context_id;
1137 
1138   g_return_val_if_fail (GIMP_IS_STATUSBAR (statusbar), NULL);
1139   g_return_val_if_fail (context != NULL, NULL);
1140 
1141   context_id = gimp_statusbar_get_context_id (statusbar, context);
1142 
1143   for (list = statusbar->messages; list; list = list->next)
1144     {
1145       GimpStatusbarMsg *msg = list->data;
1146 
1147       if (msg->context_id == context_id)
1148         {
1149           return msg->text;
1150         }
1151     }
1152 
1153   return NULL;
1154 }
1155 
1156 void
gimp_statusbar_pop(GimpStatusbar * statusbar,const gchar * context)1157 gimp_statusbar_pop (GimpStatusbar *statusbar,
1158                     const gchar   *context)
1159 {
1160   guint context_id;
1161 
1162   g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
1163   g_return_if_fail (context != NULL);
1164 
1165   context_id = gimp_statusbar_get_context_id (statusbar, context);
1166 
1167   gimp_statusbar_remove_message (statusbar,
1168                                  context_id);
1169 }
1170 
1171 void
gimp_statusbar_push_temp(GimpStatusbar * statusbar,GimpMessageSeverity severity,const gchar * icon_name,const gchar * format,...)1172 gimp_statusbar_push_temp (GimpStatusbar       *statusbar,
1173                           GimpMessageSeverity  severity,
1174                           const gchar         *icon_name,
1175                           const gchar         *format,
1176                           ...)
1177 {
1178   va_list args;
1179 
1180   va_start (args, format);
1181   gimp_statusbar_push_temp_valist (statusbar, severity, icon_name, format, args);
1182   va_end (args);
1183 }
1184 
1185 void
gimp_statusbar_push_temp_valist(GimpStatusbar * statusbar,GimpMessageSeverity severity,const gchar * icon_name,const gchar * format,va_list args)1186 gimp_statusbar_push_temp_valist (GimpStatusbar       *statusbar,
1187                                  GimpMessageSeverity  severity,
1188                                  const gchar         *icon_name,
1189                                  const gchar         *format,
1190                                  va_list              args)
1191 {
1192   g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
1193   g_return_if_fail (severity <= GIMP_MESSAGE_WARNING);
1194   g_return_if_fail (format != NULL);
1195 
1196   /*  don't accept a message if we are already displaying a more severe one  */
1197   if (statusbar->temp_timeout_id && statusbar->temp_severity > severity)
1198     return;
1199 
1200   if (statusbar->temp_timeout_id)
1201     g_source_remove (statusbar->temp_timeout_id);
1202 
1203   statusbar->temp_timeout_id =
1204     g_timeout_add (MESSAGE_TIMEOUT,
1205                    (GSourceFunc) gimp_statusbar_temp_timeout, statusbar);
1206 
1207   statusbar->temp_severity = severity;
1208 
1209   gimp_statusbar_add_message (statusbar,
1210                               statusbar->temp_context_id,
1211                               icon_name, format, args,
1212                               /*  move_to_front =  */ TRUE);
1213 
1214   if (severity >= GIMP_MESSAGE_WARNING)
1215     gimp_widget_blink (statusbar->label);
1216 }
1217 
1218 void
gimp_statusbar_pop_temp(GimpStatusbar * statusbar)1219 gimp_statusbar_pop_temp (GimpStatusbar *statusbar)
1220 {
1221   g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
1222 
1223   if (statusbar->temp_timeout_id)
1224     {
1225       g_source_remove (statusbar->temp_timeout_id);
1226       statusbar->temp_timeout_id = 0;
1227 
1228       gimp_statusbar_remove_message (statusbar,
1229                                      statusbar->temp_context_id);
1230     }
1231 }
1232 
1233 void
gimp_statusbar_update_cursor(GimpStatusbar * statusbar,GimpCursorPrecision precision,gdouble x,gdouble y)1234 gimp_statusbar_update_cursor (GimpStatusbar       *statusbar,
1235                               GimpCursorPrecision  precision,
1236                               gdouble              x,
1237                               gdouble              y)
1238 {
1239   GimpDisplayShell *shell;
1240   GimpImage        *image;
1241   gchar             buffer[CURSOR_LEN];
1242 
1243   g_return_if_fail (GIMP_IS_STATUSBAR (statusbar));
1244 
1245   shell = statusbar->shell;
1246   image = gimp_display_get_image (shell->display);
1247 
1248   if (! image                            ||
1249       x <  0                             ||
1250       y <  0                             ||
1251       x >= gimp_image_get_width  (image) ||
1252       y >= gimp_image_get_height (image))
1253     {
1254       gtk_widget_set_sensitive (statusbar->cursor_label, FALSE);
1255     }
1256   else
1257     {
1258       gtk_widget_set_sensitive (statusbar->cursor_label, TRUE);
1259     }
1260 
1261   switch (precision)
1262     {
1263     case GIMP_CURSOR_PRECISION_PIXEL_CENTER:
1264       x = (gint) x;
1265       y = (gint) y;
1266       break;
1267 
1268     case GIMP_CURSOR_PRECISION_PIXEL_BORDER:
1269       x = RINT (x);
1270       y = RINT (y);
1271       break;
1272 
1273     case GIMP_CURSOR_PRECISION_SUBPIXEL:
1274       break;
1275     }
1276 
1277   if (shell->unit == GIMP_UNIT_PIXEL)
1278     {
1279       if (precision == GIMP_CURSOR_PRECISION_SUBPIXEL)
1280         {
1281           g_snprintf (buffer, sizeof (buffer),
1282                       statusbar->cursor_format_str_f,
1283                       "", x, ", ", y, "");
1284         }
1285       else
1286         {
1287           g_snprintf (buffer, sizeof (buffer),
1288                       statusbar->cursor_format_str,
1289                       "", (gint) RINT (x), ", ", (gint) RINT (y), "");
1290         }
1291     }
1292   else /* show real world units */
1293     {
1294       GtkTreeModel  *model;
1295       GimpUnitStore *store;
1296 
1297       model = gtk_combo_box_get_model (GTK_COMBO_BOX (statusbar->unit_combo));
1298       store = GIMP_UNIT_STORE (model);
1299 
1300       gimp_unit_store_set_pixel_values (store, x, y);
1301       gimp_unit_store_get_values (store, shell->unit, &x, &y);
1302 
1303       g_snprintf (buffer, sizeof (buffer),
1304                   statusbar->cursor_format_str,
1305                   "", x, ", ", y, "");
1306     }
1307 
1308   gtk_label_set_text (GTK_LABEL (statusbar->cursor_label), buffer);
1309 }
1310 
1311 void
gimp_statusbar_clear_cursor(GimpStatusbar * statusbar)1312 gimp_statusbar_clear_cursor (GimpStatusbar *statusbar)
1313 {
1314   gtk_label_set_text (GTK_LABEL (statusbar->cursor_label), "");
1315   gtk_widget_set_sensitive (statusbar->cursor_label, TRUE);
1316 }
1317 
1318 
1319 /*  private functions  */
1320 
1321 static gboolean
gimp_statusbar_label_expose(GtkWidget * widget,GdkEventExpose * event,GimpStatusbar * statusbar)1322 gimp_statusbar_label_expose (GtkWidget      *widget,
1323                              GdkEventExpose *event,
1324                              GimpStatusbar  *statusbar)
1325 {
1326   if (statusbar->icon)
1327     {
1328       cairo_t        *cr;
1329       PangoRectangle  rect;
1330       gint            x, y;
1331 
1332       cr = gdk_cairo_create (event->window);
1333 
1334       gdk_cairo_region (cr, event->region);
1335       cairo_clip (cr);
1336 
1337       gtk_label_get_layout_offsets (GTK_LABEL (widget), &x, &y);
1338 
1339       pango_layout_index_to_pos (gtk_label_get_layout (GTK_LABEL (widget)), 0,
1340                                  &rect);
1341 
1342       /*  the rectangle width is negative when rendering right-to-left  */
1343       x += PANGO_PIXELS (rect.x) + (rect.width < 0 ?
1344                                     PANGO_PIXELS (rect.width) : 0);
1345       y += PANGO_PIXELS (rect.y);
1346 
1347       gdk_cairo_set_source_pixbuf (cr, statusbar->icon, x, y);
1348       cairo_paint (cr);
1349 
1350       cairo_destroy (cr);
1351     }
1352 
1353   return FALSE;
1354 }
1355 
1356 static void
gimp_statusbar_shell_scaled(GimpDisplayShell * shell,GimpStatusbar * statusbar)1357 gimp_statusbar_shell_scaled (GimpDisplayShell *shell,
1358                              GimpStatusbar    *statusbar)
1359 {
1360   static PangoLayout *layout = NULL;
1361 
1362   GimpImage    *image = gimp_display_get_image (shell->display);
1363   GtkTreeModel *model;
1364   const gchar  *text;
1365   gint          image_width;
1366   gint          image_height;
1367   gdouble       image_xres;
1368   gdouble       image_yres;
1369   gint          width;
1370 
1371   if (image)
1372     {
1373       image_width  = gimp_image_get_width  (image);
1374       image_height = gimp_image_get_height (image);
1375       gimp_image_get_resolution (image, &image_xres, &image_yres);
1376     }
1377   else
1378     {
1379       image_width  = shell->disp_width;
1380       image_height = shell->disp_height;
1381       image_xres   = shell->display->config->monitor_xres;
1382       image_yres   = shell->display->config->monitor_yres;
1383     }
1384 
1385   g_signal_handlers_block_by_func (statusbar->scale_combo,
1386                                    gimp_statusbar_scale_changed, statusbar);
1387   gimp_scale_combo_box_set_scale (GIMP_SCALE_COMBO_BOX (statusbar->scale_combo),
1388                                   gimp_zoom_model_get_factor (shell->zoom));
1389   g_signal_handlers_unblock_by_func (statusbar->scale_combo,
1390                                      gimp_statusbar_scale_changed, statusbar);
1391 
1392   model = gtk_combo_box_get_model (GTK_COMBO_BOX (statusbar->unit_combo));
1393   gimp_unit_store_set_resolutions (GIMP_UNIT_STORE (model),
1394                                    image_xres, image_yres);
1395 
1396   g_signal_handlers_block_by_func (statusbar->unit_combo,
1397                                    gimp_statusbar_unit_changed, statusbar);
1398   gimp_unit_combo_box_set_active (GIMP_UNIT_COMBO_BOX (statusbar->unit_combo),
1399                                   shell->unit);
1400   g_signal_handlers_unblock_by_func (statusbar->unit_combo,
1401                                      gimp_statusbar_unit_changed, statusbar);
1402 
1403   if (shell->unit == GIMP_UNIT_PIXEL)
1404     {
1405       g_snprintf (statusbar->cursor_format_str,
1406                   sizeof (statusbar->cursor_format_str),
1407                   "%%s%%d%%s%%d%%s");
1408       g_snprintf (statusbar->cursor_format_str_f,
1409                   sizeof (statusbar->cursor_format_str_f),
1410                   "%%s%%.1f%%s%%.1f%%s");
1411       g_snprintf (statusbar->length_format_str,
1412                   sizeof (statusbar->length_format_str),
1413                   "%%s%%d%%s");
1414     }
1415   else /* show real world units */
1416     {
1417       gint w_digits;
1418       gint h_digits;
1419 
1420       w_digits = gimp_unit_get_scaled_digits (shell->unit, image_xres);
1421       h_digits = gimp_unit_get_scaled_digits (shell->unit, image_yres);
1422 
1423       g_snprintf (statusbar->cursor_format_str,
1424                   sizeof (statusbar->cursor_format_str),
1425                   "%%s%%.%df%%s%%.%df%%s",
1426                   w_digits, h_digits);
1427       strcpy (statusbar->cursor_format_str_f, statusbar->cursor_format_str);
1428       g_snprintf (statusbar->length_format_str,
1429                   sizeof (statusbar->length_format_str),
1430                   "%%s%%.%df%%s", MAX (w_digits, h_digits));
1431     }
1432 
1433   gimp_statusbar_update_cursor (statusbar, GIMP_CURSOR_PRECISION_SUBPIXEL,
1434                                 -image_width, -image_height);
1435 
1436   text = gtk_label_get_text (GTK_LABEL (statusbar->cursor_label));
1437 
1438   /* one static layout for all displays should be fine */
1439   if (! layout)
1440     layout = gtk_widget_create_pango_layout (statusbar->cursor_label, NULL);
1441 
1442   pango_layout_set_text (layout, text, -1);
1443   pango_layout_get_pixel_size (layout, &width, NULL);
1444 
1445   gtk_widget_set_size_request (statusbar->cursor_label, width, -1);
1446 
1447   gimp_statusbar_clear_cursor (statusbar);
1448 }
1449 
1450 static void
gimp_statusbar_shell_rotated(GimpDisplayShell * shell,GimpStatusbar * statusbar)1451 gimp_statusbar_shell_rotated (GimpDisplayShell *shell,
1452                               GimpStatusbar    *statusbar)
1453 {
1454   if (shell->rotate_angle != 0.0)
1455     {
1456       /* Degree symbol U+00B0. There are no spaces between the value and the
1457        * unit for angular rotation.
1458        */
1459       gchar *text = g_strdup_printf (" %.2f\xC2\xB0", shell->rotate_angle);
1460 
1461       gtk_label_set_text (GTK_LABEL (statusbar->rotate_label), text);
1462       g_free (text);
1463 
1464       gtk_widget_show (statusbar->rotate_widget);
1465     }
1466   else
1467     {
1468       gtk_widget_hide (statusbar->rotate_widget);
1469     }
1470 
1471   if (shell->flip_horizontally)
1472     gtk_widget_show (statusbar->horizontal_flip_icon);
1473   else
1474     gtk_widget_hide (statusbar->horizontal_flip_icon);
1475 
1476   if (shell->flip_vertically)
1477     gtk_widget_show (statusbar->vertical_flip_icon);
1478   else
1479     gtk_widget_hide (statusbar->vertical_flip_icon);
1480 }
1481 
1482 static void
gimp_statusbar_shell_status_notify(GimpDisplayShell * shell,const GParamSpec * pspec,GimpStatusbar * statusbar)1483 gimp_statusbar_shell_status_notify (GimpDisplayShell *shell,
1484                                     const GParamSpec *pspec,
1485                                     GimpStatusbar    *statusbar)
1486 {
1487   gimp_statusbar_replace (statusbar, "title",
1488                           NULL, "%s", shell->status);
1489 }
1490 
1491 static void
gimp_statusbar_unit_changed(GimpUnitComboBox * combo,GimpStatusbar * statusbar)1492 gimp_statusbar_unit_changed (GimpUnitComboBox *combo,
1493                              GimpStatusbar    *statusbar)
1494 {
1495   gimp_display_shell_set_unit (statusbar->shell,
1496                                gimp_unit_combo_box_get_active (combo));
1497 }
1498 
1499 static void
gimp_statusbar_scale_changed(GimpScaleComboBox * combo,GimpStatusbar * statusbar)1500 gimp_statusbar_scale_changed (GimpScaleComboBox *combo,
1501                               GimpStatusbar     *statusbar)
1502 {
1503   gimp_display_shell_scale (statusbar->shell,
1504                             GIMP_ZOOM_TO,
1505                             gimp_scale_combo_box_get_scale (combo),
1506                             GIMP_ZOOM_FOCUS_BEST_GUESS);
1507 }
1508 
1509 static void
gimp_statusbar_scale_activated(GimpScaleComboBox * combo,GimpStatusbar * statusbar)1510 gimp_statusbar_scale_activated (GimpScaleComboBox *combo,
1511                                 GimpStatusbar     *statusbar)
1512 {
1513   gtk_widget_grab_focus (statusbar->shell->canvas);
1514 }
1515 
1516 static gboolean
gimp_statusbar_rotate_pressed(GtkWidget * event_box,GdkEvent * event,GimpStatusbar * statusbar)1517 gimp_statusbar_rotate_pressed (GtkWidget     *event_box,
1518                                GdkEvent      *event,
1519                                GimpStatusbar *statusbar)
1520 {
1521   GimpImageWindow *window = gimp_display_shell_get_window (statusbar->shell);
1522   GimpUIManager   *manager = gimp_image_window_get_ui_manager (window);
1523 
1524   gimp_ui_manager_activate_action (manager, "view", "view-rotate-other");
1525   return FALSE;
1526 }
1527 
1528 static gboolean
gimp_statusbar_horiz_flip_pressed(GtkWidget * event_box,GdkEvent * event,GimpStatusbar * statusbar)1529 gimp_statusbar_horiz_flip_pressed (GtkWidget     *event_box,
1530                                    GdkEvent      *event,
1531                                    GimpStatusbar *statusbar)
1532 {
1533   GimpImageWindow *window = gimp_display_shell_get_window (statusbar->shell);
1534   GimpUIManager   *manager = gimp_image_window_get_ui_manager (window);
1535 
1536   gimp_ui_manager_activate_action (manager, "view", "view-flip-horizontally");
1537 
1538   return FALSE;
1539 }
1540 
1541 static gboolean
gimp_statusbar_vert_flip_pressed(GtkWidget * event_box,GdkEvent * event,GimpStatusbar * statusbar)1542 gimp_statusbar_vert_flip_pressed (GtkWidget     *event_box,
1543                                   GdkEvent      *event,
1544                                   GimpStatusbar *statusbar)
1545 {
1546   GimpImageWindow *window = gimp_display_shell_get_window (statusbar->shell);
1547   GimpUIManager   *manager = gimp_image_window_get_ui_manager (window);
1548 
1549   gimp_ui_manager_activate_action (manager, "view", "view-flip-vertically");
1550 
1551   return FALSE;
1552 }
1553 
1554 static guint
gimp_statusbar_get_context_id(GimpStatusbar * statusbar,const gchar * context)1555 gimp_statusbar_get_context_id (GimpStatusbar *statusbar,
1556                                const gchar   *context)
1557 {
1558   guint id = GPOINTER_TO_UINT (g_hash_table_lookup (statusbar->context_ids,
1559                                                     context));
1560 
1561   if (! id)
1562     {
1563       id = statusbar->seq_context_id++;
1564 
1565       g_hash_table_insert (statusbar->context_ids,
1566                            g_strdup (context), GUINT_TO_POINTER (id));
1567     }
1568 
1569   return id;
1570 }
1571 
1572 static gboolean
gimp_statusbar_temp_timeout(GimpStatusbar * statusbar)1573 gimp_statusbar_temp_timeout (GimpStatusbar *statusbar)
1574 {
1575   gimp_statusbar_pop_temp (statusbar);
1576 
1577   return FALSE;
1578 }
1579 
1580 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)1581 gimp_statusbar_add_message (GimpStatusbar *statusbar,
1582                             guint          context_id,
1583                             const gchar   *icon_name,
1584                             const gchar   *format,
1585                             va_list        args,
1586                             gboolean       move_to_front)
1587 {
1588   gchar            *message;
1589   GSList           *list;
1590   GimpStatusbarMsg *msg;
1591   gint              position;
1592 
1593   message = gimp_statusbar_vprintf (format, args);
1594 
1595   for (list = statusbar->messages; list; list = g_slist_next (list))
1596     {
1597       msg = list->data;
1598 
1599       if (msg->context_id == context_id)
1600         {
1601           gboolean is_front_message = (list == statusbar->messages);
1602 
1603           if ((is_front_message || ! move_to_front) &&
1604               strcmp (msg->text, message) == 0      &&
1605               g_strcmp0 (msg->icon_name, icon_name) == 0)
1606             {
1607               g_free (message);
1608               return;
1609             }
1610 
1611           if (move_to_front)
1612             {
1613               statusbar->messages = g_slist_remove (statusbar->messages, msg);
1614               gimp_statusbar_msg_free (msg);
1615 
1616               break;
1617             }
1618           else
1619             {
1620               g_free (msg->icon_name);
1621               msg->icon_name = g_strdup (icon_name);
1622 
1623               g_free (msg->text);
1624               msg->text = message;
1625 
1626               if (is_front_message)
1627                 gimp_statusbar_update (statusbar);
1628 
1629               return;
1630             }
1631         }
1632     }
1633 
1634   msg = g_slice_new (GimpStatusbarMsg);
1635 
1636   msg->context_id = context_id;
1637   msg->icon_name  = g_strdup (icon_name);
1638   msg->text       = message;
1639 
1640   /*  find the position at which to insert the new message  */
1641   position = 0;
1642   /*  progress messages are always at the front of the list  */
1643   if (! (statusbar->progress_active &&
1644          context_id == gimp_statusbar_get_context_id (statusbar, "progress")))
1645     {
1646       if (statusbar->progress_active)
1647         position++;
1648 
1649       /*  temporary messages are in front of all other non-progress messages  */
1650       if (statusbar->temp_timeout_id &&
1651           context_id != statusbar->temp_context_id)
1652         position++;
1653     }
1654 
1655   statusbar->messages = g_slist_insert (statusbar->messages, msg, position);
1656 
1657   if (position == 0)
1658     gimp_statusbar_update (statusbar);
1659 }
1660 
1661 static void
gimp_statusbar_remove_message(GimpStatusbar * statusbar,guint context_id)1662 gimp_statusbar_remove_message (GimpStatusbar *statusbar,
1663                                guint          context_id)
1664 {
1665   GSList   *list;
1666   gboolean  needs_update = FALSE;
1667 
1668   for (list = statusbar->messages; list; list = g_slist_next (list))
1669     {
1670       GimpStatusbarMsg *msg = list->data;
1671 
1672       if (msg->context_id == context_id)
1673         {
1674           needs_update = (list == statusbar->messages);
1675 
1676           statusbar->messages = g_slist_remove (statusbar->messages, msg);
1677           gimp_statusbar_msg_free (msg);
1678 
1679           break;
1680         }
1681     }
1682 
1683   if (needs_update)
1684     gimp_statusbar_update (statusbar);
1685 }
1686 
1687 static void
gimp_statusbar_msg_free(GimpStatusbarMsg * msg)1688 gimp_statusbar_msg_free (GimpStatusbarMsg *msg)
1689 {
1690   g_free (msg->icon_name);
1691   g_free (msg->text);
1692 
1693   g_slice_free (GimpStatusbarMsg, msg);
1694 }
1695 
1696 static gchar *
gimp_statusbar_vprintf(const gchar * format,va_list args)1697 gimp_statusbar_vprintf (const gchar *format,
1698                         va_list      args)
1699 {
1700   gchar *message;
1701   gchar *newline;
1702 
1703   message = g_strdup_vprintf (format, args);
1704 
1705   /*  guard us from multi-line strings  */
1706   newline = strchr (message, '\r');
1707   if (newline)
1708     *newline = '\0';
1709 
1710   newline = strchr (message, '\n');
1711   if (newline)
1712     *newline = '\0';
1713 
1714   return message;
1715 }
1716 
1717 static GdkPixbuf *
gimp_statusbar_load_icon(GimpStatusbar * statusbar,const gchar * icon_name)1718 gimp_statusbar_load_icon (GimpStatusbar *statusbar,
1719                           const gchar   *icon_name)
1720 {
1721   GdkPixbuf *icon;
1722 
1723   if (G_UNLIKELY (! statusbar->icon_hash))
1724     {
1725       statusbar->icon_hash =
1726         g_hash_table_new_full (g_str_hash,
1727                                g_str_equal,
1728                                (GDestroyNotify) g_free,
1729                                (GDestroyNotify) g_object_unref);
1730     }
1731 
1732   icon = g_hash_table_lookup (statusbar->icon_hash, icon_name);
1733 
1734   if (icon)
1735     return g_object_ref (icon);
1736 
1737   icon = gimp_widget_load_icon (statusbar->label, icon_name, 16);
1738 
1739   /* this is not optimal but so what */
1740   if (g_hash_table_size (statusbar->icon_hash) > 16)
1741     g_hash_table_remove_all (statusbar->icon_hash);
1742 
1743   g_hash_table_insert (statusbar->icon_hash,
1744                        g_strdup (icon_name), g_object_ref (icon));
1745 
1746   return icon;
1747 }
1748