1 /* LIBGIMP - The GIMP Library
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
4 * gimpdialog.c
5 * Copyright (C) 2000-2003 Michael Natterer <mitch@gimp.org>
6 *
7 * This library is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 3 of the License, or (at your option) any later version.
11 *
12 * This library 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 GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library. If not, see
19 * <https://www.gnu.org/licenses/>.
20 */
21
22 #include "config.h"
23
24 #include <gtk/gtk.h>
25
26 #include "gimpwidgetstypes.h"
27
28 #include "gimpdialog.h"
29 #include "gimphelpui.h"
30
31 #include "libgimp/libgimp-intl.h"
32
33
34 /**
35 * SECTION: gimpdialog
36 * @title: GimpDialog
37 * @short_description: Constructors for #GtkDialog's and action_areas as
38 * well as other dialog-related stuff.
39 *
40 * Constructors for #GtkDialog's and action_areas as well as other
41 * dialog-related stuff.
42 **/
43
44
45 enum
46 {
47 PROP_0,
48 PROP_HELP_FUNC,
49 PROP_HELP_ID,
50 PROP_PARENT
51 };
52
53
54 typedef struct _GimpDialogPrivate GimpDialogPrivate;
55
56 struct _GimpDialogPrivate
57 {
58 GimpHelpFunc help_func;
59 gchar *help_id;
60 GtkWidget *help_button;
61 };
62
63 #define GET_PRIVATE(dialog) ((GimpDialogPrivate *) gimp_dialog_get_instance_private ((GimpDialog *) (dialog)))
64
65
66 static void gimp_dialog_constructed (GObject *object);
67 static void gimp_dialog_dispose (GObject *object);
68 static void gimp_dialog_finalize (GObject *object);
69 static void gimp_dialog_set_property (GObject *object,
70 guint property_id,
71 const GValue *value,
72 GParamSpec *pspec);
73 static void gimp_dialog_get_property (GObject *object,
74 guint property_id,
75 GValue *value,
76 GParamSpec *pspec);
77
78 static void gimp_dialog_hide (GtkWidget *widget);
79 static gboolean gimp_dialog_delete_event (GtkWidget *widget,
80 GdkEventAny *event);
81
82 static void gimp_dialog_close (GtkDialog *dialog);
83
84 static void gimp_dialog_help (GObject *dialog);
85 static void gimp_dialog_response (GtkDialog *dialog,
86 gint response_id);
87
88
89 G_DEFINE_TYPE_WITH_PRIVATE (GimpDialog, gimp_dialog, GTK_TYPE_DIALOG)
90
91 #define parent_class gimp_dialog_parent_class
92
93 static gboolean show_help_button = TRUE;
94
95
96 static void
gimp_dialog_class_init(GimpDialogClass * klass)97 gimp_dialog_class_init (GimpDialogClass *klass)
98 {
99 GObjectClass *object_class = G_OBJECT_CLASS (klass);
100 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
101 GtkDialogClass *dialog_class = GTK_DIALOG_CLASS (klass);
102
103 object_class->constructed = gimp_dialog_constructed;
104 object_class->dispose = gimp_dialog_dispose;
105 object_class->finalize = gimp_dialog_finalize;
106 object_class->set_property = gimp_dialog_set_property;
107 object_class->get_property = gimp_dialog_get_property;
108
109 widget_class->hide = gimp_dialog_hide;
110 widget_class->delete_event = gimp_dialog_delete_event;
111
112 dialog_class->close = gimp_dialog_close;
113
114 /**
115 * GimpDialog:help-func:
116 *
117 * Since: 2.2
118 **/
119 g_object_class_install_property (object_class, PROP_HELP_FUNC,
120 g_param_spec_pointer ("help-func",
121 "Help Func",
122 "The help function to call when F1 is hit",
123 GIMP_PARAM_READWRITE |
124 G_PARAM_CONSTRUCT_ONLY));
125
126 /**
127 * GimpDialog:help-id:
128 *
129 * Since: 2.2
130 **/
131 g_object_class_install_property (object_class, PROP_HELP_ID,
132 g_param_spec_string ("help-id",
133 "Help ID",
134 "The help ID to pass to help-func",
135 NULL,
136 GIMP_PARAM_READWRITE |
137 G_PARAM_CONSTRUCT));
138
139 /**
140 * GimpDialog:parent:
141 *
142 * Since: 2.8
143 **/
144 g_object_class_install_property (object_class, PROP_PARENT,
145 g_param_spec_object ("parent",
146 "Parent",
147 "The dialog's parent widget",
148 GTK_TYPE_WIDGET,
149 GIMP_PARAM_WRITABLE |
150 G_PARAM_CONSTRUCT_ONLY));
151 }
152
153 static void
gimp_dialog_init(GimpDialog * dialog)154 gimp_dialog_init (GimpDialog *dialog)
155 {
156 g_signal_connect (dialog, "response",
157 G_CALLBACK (gimp_dialog_response),
158 NULL);
159 }
160
161 static void
gimp_dialog_constructed(GObject * object)162 gimp_dialog_constructed (GObject *object)
163 {
164 GimpDialogPrivate *private = GET_PRIVATE (object);
165
166 G_OBJECT_CLASS (parent_class)->constructed (object);
167
168 if (private->help_func)
169 gimp_help_connect (GTK_WIDGET (object),
170 private->help_func, private->help_id,
171 object);
172
173 if (show_help_button && private->help_func && private->help_id)
174 {
175 GtkDialog *dialog = GTK_DIALOG (object);
176 GtkWidget *action_area = gtk_dialog_get_action_area (dialog);
177
178 private->help_button = gtk_button_new_with_mnemonic (_("_Help"));
179
180 gtk_box_pack_end (GTK_BOX (action_area), private->help_button,
181 FALSE, TRUE, 0);
182 gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (action_area),
183 private->help_button, TRUE);
184 gtk_widget_show (private->help_button);
185
186 g_signal_connect_object (private->help_button, "clicked",
187 G_CALLBACK (gimp_dialog_help),
188 dialog, G_CONNECT_SWAPPED);
189 }
190 }
191
192 static void
gimp_dialog_dispose(GObject * object)193 gimp_dialog_dispose (GObject *object)
194 {
195 GdkDisplay *display = NULL;
196
197 if (g_main_depth () == 0)
198 {
199 display = gtk_widget_get_display (GTK_WIDGET (object));
200 g_object_ref (display);
201 }
202
203 G_OBJECT_CLASS (parent_class)->dispose (object);
204
205 if (display)
206 {
207 gdk_display_flush (display);
208 g_object_unref (display);
209 }
210 }
211
212 static void
gimp_dialog_finalize(GObject * object)213 gimp_dialog_finalize (GObject *object)
214 {
215 GimpDialogPrivate *private = GET_PRIVATE (object);
216
217 g_clear_pointer (&private->help_id, g_free);
218
219 G_OBJECT_CLASS (parent_class)->finalize (object);
220 }
221
222 static void
gimp_dialog_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)223 gimp_dialog_set_property (GObject *object,
224 guint property_id,
225 const GValue *value,
226 GParamSpec *pspec)
227 {
228 GimpDialogPrivate *private = GET_PRIVATE (object);
229
230 switch (property_id)
231 {
232 case PROP_HELP_FUNC:
233 private->help_func = g_value_get_pointer (value);
234 break;
235
236 case PROP_HELP_ID:
237 g_free (private->help_id);
238 private->help_id = g_value_dup_string (value);
239 gimp_help_set_help_data (GTK_WIDGET (object), NULL, private->help_id);
240 break;
241
242 case PROP_PARENT:
243 {
244 GtkWidget *parent = g_value_get_object (value);
245
246 if (parent)
247 {
248 if (GTK_IS_WINDOW (parent))
249 {
250 gtk_window_set_transient_for (GTK_WINDOW (object),
251 GTK_WINDOW (parent));
252 }
253 else
254 {
255 gtk_window_set_screen (GTK_WINDOW (object),
256 gtk_widget_get_screen (parent));
257 gtk_window_set_position (GTK_WINDOW (object),
258 GTK_WIN_POS_MOUSE);
259 }
260 }
261 }
262 break;
263
264 default:
265 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
266 break;
267 }
268 }
269
270 static void
gimp_dialog_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)271 gimp_dialog_get_property (GObject *object,
272 guint property_id,
273 GValue *value,
274 GParamSpec *pspec)
275 {
276 GimpDialogPrivate *private = GET_PRIVATE (object);
277
278 switch (property_id)
279 {
280 case PROP_HELP_FUNC:
281 g_value_set_pointer (value, private->help_func);
282 break;
283
284 case PROP_HELP_ID:
285 g_value_set_string (value, private->help_id);
286 break;
287
288 default:
289 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
290 break;
291 }
292 }
293
294 static void
gimp_dialog_hide(GtkWidget * widget)295 gimp_dialog_hide (GtkWidget *widget)
296 {
297 /* set focus to NULL so focus_out callbacks are invoked synchronously */
298 gtk_window_set_focus (GTK_WINDOW (widget), NULL);
299
300 GTK_WIDGET_CLASS (parent_class)->hide (widget);
301 }
302
303 static gboolean
gimp_dialog_delete_event(GtkWidget * widget,GdkEventAny * event)304 gimp_dialog_delete_event (GtkWidget *widget,
305 GdkEventAny *event)
306 {
307 return TRUE;
308 }
309
310 static void
gimp_dialog_close(GtkDialog * dialog)311 gimp_dialog_close (GtkDialog *dialog)
312 {
313 /* Synthesize delete_event to close dialog. */
314
315 GtkWidget *widget = GTK_WIDGET (dialog);
316
317 if (gtk_widget_get_window (widget))
318 {
319 GdkEvent *event = gdk_event_new (GDK_DELETE);
320
321 event->any.window = g_object_ref (gtk_widget_get_window (widget));
322 event->any.send_event = TRUE;
323
324 gtk_main_do_event (event);
325 gdk_event_free (event);
326 }
327 }
328
329 static void
gimp_dialog_help(GObject * dialog)330 gimp_dialog_help (GObject *dialog)
331 {
332 GimpDialogPrivate *private = GET_PRIVATE (dialog);
333
334 if (private->help_func)
335 private->help_func (private->help_id, dialog);
336 }
337
338 static void
gimp_dialog_response(GtkDialog * dialog,gint response_id)339 gimp_dialog_response (GtkDialog *dialog,
340 gint response_id)
341 {
342 GtkWidget *action_area;
343 GList *children;
344 GList *list;
345
346 action_area = gtk_dialog_get_action_area (dialog);
347
348 children = gtk_container_get_children (GTK_CONTAINER (action_area));
349
350 for (list = children; list; list = g_list_next (list))
351 {
352 GtkWidget *widget = list->data;
353
354 if (gtk_dialog_get_response_for_widget (dialog, widget) == response_id)
355 {
356 if (! GTK_IS_BUTTON (widget) ||
357 gtk_button_get_focus_on_click (GTK_BUTTON (widget)))
358 {
359 gtk_widget_grab_focus (widget);
360 }
361
362 break;
363 }
364 }
365
366 g_list_free (children);
367 }
368
369
370 /**
371 * gimp_dialog_new:
372 * @title: The dialog's title which will be set with
373 * gtk_window_set_title().
374 * @role: The dialog's @role which will be set with
375 * gtk_window_set_role().
376 * @parent: The @parent widget of this dialog.
377 * @flags: The @flags (see the #GtkDialog documentation).
378 * @help_func: The function which will be called if the user presses "F1".
379 * @help_id: The help_id which will be passed to @help_func.
380 * @...: A %NULL-terminated @va_list destribing the
381 * action_area buttons.
382 *
383 * Creates a new @GimpDialog widget.
384 *
385 * This function simply packs the action_area arguments passed in "..."
386 * into a @va_list variable and passes everything to gimp_dialog_new_valist().
387 *
388 * For a description of the format of the @va_list describing the
389 * action_area buttons see gtk_dialog_new_with_buttons().
390 *
391 * Returns: A #GimpDialog.
392 **/
393 GtkWidget *
gimp_dialog_new(const gchar * title,const gchar * role,GtkWidget * parent,GtkDialogFlags flags,GimpHelpFunc help_func,const gchar * help_id,...)394 gimp_dialog_new (const gchar *title,
395 const gchar *role,
396 GtkWidget *parent,
397 GtkDialogFlags flags,
398 GimpHelpFunc help_func,
399 const gchar *help_id,
400 ...)
401 {
402 GtkWidget *dialog;
403 va_list args;
404
405 g_return_val_if_fail (parent == NULL || GTK_IS_WIDGET (parent), NULL);
406 g_return_val_if_fail (title != NULL, NULL);
407 g_return_val_if_fail (role != NULL, NULL);
408
409 va_start (args, help_id);
410
411 dialog = gimp_dialog_new_valist (title, role,
412 parent, flags,
413 help_func, help_id,
414 args);
415
416 va_end (args);
417
418 return dialog;
419 }
420
421 /**
422 * gimp_dialog_new_valist:
423 * @title: The dialog's title which will be set with
424 * gtk_window_set_title().
425 * @role: The dialog's @role which will be set with
426 * gtk_window_set_role().
427 * @parent: The @parent widget of this dialog or %NULL.
428 * @flags: The @flags (see the #GtkDialog documentation).
429 * @help_func: The function which will be called if the user presses "F1".
430 * @help_id: The help_id which will be passed to @help_func.
431 * @args: A @va_list destribing the action_area buttons.
432 *
433 * Creates a new @GimpDialog widget. If a GtkWindow is specified as
434 * @parent then the dialog will be made transient for this window.
435 *
436 * For a description of the format of the @va_list describing the
437 * action_area buttons see gtk_dialog_new_with_buttons().
438 *
439 * Returns: A #GimpDialog.
440 **/
441 GtkWidget *
gimp_dialog_new_valist(const gchar * title,const gchar * role,GtkWidget * parent,GtkDialogFlags flags,GimpHelpFunc help_func,const gchar * help_id,va_list args)442 gimp_dialog_new_valist (const gchar *title,
443 const gchar *role,
444 GtkWidget *parent,
445 GtkDialogFlags flags,
446 GimpHelpFunc help_func,
447 const gchar *help_id,
448 va_list args)
449 {
450 GtkWidget *dialog;
451
452 g_return_val_if_fail (title != NULL, NULL);
453 g_return_val_if_fail (role != NULL, NULL);
454 g_return_val_if_fail (parent == NULL || GTK_IS_WIDGET (parent), NULL);
455
456 dialog = g_object_new (GIMP_TYPE_DIALOG,
457 "title", title,
458 "role", role,
459 "modal", (flags & GTK_DIALOG_MODAL),
460 "help-func", help_func,
461 "help-id", help_id,
462 "parent", parent,
463 NULL);
464
465 if (parent)
466 {
467 if (flags & GTK_DIALOG_DESTROY_WITH_PARENT)
468 g_signal_connect_object (parent, "destroy",
469 G_CALLBACK (gimp_dialog_close),
470 dialog, G_CONNECT_SWAPPED);
471 }
472
473 gimp_dialog_add_buttons_valist (GIMP_DIALOG (dialog), args);
474
475 return dialog;
476 }
477
478 /**
479 * gimp_dialog_add_button:
480 * @dialog: The @dialog to add a button to.
481 * @button_text: text of button, or stock ID.
482 * @response_id: response ID for the button.
483 *
484 * This function is essentially the same as gtk_dialog_add_button()
485 * except it ensures there is only one help button and automatically
486 * sets the RESPONSE_OK widget as the default response.
487 *
488 * Return value: the button widget that was added.
489 **/
490 GtkWidget *
gimp_dialog_add_button(GimpDialog * dialog,const gchar * button_text,gint response_id)491 gimp_dialog_add_button (GimpDialog *dialog,
492 const gchar *button_text,
493 gint response_id)
494 {
495 GtkWidget *button;
496
497 /* hide the automatically added help button if another one is added */
498 if (response_id == GTK_RESPONSE_HELP)
499 {
500 GimpDialogPrivate *private = GET_PRIVATE (dialog);
501
502 if (private->help_button)
503 gtk_widget_hide (private->help_button);
504 }
505
506 button = gtk_dialog_add_button (GTK_DIALOG (dialog), button_text,
507 response_id);
508
509 if (response_id == GTK_RESPONSE_OK)
510 {
511 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
512 GTK_RESPONSE_OK);
513 }
514
515 return button;
516 }
517
518 /**
519 * gimp_dialog_add_buttons:
520 * @dialog: The @dialog to add buttons to.
521 * @...: button_text-response_id pairs.
522 *
523 * This function is essentially the same as gtk_dialog_add_buttons()
524 * except it calls gimp_dialog_add_button() instead of gtk_dialog_add_button()
525 **/
526 void
gimp_dialog_add_buttons(GimpDialog * dialog,...)527 gimp_dialog_add_buttons (GimpDialog *dialog,
528 ...)
529 {
530 va_list args;
531
532 va_start (args, dialog);
533
534 gimp_dialog_add_buttons_valist (dialog, args);
535
536 va_end (args);
537 }
538
539 /**
540 * gimp_dialog_add_buttons_valist:
541 * @dialog: The @dialog to add buttons to.
542 * @args: The buttons as va_list.
543 *
544 * This function is essentially the same as gimp_dialog_add_buttons()
545 * except it takes a va_list instead of '...'
546 **/
547 void
gimp_dialog_add_buttons_valist(GimpDialog * dialog,va_list args)548 gimp_dialog_add_buttons_valist (GimpDialog *dialog,
549 va_list args)
550 {
551 const gchar *button_text;
552 gint response_id;
553
554 g_return_if_fail (GIMP_IS_DIALOG (dialog));
555
556 while ((button_text = va_arg (args, const gchar *)))
557 {
558 response_id = va_arg (args, gint);
559
560 gimp_dialog_add_button (dialog, button_text, response_id);
561 }
562 }
563
564
565 typedef struct
566 {
567 GtkDialog *dialog;
568 gint response_id;
569 GMainLoop *loop;
570 gboolean destroyed;
571 } RunInfo;
572
573 static void
run_shutdown_loop(RunInfo * ri)574 run_shutdown_loop (RunInfo *ri)
575 {
576 if (g_main_loop_is_running (ri->loop))
577 g_main_loop_quit (ri->loop);
578 }
579
580 static void
run_unmap_handler(GtkDialog * dialog,RunInfo * ri)581 run_unmap_handler (GtkDialog *dialog,
582 RunInfo *ri)
583 {
584 run_shutdown_loop (ri);
585 }
586
587 static void
run_response_handler(GtkDialog * dialog,gint response_id,RunInfo * ri)588 run_response_handler (GtkDialog *dialog,
589 gint response_id,
590 RunInfo *ri)
591 {
592 ri->response_id = response_id;
593
594 run_shutdown_loop (ri);
595 }
596
597 static gint
run_delete_handler(GtkDialog * dialog,GdkEventAny * event,RunInfo * ri)598 run_delete_handler (GtkDialog *dialog,
599 GdkEventAny *event,
600 RunInfo *ri)
601 {
602 run_shutdown_loop (ri);
603
604 return TRUE; /* Do not destroy */
605 }
606
607 static void
run_destroy_handler(GtkDialog * dialog,RunInfo * ri)608 run_destroy_handler (GtkDialog *dialog,
609 RunInfo *ri)
610 {
611 /* shutdown_loop will be called by run_unmap_handler */
612
613 ri->destroyed = TRUE;
614 }
615
616 /**
617 * gimp_dialog_run:
618 * @dialog: a #GimpDialog
619 *
620 * This function does exactly the same as gtk_dialog_run() except it
621 * does not make the dialog modal while the #GMainLoop is running.
622 *
623 * Return value: response ID
624 **/
625 gint
gimp_dialog_run(GimpDialog * dialog)626 gimp_dialog_run (GimpDialog *dialog)
627 {
628 RunInfo ri = { NULL, GTK_RESPONSE_NONE, NULL };
629 gulong response_handler;
630 gulong unmap_handler;
631 gulong destroy_handler;
632 gulong delete_handler;
633
634 g_return_val_if_fail (GIMP_IS_DIALOG (dialog), -1);
635
636 g_object_ref (dialog);
637
638 gtk_window_present (GTK_WINDOW (dialog));
639
640 response_handler = g_signal_connect (dialog, "response",
641 G_CALLBACK (run_response_handler),
642 &ri);
643 unmap_handler = g_signal_connect (dialog, "unmap",
644 G_CALLBACK (run_unmap_handler),
645 &ri);
646 delete_handler = g_signal_connect (dialog, "delete-event",
647 G_CALLBACK (run_delete_handler),
648 &ri);
649 destroy_handler = g_signal_connect (dialog, "destroy",
650 G_CALLBACK (run_destroy_handler),
651 &ri);
652
653 ri.loop = g_main_loop_new (NULL, FALSE);
654
655 GDK_THREADS_LEAVE ();
656 g_main_loop_run (ri.loop);
657 GDK_THREADS_ENTER ();
658
659 g_main_loop_unref (ri.loop);
660
661 ri.loop = NULL;
662 ri.destroyed = FALSE;
663
664 if (!ri.destroyed)
665 {
666 g_signal_handler_disconnect (dialog, response_handler);
667 g_signal_handler_disconnect (dialog, unmap_handler);
668 g_signal_handler_disconnect (dialog, delete_handler);
669 g_signal_handler_disconnect (dialog, destroy_handler);
670 }
671
672 g_object_unref (dialog);
673
674 return ri.response_id;
675 }
676
677 /**
678 * gimp_dialogs_show_help_button:
679 * @show: whether a help button should be added when creating a GimpDialog
680 *
681 * This function is for internal use only.
682 *
683 * Since: 2.2
684 **/
685 void
gimp_dialogs_show_help_button(gboolean show)686 gimp_dialogs_show_help_button (gboolean show)
687 {
688 show_help_button = show ? TRUE : FALSE;
689 }
690