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