1 /* GIMP - The GNU Image Manipulation Program
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
4 * gimpmessagebox.c
5 * Copyright (C) 2004 Sven Neumann <sven@gimp.org>
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21 #include "config.h"
22
23 #include <gegl.h>
24 #include <gtk/gtk.h>
25
26 #include "libgimpbase/gimpbase.h"
27 #include "libgimpwidgets/gimpwidgets.h"
28
29 #include "widgets-types.h"
30
31 #include "gimpmessagebox.h"
32
33 #include "gimp-intl.h"
34
35
36 #define GIMP_MESSAGE_BOX_SPACING 12
37
38 enum
39 {
40 PROP_0,
41 PROP_ICON_NAME
42 };
43
44
45 static void gimp_message_box_constructed (GObject *object);
46 static void gimp_message_box_dispose (GObject *object);
47 static void gimp_message_box_finalize (GObject *object);
48 static void gimp_message_box_set_property (GObject *object,
49 guint property_id,
50 const GValue *value,
51 GParamSpec *pspec);
52 static void gimp_message_box_get_property (GObject *object,
53 guint property_id,
54 GValue *value,
55 GParamSpec *pspec);
56
57 static void gimp_message_box_forall (GtkContainer *container,
58 gboolean include_internals,
59 GtkCallback callback,
60 gpointer callback_data);
61
62 static void gimp_message_box_size_request (GtkWidget *widget,
63 GtkRequisition *requisition);
64 static void gimp_message_box_size_allocate (GtkWidget *widget,
65 GtkAllocation *allocation);
66
67 static void gimp_message_box_set_label_text (GimpMessageBox *box,
68 gint n,
69 const gchar *format,
70 va_list args) G_GNUC_PRINTF (3, 0);
71 static void gimp_message_box_set_label_markup (GimpMessageBox *box,
72 gint n,
73 const gchar *format,
74 va_list args) G_GNUC_PRINTF (3, 0);
75
76 static gboolean gimp_message_box_update (gpointer data);
77
G_DEFINE_TYPE(GimpMessageBox,gimp_message_box,GTK_TYPE_BOX)78 G_DEFINE_TYPE (GimpMessageBox, gimp_message_box, GTK_TYPE_BOX)
79
80 #define parent_class gimp_message_box_parent_class
81
82
83 static void
84 gimp_message_box_class_init (GimpMessageBoxClass *klass)
85 {
86 GObjectClass *object_class = G_OBJECT_CLASS (klass);
87 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
88 GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
89
90 object_class->constructed = gimp_message_box_constructed;
91 object_class->dispose = gimp_message_box_dispose;
92 object_class->finalize = gimp_message_box_finalize;
93 object_class->set_property = gimp_message_box_set_property;
94 object_class->get_property = gimp_message_box_get_property;
95
96
97 widget_class->size_request = gimp_message_box_size_request;
98 widget_class->size_allocate = gimp_message_box_size_allocate;
99
100 container_class->forall = gimp_message_box_forall;
101
102 g_object_class_install_property (object_class, PROP_ICON_NAME,
103 g_param_spec_string ("icon-name", NULL, NULL,
104 NULL,
105 GIMP_PARAM_READWRITE |
106 G_PARAM_CONSTRUCT_ONLY));
107 }
108
109 static void
gimp_message_box_init(GimpMessageBox * box)110 gimp_message_box_init (GimpMessageBox *box)
111 {
112 gint i;
113
114 gtk_orientable_set_orientation (GTK_ORIENTABLE (box),
115 GTK_ORIENTATION_VERTICAL);
116
117 gtk_box_set_spacing (GTK_BOX (box), 12);
118 gtk_container_set_border_width (GTK_CONTAINER (box), 12);
119
120 /* Unset the focus chain to keep the labels from being in the focus
121 * chain. Users of GimpMessageBox that add focusable widgets should
122 * either unset the focus chain or (better) explicitly set one.
123 */
124 gtk_container_set_focus_chain (GTK_CONTAINER (box), NULL);
125
126 for (i = 0; i < 2; i++)
127 {
128 GtkWidget *label = g_object_new (GTK_TYPE_LABEL,
129 "wrap", TRUE,
130 "selectable", TRUE,
131 "xalign", 0.0,
132 "yalign", 0.5,
133 NULL);
134
135 if (i == 0)
136 gimp_label_set_attributes (GTK_LABEL (label),
137 PANGO_ATTR_SCALE, PANGO_SCALE_LARGE,
138 PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD,
139 -1);
140
141 gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
142
143 box->label[i] = label;
144 }
145
146 box->repeat = 0;
147 box->label[2] = NULL;
148 box->idle_id = 0;
149 }
150
151 static void
gimp_message_box_constructed(GObject * object)152 gimp_message_box_constructed (GObject *object)
153 {
154 GimpMessageBox *box = GIMP_MESSAGE_BOX (object);
155
156 G_OBJECT_CLASS (parent_class)->constructed (object);
157
158 if (box->icon_name)
159 {
160 gtk_widget_push_composite_child ();
161 box->image = gtk_image_new_from_icon_name (box->icon_name,
162 GTK_ICON_SIZE_DIALOG);
163 gtk_widget_pop_composite_child ();
164
165 gtk_misc_set_alignment (GTK_MISC (box->image), 0.0, 0.0);
166 gtk_widget_set_parent (box->image, GTK_WIDGET (box));
167 gtk_widget_show (box->image);
168 }
169 }
170
171 static void
gimp_message_box_dispose(GObject * object)172 gimp_message_box_dispose (GObject *object)
173 {
174 GimpMessageBox *box = GIMP_MESSAGE_BOX (object);
175
176 if (box->image)
177 {
178 gtk_widget_unparent (box->image);
179 box->image = NULL;
180 }
181
182 G_OBJECT_CLASS (parent_class)->dispose (object);
183 }
184
185 static void
gimp_message_box_finalize(GObject * object)186 gimp_message_box_finalize (GObject *object)
187 {
188 GimpMessageBox *box = GIMP_MESSAGE_BOX (object);
189
190 if (box->idle_id)
191 {
192 g_source_remove (box->idle_id);
193 box->idle_id = 0;
194 }
195
196 g_clear_pointer (&box->icon_name, g_free);
197
198 G_OBJECT_CLASS (parent_class)->finalize (object);
199 }
200
201 static void
gimp_message_box_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)202 gimp_message_box_set_property (GObject *object,
203 guint property_id,
204 const GValue *value,
205 GParamSpec *pspec)
206 {
207 GimpMessageBox *box = GIMP_MESSAGE_BOX (object);
208
209 switch (property_id)
210 {
211 case PROP_ICON_NAME:
212 box->icon_name = g_value_dup_string (value);
213 break;
214 default:
215 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
216 break;
217 }
218 }
219
220 static void
gimp_message_box_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)221 gimp_message_box_get_property (GObject *object,
222 guint property_id,
223 GValue *value,
224 GParamSpec *pspec)
225 {
226 GimpMessageBox *box = GIMP_MESSAGE_BOX (object);
227
228 switch (property_id)
229 {
230 case PROP_ICON_NAME:
231 g_value_set_string (value, box->icon_name);
232 break;
233 default:
234 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
235 break;
236 }
237 }
238
239 static void
gimp_message_box_size_request(GtkWidget * widget,GtkRequisition * requisition)240 gimp_message_box_size_request (GtkWidget *widget,
241 GtkRequisition *requisition)
242 {
243 GimpMessageBox *box = GIMP_MESSAGE_BOX (widget);
244
245 GTK_WIDGET_CLASS (parent_class)->size_request (widget, requisition);
246
247 if (box->image && gtk_widget_get_visible (box->image))
248 {
249 GtkRequisition child_requisition;
250 gint border_width;
251
252 gtk_widget_size_request (box->image, &child_requisition);
253
254 border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
255
256 requisition->width += child_requisition.width + GIMP_MESSAGE_BOX_SPACING;
257 requisition->height = MAX (requisition->height,
258 child_requisition.height +
259 2 * border_width);
260 }
261 }
262
263 static void
gimp_message_box_size_allocate(GtkWidget * widget,GtkAllocation * allocation)264 gimp_message_box_size_allocate (GtkWidget *widget,
265 GtkAllocation *allocation)
266 {
267 GimpMessageBox *box = GIMP_MESSAGE_BOX (widget);
268 GtkContainer *container = GTK_CONTAINER (widget);
269 gint width = 0;
270 gboolean rtl;
271
272 rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
273
274 if (box->image && gtk_widget_get_visible (box->image))
275 {
276 GtkRequisition child_requisition;
277 GtkAllocation child_allocation;
278 gint border_width;
279 gint height;
280
281 gtk_widget_size_request (box->image, &child_requisition);
282
283 border_width = gtk_container_get_border_width (container);
284
285 width = MIN (allocation->width - 2 * border_width,
286 child_requisition.width + GIMP_MESSAGE_BOX_SPACING);
287 width = MAX (1, width);
288
289 height = allocation->height - 2 * border_width;
290 height = MAX (1, height);
291
292 if (rtl)
293 child_allocation.x = (allocation->width -
294 border_width -
295 child_requisition.width);
296 else
297 child_allocation.x = allocation->x + border_width;
298
299 child_allocation.y = allocation->y + border_width;
300 child_allocation.width = width;
301 child_allocation.height = height;
302
303 gtk_widget_size_allocate (box->image, &child_allocation);
304 }
305
306 allocation->x += rtl ? 0 : width;
307 allocation->width -= width;
308
309 GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
310
311 allocation->x -= rtl ? 0 : width;
312 allocation->width += width;
313
314 gtk_widget_set_allocation (widget, allocation);
315 }
316
317 static void
gimp_message_box_forall(GtkContainer * container,gboolean include_internals,GtkCallback callback,gpointer callback_data)318 gimp_message_box_forall (GtkContainer *container,
319 gboolean include_internals,
320 GtkCallback callback,
321 gpointer callback_data)
322 {
323 if (include_internals)
324 {
325 GimpMessageBox *box = GIMP_MESSAGE_BOX (container);
326
327 if (box->image)
328 (* callback) (box->image, callback_data);
329 }
330
331 GTK_CONTAINER_CLASS (parent_class)->forall (container, include_internals,
332 callback, callback_data);
333 }
334
335 static void
gimp_message_box_set_label_text(GimpMessageBox * box,gint n,const gchar * format,va_list args)336 gimp_message_box_set_label_text (GimpMessageBox *box,
337 gint n,
338 const gchar *format,
339 va_list args)
340 {
341 GtkWidget *label = box->label[n];
342
343 if (format)
344 {
345 gchar *text = g_strdup_vprintf (format, args);
346 gchar *utf8 = gimp_any_to_utf8 (text, -1, "Cannot convert text to utf8.");
347
348 gtk_label_set_text (GTK_LABEL (label), utf8);
349 gtk_widget_show (label);
350
351 g_free (utf8);
352 g_free (text);
353 }
354 else
355 {
356 gtk_widget_hide (label);
357 gtk_label_set_text (GTK_LABEL (label), NULL);
358 }
359 }
360
361 static void
gimp_message_box_set_label_markup(GimpMessageBox * box,gint n,const gchar * format,va_list args)362 gimp_message_box_set_label_markup (GimpMessageBox *box,
363 gint n,
364 const gchar *format,
365 va_list args)
366 {
367 GtkWidget *label = box->label[n];
368
369 if (format)
370 {
371 gchar *text = g_markup_vprintf_escaped (format, args);
372
373 gtk_label_set_markup (GTK_LABEL (label), text);
374 gtk_widget_show (label);
375
376 g_free (text);
377 }
378 else
379 {
380 gtk_widget_hide (label);
381 gtk_label_set_text (GTK_LABEL (label), NULL);
382 }
383 }
384
385 static gboolean
gimp_message_box_update(gpointer data)386 gimp_message_box_update (gpointer data)
387 {
388 GimpMessageBox *box = data;
389 gchar *message;
390
391 box->idle_id = 0;
392
393 message = g_strdup_printf (ngettext ("Message repeated once.",
394 "Message repeated %d times.",
395 box->repeat),
396 box->repeat);
397
398 if (box->label[2])
399 {
400 gtk_label_set_text (GTK_LABEL (box->label[2]), message);
401 }
402 else
403 {
404 GtkWidget *label = box->label[2] = gtk_label_new (message);
405
406 gtk_label_set_xalign (GTK_LABEL (label), 0.0);
407 gimp_label_set_attributes (GTK_LABEL (label),
408 PANGO_ATTR_STYLE, PANGO_STYLE_OBLIQUE,
409 -1);
410 gtk_box_pack_end (GTK_BOX (box), label, FALSE, FALSE, 0);
411 gtk_widget_show (label);
412 }
413
414 g_free (message);
415
416 return G_SOURCE_REMOVE;
417 }
418
419 /* public functions */
420
421 GtkWidget *
gimp_message_box_new(const gchar * icon_name)422 gimp_message_box_new (const gchar *icon_name)
423 {
424 return g_object_new (GIMP_TYPE_MESSAGE_BOX,
425 "icon-name", icon_name,
426 NULL);
427 }
428
429 void
gimp_message_box_set_primary_text(GimpMessageBox * box,const gchar * format,...)430 gimp_message_box_set_primary_text (GimpMessageBox *box,
431 const gchar *format,
432 ...)
433 {
434 va_list args;
435
436 g_return_if_fail (GIMP_IS_MESSAGE_BOX (box));
437
438 va_start (args, format);
439 gimp_message_box_set_label_text (box, 0, format, args);
440 va_end (args);
441 }
442
443 void
gimp_message_box_set_text(GimpMessageBox * box,const gchar * format,...)444 gimp_message_box_set_text (GimpMessageBox *box,
445 const gchar *format,
446 ...)
447 {
448 va_list args;
449
450 g_return_if_fail (GIMP_IS_MESSAGE_BOX (box));
451
452 va_start (args, format);
453 gimp_message_box_set_label_text (box, 1, format, args);
454 va_end (args);
455 }
456
457 void
gimp_message_box_set_markup(GimpMessageBox * box,const gchar * format,...)458 gimp_message_box_set_markup (GimpMessageBox *box,
459 const gchar *format,
460 ...)
461 {
462 va_list args;
463
464 g_return_if_fail (GIMP_IS_MESSAGE_BOX (box));
465
466 va_start (args, format);
467 gimp_message_box_set_label_markup (box, 1,format, args);
468 va_end (args);
469 }
470
471 gint
gimp_message_box_repeat(GimpMessageBox * box)472 gimp_message_box_repeat (GimpMessageBox *box)
473 {
474 g_return_val_if_fail (GIMP_IS_MESSAGE_BOX (box), 0);
475
476 box->repeat++;
477
478 if (box->idle_id == 0)
479 {
480 /* When a same message is repeated dozens of thousands of times in
481 * a short span of time, updating the GUI at each increment is
482 * extremely slow (like really really slow, your GUI gets stuck
483 * for 10 minutes). So let's just delay GUI update as a low
484 * priority idle task.
485 */
486 box->idle_id = g_idle_add_full (G_PRIORITY_LOW,
487 gimp_message_box_update,
488 box, NULL);
489 }
490
491 return box->repeat;
492 }
493