1 | /* GTK - The GIMP Toolkit |
2 | * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Lesser General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2 of the License, or (at your option) any later version. |
8 | * |
9 | * This library 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 GNU |
12 | * Lesser General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Lesser General Public |
15 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
16 | */ |
17 | |
18 | /* |
19 | * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS |
20 | * file for a list of people on the GTK+ Team. See the ChangeLog |
21 | * files for a list of changes. These files are distributed with |
22 | * GTK+ at ftp://ftp.gtk.org/pub/gtk/. |
23 | */ |
24 | |
25 | #include "config.h" |
26 | |
27 | #include <stdlib.h> |
28 | #include <string.h> |
29 | |
30 | #include "gtkbutton.h" |
31 | #include "gtkdialog.h" |
32 | #include "gtkdialogprivate.h" |
33 | #include "gtkheaderbar.h" |
34 | #include "gtkheaderbarprivate.h" |
35 | #include "gtklabel.h" |
36 | #include "gtkmarshalers.h" |
37 | #include "gtkbox.h" |
38 | #include "gtkmain.h" |
39 | #include "gtkintl.h" |
40 | #include "gtkprivate.h" |
41 | #include "gtkbuildable.h" |
42 | #include "gtkbuilderprivate.h" |
43 | #include "gtksettings.h" |
44 | #include "gtktypebuiltins.h" |
45 | #include "gtksizegroup.h" |
46 | |
47 | /** |
48 | * GtkDialog: |
49 | * |
50 | * Dialogs are a convenient way to prompt the user for a small amount |
51 | * of input. |
52 | * |
53 | *  |
54 | * |
55 | * Typical uses are to display a message, ask a question, or anything else |
56 | * that does not require extensive effort on the user’s part. |
57 | * |
58 | * The main area of a `GtkDialog` is called the "content area", and is yours |
59 | * to populate with widgets such a `GtkLabel` or `GtkEntry`, to present |
60 | * your information, questions, or tasks to the user. |
61 | * |
62 | * In addition, dialogs allow you to add "action widgets". Most commonly, |
63 | * action widgets are buttons. Depending on the platform, action widgets may |
64 | * be presented in the header bar at the top of the window, or at the bottom |
65 | * of the window. To add action widgets, create your `GtkDialog` using |
66 | * [ctor@Gtk.Dialog.new_with_buttons], or use |
67 | * [method@Gtk.Dialog.add_button], [method@Gtk.Dialog.add_buttons], |
68 | * or [method@Gtk.Dialog.add_action_widget]. |
69 | * |
70 | * `GtkDialogs` uses some heuristics to decide whether to add a close |
71 | * button to the window decorations. If any of the action buttons use |
72 | * the response ID %GTK_RESPONSE_CLOSE or %GTK_RESPONSE_CANCEL, the |
73 | * close button is omitted. |
74 | * |
75 | * Clicking a button that was added as an action widget will emit the |
76 | * [signal@Gtk.Dialog::response] signal with a response ID that you specified. |
77 | * GTK will never assign a meaning to positive response IDs; these are |
78 | * entirely user-defined. But for convenience, you can use the response |
79 | * IDs in the [enum@Gtk.ResponseType] enumeration (these all have values |
80 | * less than zero). If a dialog receives a delete event, the |
81 | * [signal@Gtk.Dialog::response] signal will be emitted with the |
82 | * %GTK_RESPONSE_DELETE_EVENT response ID. |
83 | * |
84 | * Dialogs are created with a call to [ctor@Gtk.Dialog.new] or |
85 | * [ctor@Gtk.Dialog.new_with_buttons]. The latter is recommended; it allows |
86 | * you to set the dialog title, some convenient flags, and add buttons. |
87 | * |
88 | * A “modal” dialog (that is, one which freezes the rest of the application |
89 | * from user input), can be created by calling [method@Gtk.Window.set_modal] |
90 | * on the dialog. When using [ctor@Gtk.Dialog.new_with_buttons], you can also |
91 | * pass the %GTK_DIALOG_MODAL flag to make a dialog modal. |
92 | * |
93 | * For the simple dialog in the following example, a [class@Gtk.MessageDialog] |
94 | * would save some effort. But you’d need to create the dialog contents manually |
95 | * if you had more than a simple message in the dialog. |
96 | * |
97 | * An example for simple `GtkDialog` usage: |
98 | * |
99 | * ```c |
100 | * // Function to open a dialog box with a message |
101 | * void |
102 | * quick_message (GtkWindow *parent, char *message) |
103 | * { |
104 | * GtkWidget *dialog, *label, *content_area; |
105 | * GtkDialogFlags flags; |
106 | * |
107 | * // Create the widgets |
108 | * flags = GTK_DIALOG_DESTROY_WITH_PARENT; |
109 | * dialog = gtk_dialog_new_with_buttons ("Message", |
110 | * parent, |
111 | * flags, |
112 | * _("_OK"), |
113 | * GTK_RESPONSE_NONE, |
114 | * NULL); |
115 | * content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); |
116 | * label = gtk_label_new (message); |
117 | * |
118 | * // Ensure that the dialog box is destroyed when the user responds |
119 | * |
120 | * g_signal_connect_swapped (dialog, |
121 | * "response", |
122 | * G_CALLBACK (gtk_window_destroy), |
123 | * dialog); |
124 | * |
125 | * // Add the label, and show everything we’ve added |
126 | * |
127 | * gtk_box_append (GTK_BOX (content_area), label); |
128 | * gtk_widget_show (dialog); |
129 | * } |
130 | * ``` |
131 | * |
132 | * # GtkDialog as GtkBuildable |
133 | * |
134 | * The `GtkDialog` implementation of the `GtkBuildable` interface exposes the |
135 | * @content_area as an internal child with the name “content_area”. |
136 | * |
137 | * `GtkDialog` supports a custom `<action-widgets>` element, which can contain |
138 | * multiple `<action-widget>` elements. The “response” attribute specifies a |
139 | * numeric response, and the content of the element is the id of widget |
140 | * (which should be a child of the dialogs @action_area). To mark a response |
141 | * as default, set the “default” attribute of the `<action-widget>` element |
142 | * to true. |
143 | * |
144 | * `GtkDialog` supports adding action widgets by specifying “action” as |
145 | * the “type” attribute of a `<child>` element. The widget will be added |
146 | * either to the action area or the headerbar of the dialog, depending |
147 | * on the “use-header-bar” property. The response id has to be associated |
148 | * with the action widget using the `<action-widgets>` element. |
149 | * |
150 | * An example of a `GtkDialog` UI definition fragment: |
151 | * |
152 | * ```xml |
153 | * <object class="GtkDialog" id="dialog1"> |
154 | * <child type="action"> |
155 | * <object class="GtkButton" id="button_cancel"/> |
156 | * </child> |
157 | * <child type="action"> |
158 | * <object class="GtkButton" id="button_ok"> |
159 | * </object> |
160 | * </child> |
161 | * <action-widgets> |
162 | * <action-widget response="cancel">button_cancel</action-widget> |
163 | * <action-widget response="ok" default="true">button_ok</action-widget> |
164 | * </action-widgets> |
165 | * </object> |
166 | * ``` |
167 | * |
168 | * # Accessibility |
169 | * |
170 | * `GtkDialog` uses the %GTK_ACCESSIBLE_ROLE_DIALOG role. |
171 | */ |
172 | |
173 | typedef struct _ResponseData ResponseData; |
174 | |
175 | typedef struct |
176 | { |
177 | GtkWidget *; |
178 | GtkWidget *action_area; |
179 | GtkWidget *content_area; |
180 | GtkWidget *action_box; |
181 | GtkSizeGroup *size_group; |
182 | |
183 | int ; |
184 | gboolean constructed; |
185 | ResponseData *action_widgets; |
186 | } GtkDialogPrivate; |
187 | |
188 | struct _ResponseData |
189 | { |
190 | ResponseData *next; |
191 | GtkDialog *dialog; |
192 | GtkWidget *widget; |
193 | int response_id; |
194 | }; |
195 | |
196 | static void gtk_dialog_add_buttons_valist (GtkDialog *dialog, |
197 | const char *first_button_text, |
198 | va_list args); |
199 | |
200 | static gboolean gtk_dialog_close_request (GtkWindow *window); |
201 | static void gtk_dialog_map (GtkWidget *widget); |
202 | |
203 | static void gtk_dialog_close (GtkDialog *dialog); |
204 | |
205 | static ResponseData * get_response_data (GtkDialog *dialog, |
206 | GtkWidget *widget, |
207 | gboolean create); |
208 | |
209 | static void gtk_dialog_buildable_interface_init (GtkBuildableIface *iface); |
210 | static gboolean gtk_dialog_buildable_custom_tag_start (GtkBuildable *buildable, |
211 | GtkBuilder *builder, |
212 | GObject *child, |
213 | const char *tagname, |
214 | GtkBuildableParser *parser, |
215 | gpointer *data); |
216 | static void gtk_dialog_buildable_custom_finished (GtkBuildable *buildable, |
217 | GtkBuilder *builder, |
218 | GObject *child, |
219 | const char *tagname, |
220 | gpointer user_data); |
221 | static void gtk_dialog_buildable_add_child (GtkBuildable *buildable, |
222 | GtkBuilder *builder, |
223 | GObject *child, |
224 | const char *type); |
225 | |
226 | |
227 | enum { |
228 | PROP_0, |
229 | |
230 | }; |
231 | |
232 | enum { |
233 | RESPONSE, |
234 | CLOSE, |
235 | LAST_SIGNAL |
236 | }; |
237 | |
238 | static guint dialog_signals[LAST_SIGNAL]; |
239 | |
240 | G_DEFINE_TYPE_WITH_CODE (GtkDialog, gtk_dialog, GTK_TYPE_WINDOW, |
241 | G_ADD_PRIVATE (GtkDialog) |
242 | G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, |
243 | gtk_dialog_buildable_interface_init)) |
244 | |
245 | static void |
246 | (GtkDialog *dialog, |
247 | int ) |
248 | { |
249 | GtkDialogPrivate *priv = gtk_dialog_get_instance_private (self: dialog); |
250 | |
251 | if (use_header_bar == -1) |
252 | return; |
253 | |
254 | priv->use_header_bar = use_header_bar; |
255 | } |
256 | |
257 | /* A convenience helper for built-in dialogs */ |
258 | void |
259 | (GtkDialog *dialog) |
260 | { |
261 | GtkDialogPrivate *priv = gtk_dialog_get_instance_private (self: dialog); |
262 | |
263 | g_assert (!priv->constructed); |
264 | |
265 | g_object_get (object: gtk_widget_get_settings (GTK_WIDGET (dialog)), |
266 | first_property_name: "gtk-dialogs-use-header" , &priv->use_header_bar, |
267 | NULL); |
268 | } |
269 | |
270 | static void |
271 | gtk_dialog_set_property (GObject *object, |
272 | guint prop_id, |
273 | const GValue *value, |
274 | GParamSpec *pspec) |
275 | { |
276 | GtkDialog *dialog = GTK_DIALOG (object); |
277 | |
278 | switch (prop_id) |
279 | { |
280 | case PROP_USE_HEADER_BAR: |
281 | set_use_header_bar (dialog, use_header_bar: g_value_get_int (value)); |
282 | break; |
283 | |
284 | default: |
285 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
286 | break; |
287 | } |
288 | } |
289 | |
290 | static void |
291 | gtk_dialog_get_property (GObject *object, |
292 | guint prop_id, |
293 | GValue *value, |
294 | GParamSpec *pspec) |
295 | { |
296 | GtkDialog *dialog = GTK_DIALOG (object); |
297 | GtkDialogPrivate *priv = gtk_dialog_get_instance_private (self: dialog); |
298 | |
299 | switch (prop_id) |
300 | { |
301 | case PROP_USE_HEADER_BAR: |
302 | g_value_set_int (value, v_int: priv->use_header_bar); |
303 | break; |
304 | |
305 | default: |
306 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
307 | break; |
308 | } |
309 | } |
310 | |
311 | static void |
312 | action_widget_activated (GtkWidget *widget, GtkDialog *dialog) |
313 | { |
314 | int response_id; |
315 | |
316 | response_id = gtk_dialog_get_response_for_widget (dialog, widget); |
317 | |
318 | gtk_dialog_response (dialog, response_id); |
319 | } |
320 | |
321 | static void |
322 | add_response_data (GtkDialog *dialog, |
323 | GtkWidget *child, |
324 | int response_id) |
325 | { |
326 | ResponseData *ad; |
327 | guint signal_id; |
328 | |
329 | ad = get_response_data (dialog, widget: child, TRUE); |
330 | ad->response_id = response_id; |
331 | |
332 | if (GTK_IS_BUTTON (child)) |
333 | signal_id = g_signal_lookup (name: "clicked" , GTK_TYPE_BUTTON); |
334 | else |
335 | signal_id = gtk_widget_class_get_activate_signal (GTK_WIDGET_GET_CLASS (child)); |
336 | |
337 | if (signal_id) |
338 | { |
339 | GClosure *closure; |
340 | |
341 | closure = g_cclosure_new_object (G_CALLBACK (action_widget_activated), |
342 | G_OBJECT (dialog)); |
343 | g_signal_connect_closure_by_id (instance: child, signal_id, detail: 0, closure, FALSE); |
344 | } |
345 | else |
346 | g_warning ("Only 'activatable' widgets can be packed into the action area of a GtkDialog" ); |
347 | } |
348 | |
349 | static void |
350 | (GtkDialog *dialog, |
351 | GtkWidget *child, |
352 | int response_id) |
353 | { |
354 | GtkDialogPrivate *priv = gtk_dialog_get_instance_private (self: dialog); |
355 | |
356 | gtk_widget_set_valign (widget: child, align: GTK_ALIGN_CENTER); |
357 | |
358 | if (response_id == GTK_RESPONSE_CANCEL || response_id == GTK_RESPONSE_HELP) |
359 | gtk_header_bar_pack_start (GTK_HEADER_BAR (priv->headerbar), child); |
360 | else |
361 | gtk_header_bar_pack_end (GTK_HEADER_BAR (priv->headerbar), child); |
362 | |
363 | gtk_size_group_add_widget (size_group: priv->size_group, widget: child); |
364 | |
365 | if (response_id == GTK_RESPONSE_CANCEL || response_id == GTK_RESPONSE_CLOSE) |
366 | gtk_header_bar_set_show_title_buttons (GTK_HEADER_BAR (priv->headerbar), FALSE); |
367 | } |
368 | |
369 | static void |
370 | apply_response_for_action_area (GtkDialog *dialog, |
371 | GtkWidget *child, |
372 | int response_id) |
373 | { |
374 | GtkDialogPrivate *priv G_GNUC_UNUSED = gtk_dialog_get_instance_private (self: dialog); |
375 | |
376 | g_assert (gtk_widget_get_parent (child) == priv->action_area); |
377 | } |
378 | |
379 | static void |
380 | add_to_action_area (GtkDialog *dialog, |
381 | GtkWidget *child, |
382 | int response_id) |
383 | { |
384 | GtkDialogPrivate *priv = gtk_dialog_get_instance_private (self: dialog); |
385 | |
386 | gtk_widget_set_valign (widget: child, align: GTK_ALIGN_BASELINE); |
387 | gtk_box_append (GTK_BOX (priv->action_area), child); |
388 | apply_response_for_action_area (dialog, child, response_id); |
389 | } |
390 | |
391 | static void |
392 | update_suggested_action (GtkDialog *dialog, |
393 | GtkWidget *child) |
394 | { |
395 | GtkDialogPrivate *priv = gtk_dialog_get_instance_private (self: dialog); |
396 | |
397 | if (priv->use_header_bar) |
398 | { |
399 | if (gtk_widget_has_css_class (widget: child, css_class: "default" )) |
400 | gtk_widget_add_css_class (widget: child, css_class: "suggested-action" ); |
401 | else |
402 | gtk_widget_remove_css_class (widget: child, css_class: "suggested-action" ); |
403 | } |
404 | } |
405 | |
406 | static void |
407 | gtk_dialog_constructed (GObject *object) |
408 | { |
409 | GtkDialog *dialog = GTK_DIALOG (object); |
410 | GtkDialogPrivate *priv = gtk_dialog_get_instance_private (self: dialog); |
411 | |
412 | G_OBJECT_CLASS (gtk_dialog_parent_class)->constructed (object); |
413 | |
414 | priv->constructed = TRUE; |
415 | if (priv->use_header_bar == -1) |
416 | priv->use_header_bar = FALSE; |
417 | |
418 | if (priv->use_header_bar) |
419 | { |
420 | GList *children, *l; |
421 | GtkWidget *child; |
422 | |
423 | children = NULL; |
424 | for (child = gtk_widget_get_first_child (widget: priv->action_area); |
425 | child != NULL; |
426 | child = gtk_widget_get_next_sibling (widget: child)) |
427 | children = g_list_append (list: children, data: child); |
428 | |
429 | for (l = children; l != NULL; l = l->next) |
430 | { |
431 | gboolean has_default; |
432 | ResponseData *rd; |
433 | int response_id; |
434 | |
435 | child = l->data; |
436 | |
437 | has_default = gtk_widget_has_default (widget: child); |
438 | rd = get_response_data (dialog, widget: child, FALSE); |
439 | response_id = rd ? rd->response_id : GTK_RESPONSE_NONE; |
440 | |
441 | g_object_ref (child); |
442 | gtk_box_remove (GTK_BOX (priv->action_area), child); |
443 | add_to_header_bar (dialog, child, response_id); |
444 | g_object_unref (object: child); |
445 | |
446 | if (has_default) |
447 | { |
448 | gtk_window_set_default_widget (GTK_WINDOW (dialog), default_widget: child); |
449 | update_suggested_action (dialog, child); |
450 | } |
451 | } |
452 | g_list_free (list: children); |
453 | |
454 | _gtk_header_bar_track_default_decoration (GTK_HEADER_BAR (priv->headerbar)); |
455 | } |
456 | else |
457 | { |
458 | gtk_window_set_titlebar (GTK_WINDOW (dialog), NULL); |
459 | priv->headerbar = NULL; |
460 | } |
461 | |
462 | gtk_widget_set_visible (widget: priv->action_box, visible: !priv->use_header_bar); |
463 | } |
464 | |
465 | static void |
466 | gtk_dialog_finalize (GObject *obj) |
467 | { |
468 | GtkDialog *dialog = GTK_DIALOG (obj); |
469 | GtkDialogPrivate *priv = gtk_dialog_get_instance_private (self: dialog); |
470 | |
471 | while (priv->action_widgets) |
472 | g_object_set_data (G_OBJECT (priv->action_widgets->widget), |
473 | key: "gtk-dialog-response-data" , NULL); |
474 | |
475 | g_object_unref (object: priv->size_group); |
476 | |
477 | G_OBJECT_CLASS (gtk_dialog_parent_class)->finalize (obj); |
478 | } |
479 | |
480 | static void |
481 | gtk_dialog_class_init (GtkDialogClass *class) |
482 | { |
483 | GObjectClass *gobject_class; |
484 | GtkWidgetClass *widget_class; |
485 | GtkWindowClass *window_class; |
486 | |
487 | gobject_class = G_OBJECT_CLASS (class); |
488 | widget_class = GTK_WIDGET_CLASS (class); |
489 | window_class = GTK_WINDOW_CLASS (class); |
490 | |
491 | gobject_class->constructed = gtk_dialog_constructed; |
492 | gobject_class->set_property = gtk_dialog_set_property; |
493 | gobject_class->get_property = gtk_dialog_get_property; |
494 | gobject_class->finalize = gtk_dialog_finalize; |
495 | |
496 | widget_class->map = gtk_dialog_map; |
497 | |
498 | window_class->close_request = gtk_dialog_close_request; |
499 | |
500 | class->close = gtk_dialog_close; |
501 | |
502 | /** |
503 | * GtkDialog::response: |
504 | * @dialog: the object on which the signal is emitted |
505 | * @response_id: the response ID |
506 | * |
507 | * Emitted when an action widget is clicked. |
508 | * |
509 | * The signal is also emitted when the dialog receives a |
510 | * delete event, and when [method@Gtk.Dialog.response] is called. |
511 | * On a delete event, the response ID is %GTK_RESPONSE_DELETE_EVENT. |
512 | * Otherwise, it depends on which action widget was clicked. |
513 | */ |
514 | dialog_signals[RESPONSE] = |
515 | g_signal_new (I_("response" ), |
516 | G_OBJECT_CLASS_TYPE (class), |
517 | signal_flags: G_SIGNAL_RUN_LAST, |
518 | G_STRUCT_OFFSET (GtkDialogClass, response), |
519 | NULL, NULL, |
520 | NULL, |
521 | G_TYPE_NONE, n_params: 1, |
522 | G_TYPE_INT); |
523 | |
524 | /** |
525 | * GtkDialog::close: |
526 | * |
527 | * Emitted when the user uses a keybinding to close the dialog. |
528 | * |
529 | * This is a [keybinding signal](class.SignalAction.html). |
530 | * |
531 | * The default binding for this signal is the Escape key. |
532 | */ |
533 | dialog_signals[CLOSE] = |
534 | g_signal_new (I_("close" ), |
535 | G_OBJECT_CLASS_TYPE (class), |
536 | signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, |
537 | G_STRUCT_OFFSET (GtkDialogClass, close), |
538 | NULL, NULL, |
539 | NULL, |
540 | G_TYPE_NONE, n_params: 0); |
541 | |
542 | /** |
543 | * GtkDialog:use-header-bar: |
544 | * |
545 | * %TRUE if the dialog uses a headerbar for action buttons |
546 | * instead of the action-area. |
547 | * |
548 | * For technical reasons, this property is declared as an integer |
549 | * property, but you should only set it to %TRUE or %FALSE. |
550 | * |
551 | * ## Creating a dialog with headerbar |
552 | * |
553 | * Builtin `GtkDialog` subclasses such as [class@Gtk.ColorChooserDialog] |
554 | * set this property according to platform conventions (using the |
555 | * [property@Gtk.Settings:gtk-dialogs-use-header] setting). |
556 | * |
557 | * Here is how you can achieve the same: |
558 | * |
559 | * ```c |
560 | * g_object_get (settings, "gtk-dialogs-use-header", &header, NULL); |
561 | * dialog = g_object_new (GTK_TYPE_DIALOG, header, TRUE, NULL); |
562 | * ``` |
563 | */ |
564 | g_object_class_install_property (oclass: gobject_class, |
565 | property_id: PROP_USE_HEADER_BAR, |
566 | pspec: g_param_spec_int (name: "use-header-bar" , |
567 | P_("Use Header Bar" ), |
568 | P_("Use Header Bar for actions." ), |
569 | minimum: -1, maximum: 1, default_value: -1, |
570 | GTK_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY)); |
571 | |
572 | gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_Escape, mods: 0, signal: "close" , NULL); |
573 | |
574 | /* Bind class to template |
575 | */ |
576 | gtk_widget_class_set_template_from_resource (widget_class, resource_name: "/org/gtk/libgtk/ui/gtkdialog.ui" ); |
577 | gtk_widget_class_bind_template_child_internal_private (widget_class, GtkDialog, headerbar); |
578 | gtk_widget_class_bind_template_child_internal_private (widget_class, GtkDialog, action_area); |
579 | gtk_widget_class_bind_template_child_internal_private (widget_class, GtkDialog, content_area); |
580 | gtk_widget_class_bind_template_child_private (widget_class, GtkDialog, action_box); |
581 | |
582 | gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_DIALOG); |
583 | } |
584 | |
585 | static void |
586 | gtk_dialog_init (GtkDialog *dialog) |
587 | { |
588 | GtkDialogPrivate *priv = gtk_dialog_get_instance_private (self: dialog); |
589 | |
590 | gtk_widget_add_css_class (GTK_WIDGET (dialog), css_class: "dialog" ); |
591 | |
592 | priv->use_header_bar = -1; |
593 | priv->size_group = gtk_size_group_new (mode: GTK_SIZE_GROUP_HORIZONTAL); |
594 | |
595 | gtk_widget_init_template (GTK_WIDGET (dialog)); |
596 | } |
597 | |
598 | static GtkBuildableIface *parent_buildable_iface; |
599 | |
600 | static void |
601 | gtk_dialog_buildable_interface_init (GtkBuildableIface *iface) |
602 | { |
603 | parent_buildable_iface = g_type_interface_peek_parent (g_iface: iface); |
604 | iface->custom_tag_start = gtk_dialog_buildable_custom_tag_start; |
605 | iface->custom_finished = gtk_dialog_buildable_custom_finished; |
606 | iface->add_child = gtk_dialog_buildable_add_child; |
607 | } |
608 | |
609 | static gboolean |
610 | gtk_dialog_close_request (GtkWindow *window) |
611 | { |
612 | gtk_dialog_response (GTK_DIALOG (window), response_id: GTK_RESPONSE_DELETE_EVENT); |
613 | |
614 | return GTK_WINDOW_CLASS (gtk_dialog_parent_class)->close_request (window); |
615 | } |
616 | |
617 | /* A far too tricky heuristic for getting the right initial |
618 | * focus widget if none was set. What we do is we focus the first |
619 | * widget in the tab chain, but if this results in the focus |
620 | * ending up on one of the response widgets _other_ than the |
621 | * default response, we focus the default response instead. |
622 | * |
623 | * Additionally, skip selectable labels when looking for the |
624 | * right initial focus widget. |
625 | */ |
626 | static void |
627 | gtk_dialog_map (GtkWidget *widget) |
628 | { |
629 | GtkWidget *default_widget, *focus; |
630 | GtkWindow *window = GTK_WINDOW (widget); |
631 | GtkDialog *dialog = GTK_DIALOG (widget); |
632 | GtkDialogPrivate *priv = gtk_dialog_get_instance_private (self: dialog); |
633 | ResponseData *rd; |
634 | |
635 | if (gtk_window_get_transient_for (window) == NULL) |
636 | g_message ("GtkDialog mapped without a transient parent. This is discouraged." ); |
637 | |
638 | GTK_WIDGET_CLASS (gtk_dialog_parent_class)->map (widget); |
639 | |
640 | focus = gtk_window_get_focus (window); |
641 | if (!focus) |
642 | { |
643 | GtkWidget *first_focus = NULL; |
644 | |
645 | do |
646 | { |
647 | g_signal_emit_by_name (instance: window, detailed_signal: "move_focus" , GTK_DIR_TAB_FORWARD); |
648 | |
649 | focus = gtk_window_get_focus (window); |
650 | if (GTK_IS_LABEL (focus) && |
651 | !gtk_label_get_current_uri (GTK_LABEL (focus))) |
652 | gtk_label_select_region (GTK_LABEL (focus), start_offset: 0, end_offset: 0); |
653 | |
654 | if (first_focus == NULL) |
655 | first_focus = focus; |
656 | else if (first_focus == focus) |
657 | break; |
658 | |
659 | if (!GTK_IS_LABEL (focus)) |
660 | break; |
661 | } |
662 | while (TRUE); |
663 | |
664 | default_widget = gtk_window_get_default_widget (window); |
665 | for (rd = priv->action_widgets; rd != NULL; rd = rd->next) |
666 | { |
667 | if ((focus == NULL || rd->widget == focus) && |
668 | rd->widget != default_widget && |
669 | default_widget) |
670 | { |
671 | gtk_widget_grab_focus (widget: default_widget); |
672 | break; |
673 | } |
674 | } |
675 | } |
676 | } |
677 | |
678 | static void |
679 | gtk_dialog_close (GtkDialog *dialog) |
680 | { |
681 | gtk_window_close (GTK_WINDOW (dialog)); |
682 | } |
683 | |
684 | /** |
685 | * gtk_dialog_new: |
686 | * |
687 | * Creates a new dialog box. |
688 | * |
689 | * Widgets should not be packed into the `GtkWindow` |
690 | * directly, but into the @content_area and @action_area, |
691 | * as described above. |
692 | * |
693 | * Returns: the new dialog as a `GtkWidget` |
694 | */ |
695 | GtkWidget* |
696 | gtk_dialog_new (void) |
697 | { |
698 | return g_object_new (GTK_TYPE_DIALOG, NULL); |
699 | } |
700 | |
701 | static GtkWidget* |
702 | gtk_dialog_new_empty (const char *title, |
703 | GtkWindow *parent, |
704 | GtkDialogFlags flags) |
705 | { |
706 | GtkDialog *dialog; |
707 | |
708 | dialog = g_object_new (GTK_TYPE_DIALOG, |
709 | first_property_name: "use-header-bar" , (flags & GTK_DIALOG_USE_HEADER_BAR) != 0, |
710 | NULL); |
711 | |
712 | if (title) |
713 | gtk_window_set_title (GTK_WINDOW (dialog), title); |
714 | |
715 | if (parent) |
716 | gtk_window_set_transient_for (GTK_WINDOW (dialog), parent); |
717 | |
718 | if (flags & GTK_DIALOG_MODAL) |
719 | gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); |
720 | |
721 | if (flags & GTK_DIALOG_DESTROY_WITH_PARENT) |
722 | gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE); |
723 | |
724 | return GTK_WIDGET (dialog); |
725 | } |
726 | |
727 | /** |
728 | * gtk_dialog_new_with_buttons: |
729 | * @title: (nullable): Title of the dialog |
730 | * @parent: (nullable): Transient parent of the dialog |
731 | * @flags: from `GtkDialogFlags` |
732 | * @first_button_text: (nullable): text to go in first button |
733 | * @...: response ID for first button, then additional buttons, ending with %NULL |
734 | * |
735 | * Creates a new `GtkDialog` with the given title and transient parent. |
736 | * |
737 | * The @flags argument can be used to make the dialog modal, have it |
738 | * destroyed along with its transient parent, or make it use a headerbar. |
739 | * |
740 | * Button text/response ID pairs should be listed in pairs, with a %NULL |
741 | * pointer ending the list. Button text can be arbitrary text. A response |
742 | * ID can be any positive number, or one of the values in the |
743 | * [enum@Gtk.ResponseType] enumeration. If the user clicks one of these |
744 | * buttons, `GtkDialog` will emit the [signal@Gtk.Dialog::response] signal |
745 | * with the corresponding response ID. |
746 | * |
747 | * If a `GtkDialog` receives a delete event, it will emit ::response with a |
748 | * response ID of %GTK_RESPONSE_DELETE_EVENT. |
749 | * |
750 | * However, destroying a dialog does not emit the ::response signal; |
751 | * so be careful relying on ::response when using the |
752 | * %GTK_DIALOG_DESTROY_WITH_PARENT flag. |
753 | * |
754 | * Here’s a simple example: |
755 | * ```c |
756 | * GtkWindow *main_app_window; // Window the dialog should show up on |
757 | * GtkWidget *dialog; |
758 | * GtkDialogFlags flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT; |
759 | * dialog = gtk_dialog_new_with_buttons ("My dialog", |
760 | * main_app_window, |
761 | * flags, |
762 | * _("_OK"), |
763 | * GTK_RESPONSE_ACCEPT, |
764 | * _("_Cancel"), |
765 | * GTK_RESPONSE_REJECT, |
766 | * NULL); |
767 | * ``` |
768 | * |
769 | * Returns: a new `GtkDialog` |
770 | */ |
771 | GtkWidget* |
772 | gtk_dialog_new_with_buttons (const char *title, |
773 | GtkWindow *parent, |
774 | GtkDialogFlags flags, |
775 | const char *first_button_text, |
776 | ...) |
777 | { |
778 | GtkDialog *dialog; |
779 | va_list args; |
780 | |
781 | dialog = GTK_DIALOG (gtk_dialog_new_empty (title, parent, flags)); |
782 | |
783 | va_start (args, first_button_text); |
784 | |
785 | gtk_dialog_add_buttons_valist (dialog, |
786 | first_button_text, |
787 | args); |
788 | |
789 | va_end (args); |
790 | |
791 | return GTK_WIDGET (dialog); |
792 | } |
793 | |
794 | static void |
795 | response_data_free (gpointer data) |
796 | { |
797 | ResponseData *ad = data; |
798 | GtkDialogPrivate *priv = gtk_dialog_get_instance_private (self: ad->dialog); |
799 | |
800 | if (priv->action_widgets == ad) |
801 | { |
802 | priv->action_widgets = ad->next; |
803 | } |
804 | else |
805 | { |
806 | ResponseData *prev = priv->action_widgets; |
807 | while (prev) |
808 | { |
809 | if (prev->next == ad) |
810 | { |
811 | prev->next = ad->next; |
812 | break; |
813 | } |
814 | prev = prev->next; |
815 | } |
816 | } |
817 | g_slice_free (ResponseData, data); |
818 | } |
819 | |
820 | static ResponseData * |
821 | get_response_data (GtkDialog *dialog, |
822 | GtkWidget *widget, |
823 | gboolean create) |
824 | { |
825 | GtkDialogPrivate *priv = gtk_dialog_get_instance_private (self: dialog); |
826 | |
827 | ResponseData *ad = g_object_get_data (G_OBJECT (widget), |
828 | key: "gtk-dialog-response-data" ); |
829 | |
830 | if (ad == NULL && create) |
831 | { |
832 | ad = g_slice_new (ResponseData); |
833 | ad->dialog = dialog; |
834 | ad->widget = widget; |
835 | g_object_set_data_full (G_OBJECT (widget), |
836 | I_("gtk-dialog-response-data" ), |
837 | data: ad, |
838 | destroy: response_data_free); |
839 | ad->next = priv->action_widgets; |
840 | priv->action_widgets = ad; |
841 | } |
842 | |
843 | return ad; |
844 | } |
845 | |
846 | /** |
847 | * gtk_dialog_add_action_widget: |
848 | * @dialog: a `GtkDialog` |
849 | * @child: an activatable widget |
850 | * @response_id: response ID for @child |
851 | * |
852 | * Adds an activatable widget to the action area of a `GtkDialog`. |
853 | * |
854 | * GTK connects a signal handler that will emit the |
855 | * [signal@Gtk.Dialog::response] signal on the dialog when the widget |
856 | * is activated. The widget is appended to the end of the dialog’s action |
857 | * area. |
858 | * |
859 | * If you want to add a non-activatable widget, simply pack it into |
860 | * the @action_area field of the `GtkDialog` struct. |
861 | */ |
862 | void |
863 | gtk_dialog_add_action_widget (GtkDialog *dialog, |
864 | GtkWidget *child, |
865 | int response_id) |
866 | { |
867 | GtkDialogPrivate *priv = gtk_dialog_get_instance_private (self: dialog); |
868 | |
869 | g_return_if_fail (GTK_IS_DIALOG (dialog)); |
870 | g_return_if_fail (GTK_IS_WIDGET (child)); |
871 | |
872 | add_response_data (dialog, child, response_id); |
873 | |
874 | if (priv->constructed && priv->use_header_bar) |
875 | { |
876 | add_to_header_bar (dialog, child, response_id); |
877 | |
878 | if (gtk_widget_has_default (widget: child)) |
879 | { |
880 | gtk_window_set_default_widget (GTK_WINDOW (dialog), default_widget: child); |
881 | update_suggested_action (dialog, child); |
882 | } |
883 | } |
884 | else |
885 | add_to_action_area (dialog, child, response_id); |
886 | } |
887 | |
888 | /** |
889 | * gtk_dialog_add_button: |
890 | * @dialog: a `GtkDialog` |
891 | * @button_text: text of button |
892 | * @response_id: response ID for the button |
893 | * |
894 | * Adds a button with the given text. |
895 | * |
896 | * GTK arranges things so that clicking the button will emit the |
897 | * [signal@Gtk.Dialog::response] signal with the given @response_id. |
898 | * The button is appended to the end of the dialog’s action area. |
899 | * The button widget is returned, but usually you don’t need it. |
900 | * |
901 | * Returns: (transfer none): the `GtkButton` widget that was added |
902 | */ |
903 | GtkWidget* |
904 | gtk_dialog_add_button (GtkDialog *dialog, |
905 | const char *button_text, |
906 | int response_id) |
907 | { |
908 | GtkWidget *button; |
909 | |
910 | g_return_val_if_fail (GTK_IS_DIALOG (dialog), NULL); |
911 | g_return_val_if_fail (button_text != NULL, NULL); |
912 | |
913 | button = gtk_button_new_with_label (label: button_text); |
914 | gtk_button_set_use_underline (GTK_BUTTON (button), TRUE); |
915 | |
916 | gtk_dialog_add_action_widget (dialog, child: button, response_id); |
917 | |
918 | return button; |
919 | } |
920 | |
921 | static void |
922 | gtk_dialog_add_buttons_valist (GtkDialog *dialog, |
923 | const char *first_button_text, |
924 | va_list args) |
925 | { |
926 | const char * text; |
927 | int response_id; |
928 | |
929 | g_return_if_fail (GTK_IS_DIALOG (dialog)); |
930 | |
931 | if (first_button_text == NULL) |
932 | return; |
933 | |
934 | text = first_button_text; |
935 | response_id = va_arg (args, int); |
936 | |
937 | while (text != NULL) |
938 | { |
939 | gtk_dialog_add_button (dialog, button_text: text, response_id); |
940 | |
941 | text = va_arg (args, char *); |
942 | if (text == NULL) |
943 | break; |
944 | response_id = va_arg (args, int); |
945 | } |
946 | } |
947 | |
948 | /** |
949 | * gtk_dialog_add_buttons: |
950 | * @dialog: a `GtkDialog` |
951 | * @first_button_text: button text |
952 | * @...: response ID for first button, then more text-response_id pairs |
953 | * |
954 | * Adds multiple buttons. |
955 | * |
956 | * This is the same as calling [method@Gtk.Dialog.add_button] |
957 | * repeatedly. The variable argument list should be %NULL-terminated |
958 | * as with [ctor@Gtk.Dialog.new_with_buttons]. Each button must have both |
959 | * text and response ID. |
960 | */ |
961 | void |
962 | gtk_dialog_add_buttons (GtkDialog *dialog, |
963 | const char *first_button_text, |
964 | ...) |
965 | { |
966 | va_list args; |
967 | |
968 | va_start (args, first_button_text); |
969 | |
970 | gtk_dialog_add_buttons_valist (dialog, |
971 | first_button_text, |
972 | args); |
973 | |
974 | va_end (args); |
975 | } |
976 | |
977 | /** |
978 | * gtk_dialog_set_response_sensitive: |
979 | * @dialog: a `GtkDialog` |
980 | * @response_id: a response ID |
981 | * @setting: %TRUE for sensitive |
982 | * |
983 | * A convenient way to sensitize/desensitize dialog buttons. |
984 | * |
985 | * Calls `gtk_widget_set_sensitive (widget, @setting)` |
986 | * for each widget in the dialog’s action area with the given @response_id. |
987 | */ |
988 | void |
989 | gtk_dialog_set_response_sensitive (GtkDialog *dialog, |
990 | int response_id, |
991 | gboolean setting) |
992 | { |
993 | GtkDialogPrivate *priv = gtk_dialog_get_instance_private (self: dialog); |
994 | ResponseData *rd; |
995 | |
996 | g_return_if_fail (GTK_IS_DIALOG (dialog)); |
997 | |
998 | for (rd = priv->action_widgets; rd != NULL; rd = rd->next) |
999 | { |
1000 | if (rd->response_id == response_id) |
1001 | gtk_widget_set_sensitive (widget: rd->widget, sensitive: setting); |
1002 | } |
1003 | } |
1004 | |
1005 | /** |
1006 | * gtk_dialog_set_default_response: |
1007 | * @dialog: a `GtkDialog` |
1008 | * @response_id: a response ID |
1009 | * |
1010 | * Sets the default widget for the dialog based on the response ID. |
1011 | * |
1012 | * Pressing “Enter” normally activates the default widget. |
1013 | */ |
1014 | void |
1015 | gtk_dialog_set_default_response (GtkDialog *dialog, |
1016 | int response_id) |
1017 | { |
1018 | GtkDialogPrivate *priv = gtk_dialog_get_instance_private (self: dialog); |
1019 | ResponseData *rd; |
1020 | |
1021 | g_return_if_fail (GTK_IS_DIALOG (dialog)); |
1022 | |
1023 | for (rd = priv->action_widgets; rd != NULL; rd = rd->next) |
1024 | { |
1025 | if (rd->response_id == response_id) |
1026 | { |
1027 | gtk_window_set_default_widget (GTK_WINDOW (dialog), default_widget: rd->widget); |
1028 | update_suggested_action (dialog, child: rd->widget); |
1029 | } |
1030 | } |
1031 | } |
1032 | |
1033 | /** |
1034 | * gtk_dialog_response: (attributes org.gtk.Method.signal=response) |
1035 | * @dialog: a `GtkDialog` |
1036 | * @response_id: response ID |
1037 | * |
1038 | * Emits the ::response signal with the given response ID. |
1039 | * |
1040 | * Used to indicate that the user has responded to the dialog in some way. |
1041 | */ |
1042 | void |
1043 | gtk_dialog_response (GtkDialog *dialog, |
1044 | int response_id) |
1045 | { |
1046 | g_return_if_fail (GTK_IS_DIALOG (dialog)); |
1047 | |
1048 | g_signal_emit (instance: dialog, |
1049 | signal_id: dialog_signals[RESPONSE], |
1050 | detail: 0, |
1051 | response_id); |
1052 | } |
1053 | |
1054 | /** |
1055 | * gtk_dialog_get_widget_for_response: |
1056 | * @dialog: a `GtkDialog` |
1057 | * @response_id: the response ID used by the @dialog widget |
1058 | * |
1059 | * Gets the widget button that uses the given response ID in the action area |
1060 | * of a dialog. |
1061 | * |
1062 | * Returns: (nullable) (transfer none): the @widget button that uses the given |
1063 | * @response_id |
1064 | */ |
1065 | GtkWidget* |
1066 | gtk_dialog_get_widget_for_response (GtkDialog *dialog, |
1067 | int response_id) |
1068 | { |
1069 | GtkDialogPrivate *priv = gtk_dialog_get_instance_private (self: dialog); |
1070 | ResponseData *rd; |
1071 | |
1072 | g_return_val_if_fail (GTK_IS_DIALOG (dialog), NULL); |
1073 | |
1074 | for (rd = priv->action_widgets; rd != NULL; rd = rd->next) |
1075 | { |
1076 | if (rd->response_id == response_id) |
1077 | return rd->widget; |
1078 | } |
1079 | |
1080 | return NULL; |
1081 | } |
1082 | |
1083 | /** |
1084 | * gtk_dialog_get_response_for_widget: |
1085 | * @dialog: a `GtkDialog` |
1086 | * @widget: a widget in the action area of @dialog |
1087 | * |
1088 | * Gets the response id of a widget in the action area |
1089 | * of a dialog. |
1090 | * |
1091 | * Returns: the response id of @widget, or %GTK_RESPONSE_NONE |
1092 | * if @widget doesn’t have a response id set. |
1093 | */ |
1094 | int |
1095 | gtk_dialog_get_response_for_widget (GtkDialog *dialog, |
1096 | GtkWidget *widget) |
1097 | { |
1098 | ResponseData *rd; |
1099 | |
1100 | rd = get_response_data (dialog, widget, FALSE); |
1101 | if (!rd) |
1102 | return GTK_RESPONSE_NONE; |
1103 | else |
1104 | return rd->response_id; |
1105 | } |
1106 | |
1107 | typedef struct { |
1108 | char *widget_name; |
1109 | int response_id; |
1110 | gboolean is_default; |
1111 | int line; |
1112 | int col; |
1113 | } ActionWidgetInfo; |
1114 | |
1115 | typedef struct { |
1116 | GtkDialog *dialog; |
1117 | GtkBuilder *builder; |
1118 | GSList *items; |
1119 | int response_id; |
1120 | gboolean is_default; |
1121 | gboolean is_text; |
1122 | GString *string; |
1123 | gboolean in_action_widgets; |
1124 | int line; |
1125 | int col; |
1126 | } SubParserData; |
1127 | |
1128 | static void |
1129 | free_action_widget_info (gpointer data) |
1130 | { |
1131 | ActionWidgetInfo *item = data; |
1132 | |
1133 | g_free (mem: item->widget_name); |
1134 | g_free (mem: item); |
1135 | } |
1136 | |
1137 | static void |
1138 | parser_start_element (GtkBuildableParseContext *context, |
1139 | const char *element_name, |
1140 | const char **names, |
1141 | const char **values, |
1142 | gpointer user_data, |
1143 | GError **error) |
1144 | { |
1145 | SubParserData *data = (SubParserData*)user_data; |
1146 | |
1147 | if (strcmp (s1: element_name, s2: "action-widget" ) == 0) |
1148 | { |
1149 | const char *response; |
1150 | gboolean is_default = FALSE; |
1151 | GValue gvalue = G_VALUE_INIT; |
1152 | |
1153 | if (!_gtk_builder_check_parent (builder: data->builder, context, parent_name: "action-widgets" , error)) |
1154 | return; |
1155 | |
1156 | if (!g_markup_collect_attributes (element_name, attribute_names: names, attribute_values: values, error, |
1157 | first_type: G_MARKUP_COLLECT_STRING, first_attr: "response" , &response, |
1158 | G_MARKUP_COLLECT_BOOLEAN|G_MARKUP_COLLECT_OPTIONAL, "default" , &is_default, |
1159 | G_MARKUP_COLLECT_INVALID)) |
1160 | { |
1161 | _gtk_builder_prefix_error (builder: data->builder, context, error); |
1162 | return; |
1163 | } |
1164 | |
1165 | if (!gtk_builder_value_from_string_type (builder: data->builder, type: GTK_TYPE_RESPONSE_TYPE, string: response, value: &gvalue, error)) |
1166 | { |
1167 | _gtk_builder_prefix_error (builder: data->builder, context, error); |
1168 | return; |
1169 | } |
1170 | |
1171 | data->response_id = g_value_get_enum (value: &gvalue); |
1172 | data->is_default = is_default; |
1173 | data->is_text = TRUE; |
1174 | g_string_set_size (string: data->string, len: 0); |
1175 | gtk_buildable_parse_context_get_position (context, line_number: &data->line, char_number: &data->col); |
1176 | } |
1177 | else if (strcmp (s1: element_name, s2: "action-widgets" ) == 0) |
1178 | { |
1179 | if (!_gtk_builder_check_parent (builder: data->builder, context, parent_name: "object" , error)) |
1180 | return; |
1181 | |
1182 | if (!g_markup_collect_attributes (element_name, attribute_names: names, attribute_values: values, error, |
1183 | first_type: G_MARKUP_COLLECT_INVALID, NULL, NULL, |
1184 | G_MARKUP_COLLECT_INVALID)) |
1185 | _gtk_builder_prefix_error (builder: data->builder, context, error); |
1186 | |
1187 | data->in_action_widgets = TRUE; |
1188 | } |
1189 | else |
1190 | { |
1191 | _gtk_builder_error_unhandled_tag (builder: data->builder, context, |
1192 | object: "GtkDialog" , element_name, |
1193 | error); |
1194 | } |
1195 | } |
1196 | |
1197 | static void |
1198 | parser_text_element (GtkBuildableParseContext *context, |
1199 | const char *text, |
1200 | gsize text_len, |
1201 | gpointer user_data, |
1202 | GError **error) |
1203 | { |
1204 | SubParserData *data = (SubParserData*)user_data; |
1205 | |
1206 | if (data->is_text) |
1207 | g_string_append_len (string: data->string, val: text, len: text_len); |
1208 | } |
1209 | |
1210 | static void |
1211 | parser_end_element (GtkBuildableParseContext *context, |
1212 | const char *element_name, |
1213 | gpointer user_data, |
1214 | GError **error) |
1215 | { |
1216 | SubParserData *data = (SubParserData*)user_data; |
1217 | |
1218 | if (data->is_text) |
1219 | { |
1220 | ActionWidgetInfo *item; |
1221 | |
1222 | item = g_new (ActionWidgetInfo, 1); |
1223 | item->widget_name = g_strdup (str: data->string->str); |
1224 | item->response_id = data->response_id; |
1225 | item->is_default = data->is_default; |
1226 | item->line = data->line; |
1227 | item->col = data->col; |
1228 | |
1229 | data->items = g_slist_prepend (list: data->items, data: item); |
1230 | data->is_default = FALSE; |
1231 | data->is_text = FALSE; |
1232 | } |
1233 | } |
1234 | |
1235 | static const GtkBuildableParser sub_parser = |
1236 | { |
1237 | parser_start_element, |
1238 | parser_end_element, |
1239 | parser_text_element, |
1240 | }; |
1241 | |
1242 | static gboolean |
1243 | gtk_dialog_buildable_custom_tag_start (GtkBuildable *buildable, |
1244 | GtkBuilder *builder, |
1245 | GObject *child, |
1246 | const char *tagname, |
1247 | GtkBuildableParser *parser, |
1248 | gpointer *parser_data) |
1249 | { |
1250 | SubParserData *data; |
1251 | |
1252 | if (child) |
1253 | return FALSE; |
1254 | |
1255 | if (strcmp (s1: tagname, s2: "action-widgets" ) == 0) |
1256 | { |
1257 | data = g_slice_new0 (SubParserData); |
1258 | data->dialog = GTK_DIALOG (buildable); |
1259 | data->builder = builder; |
1260 | data->string = g_string_new (init: "" ); |
1261 | data->items = NULL; |
1262 | data->in_action_widgets = FALSE; |
1263 | |
1264 | *parser = sub_parser; |
1265 | *parser_data = data; |
1266 | return TRUE; |
1267 | } |
1268 | |
1269 | return parent_buildable_iface->custom_tag_start (buildable, builder, child, |
1270 | tagname, parser, parser_data); |
1271 | } |
1272 | |
1273 | static void |
1274 | gtk_dialog_buildable_custom_finished (GtkBuildable *buildable, |
1275 | GtkBuilder *builder, |
1276 | GObject *child, |
1277 | const char *tagname, |
1278 | gpointer user_data) |
1279 | { |
1280 | GtkDialog *dialog = GTK_DIALOG (buildable); |
1281 | GtkDialogPrivate *priv = gtk_dialog_get_instance_private (self: dialog); |
1282 | GSList *l; |
1283 | SubParserData *data; |
1284 | GObject *object; |
1285 | ResponseData *ad; |
1286 | guint signal_id; |
1287 | |
1288 | if (strcmp (s1: tagname, s2: "action-widgets" ) != 0) |
1289 | { |
1290 | parent_buildable_iface->custom_finished (buildable, builder, child, |
1291 | tagname, user_data); |
1292 | return; |
1293 | } |
1294 | |
1295 | data = (SubParserData*)user_data; |
1296 | data->items = g_slist_reverse (list: data->items); |
1297 | |
1298 | for (l = data->items; l; l = l->next) |
1299 | { |
1300 | ActionWidgetInfo *item = l->data; |
1301 | gboolean is_action; |
1302 | |
1303 | object = _gtk_builder_lookup_object (builder, name: item->widget_name, line: item->line, col: item->col); |
1304 | if (!object) |
1305 | continue; |
1306 | |
1307 | /* If the widget already has response data at this point, it |
1308 | * was either added by gtk_dialog_add_action_widget(), or via |
1309 | * <child type="action"> or by moving an action area child |
1310 | * to the header bar. In these cases, apply placement heuristics |
1311 | * based on the response id. |
1312 | */ |
1313 | is_action = get_response_data (dialog, GTK_WIDGET (object), FALSE) != NULL; |
1314 | |
1315 | ad = get_response_data (dialog, GTK_WIDGET (object), TRUE); |
1316 | ad->response_id = item->response_id; |
1317 | |
1318 | if (GTK_IS_BUTTON (object)) |
1319 | signal_id = g_signal_lookup (name: "clicked" , GTK_TYPE_BUTTON); |
1320 | else |
1321 | signal_id = gtk_widget_class_get_activate_signal (GTK_WIDGET_GET_CLASS (object)); |
1322 | |
1323 | if (signal_id && !is_action) |
1324 | { |
1325 | GClosure *closure; |
1326 | |
1327 | closure = g_cclosure_new_object (G_CALLBACK (action_widget_activated), |
1328 | G_OBJECT (dialog)); |
1329 | g_signal_connect_closure_by_id (instance: object, signal_id, detail: 0, closure, FALSE); |
1330 | } |
1331 | |
1332 | if (gtk_widget_get_parent (GTK_WIDGET (object)) == priv->action_area) |
1333 | { |
1334 | apply_response_for_action_area (dialog, GTK_WIDGET (object), response_id: ad->response_id); |
1335 | } |
1336 | else if (gtk_widget_get_ancestor (GTK_WIDGET (object), GTK_TYPE_HEADER_BAR) == priv->headerbar) |
1337 | { |
1338 | if (is_action) |
1339 | { |
1340 | g_object_ref (object); |
1341 | gtk_header_bar_remove (GTK_HEADER_BAR (priv->headerbar), GTK_WIDGET (object)); |
1342 | add_to_header_bar (dialog, GTK_WIDGET (object), response_id: ad->response_id); |
1343 | g_object_unref (object); |
1344 | } |
1345 | } |
1346 | |
1347 | if (item->is_default) |
1348 | { |
1349 | gtk_window_set_default_widget (GTK_WINDOW (dialog), GTK_WIDGET (object)); |
1350 | update_suggested_action (dialog, GTK_WIDGET (object)); |
1351 | } |
1352 | } |
1353 | |
1354 | g_slist_free_full (list: data->items, free_func: free_action_widget_info); |
1355 | g_string_free (string: data->string, TRUE); |
1356 | g_slice_free (SubParserData, data); |
1357 | } |
1358 | |
1359 | static void |
1360 | gtk_dialog_buildable_add_child (GtkBuildable *buildable, |
1361 | GtkBuilder *builder, |
1362 | GObject *child, |
1363 | const char *type) |
1364 | { |
1365 | GtkDialog *dialog = GTK_DIALOG (buildable); |
1366 | GtkDialogPrivate *priv = gtk_dialog_get_instance_private (self: dialog); |
1367 | |
1368 | if (type == NULL) |
1369 | parent_buildable_iface->add_child (buildable, builder, child, type); |
1370 | else if (g_str_equal (v1: type, v2: "titlebar" )) |
1371 | { |
1372 | priv->headerbar = GTK_WIDGET (child); |
1373 | _gtk_header_bar_track_default_decoration (GTK_HEADER_BAR (priv->headerbar)); |
1374 | gtk_window_set_titlebar (GTK_WINDOW (buildable), titlebar: priv->headerbar); |
1375 | } |
1376 | else if (g_str_equal (v1: type, v2: "action" )) |
1377 | gtk_dialog_add_action_widget (GTK_DIALOG (buildable), GTK_WIDGET (child), response_id: GTK_RESPONSE_NONE); |
1378 | else |
1379 | GTK_BUILDER_WARN_INVALID_CHILD_TYPE (buildable, type); |
1380 | } |
1381 | |
1382 | GtkWidget * |
1383 | gtk_dialog_get_action_area (GtkDialog *dialog) |
1384 | { |
1385 | GtkDialogPrivate *priv = gtk_dialog_get_instance_private (self: dialog); |
1386 | |
1387 | g_return_val_if_fail (GTK_IS_DIALOG (dialog), NULL); |
1388 | |
1389 | return priv->action_area; |
1390 | } |
1391 | |
1392 | /** |
1393 | * gtk_dialog_get_header_bar: |
1394 | * @dialog: a `GtkDialog` |
1395 | * |
1396 | * Returns the header bar of @dialog. |
1397 | * |
1398 | * Note that the headerbar is only used by the dialog if the |
1399 | * [property@Gtk.Dialog:use-header-bar] property is %TRUE. |
1400 | * |
1401 | * Returns: (type Gtk.HeaderBar) (transfer none): the header bar |
1402 | */ |
1403 | GtkWidget * |
1404 | (GtkDialog *dialog) |
1405 | { |
1406 | GtkDialogPrivate *priv = gtk_dialog_get_instance_private (self: dialog); |
1407 | |
1408 | g_return_val_if_fail (GTK_IS_DIALOG (dialog), NULL); |
1409 | |
1410 | return priv->headerbar; |
1411 | } |
1412 | |
1413 | /** |
1414 | * gtk_dialog_get_content_area: |
1415 | * @dialog: a `GtkDialog` |
1416 | * |
1417 | * Returns the content area of @dialog. |
1418 | * |
1419 | * Returns: (type Gtk.Box) (transfer none): the content area `GtkBox`. |
1420 | */ |
1421 | GtkWidget * |
1422 | gtk_dialog_get_content_area (GtkDialog *dialog) |
1423 | { |
1424 | GtkDialogPrivate *priv = gtk_dialog_get_instance_private (self: dialog); |
1425 | |
1426 | g_return_val_if_fail (GTK_IS_DIALOG (dialog), NULL); |
1427 | |
1428 | return priv->content_area; |
1429 | } |
1430 | |