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