gtkdialog.c 60.4 KB
Newer Older
Cody Russell's avatar
Cody Russell committed
1
/* GTK - The GIMP Toolkit
Elliot Lee's avatar
Elliot Lee committed
2 3 4
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * This library is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
Elliot Lee's avatar
Elliot Lee committed
6 7 8 9 10 11
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12
 * Lesser General Public License for more details.
Elliot Lee's avatar
Elliot Lee committed
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
Javier Jardón's avatar
Javier Jardón committed
15
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
Elliot Lee's avatar
Elliot Lee committed
16
 */
17 18

/*
19
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
20 21
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
22
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
23 24
 */

25 26
#include "config.h"

27 28
#include <stdlib.h>
#include <string.h>
29

Elliot Lee's avatar
Elliot Lee committed
30 31
#include "gtkbutton.h"
#include "gtkdialog.h"
32
#include "gtkdialogprivate.h"
33
#include "gtkheaderbar.h"
Matthias Clasen's avatar
Matthias Clasen committed
34
#include "gtkbbox.h"
35
#include "gtklabel.h"
36
#include "gtkmarshalers.h"
37
#include "gtkbox.h"
38
#include "gtkboxprivate.h"
39
#include "gtkcontainerprivate.h"
40
#include "gtkmain.h"
41
#include "gtkintl.h"
42
#include "gtkbindings.h"
43
#include "gtkprivate.h"
44
#include "gtkbuildable.h"
45
#include "gtkbuilderprivate.h"
46
#include "gtksettings.h"
47
#include "gtktypebuiltins.h"
48
#include "deprecated/gtkstock.h"
49
#include "gtksizegroup.h"
Elliot Lee's avatar
Elliot Lee committed
50

51 52 53 54 55 56 57 58
/**
 * SECTION:gtkdialog
 * @Short_description: Create popup windows
 * @Title: GtkDialog
 * @See_also: #GtkVBox, #GtkWindow, #GtkButton
 *
 * Dialog boxes are a convenient way to prompt the user for a small amount
 * of input, e.g. to display a message, ask a question, or anything else
59
 * that does not require extensive effort on the user’s part.
60 61 62 63
 *
 * GTK+ treats a dialog as a window split vertically. The top section is a
 * #GtkVBox, and is where widgets such as a #GtkLabel or a #GtkEntry should
 * be packed. The bottom area is known as the
64
 * “action area”. This is generally used for
65
 * packing buttons into the dialog which may perform functions such as
66
 * cancel, ok, or apply.
67 68 69
 *
 * #GtkDialog boxes are created with a call to gtk_dialog_new() or
 * gtk_dialog_new_with_buttons(). gtk_dialog_new_with_buttons() is
70 71
 * recommended; it allows you to set the dialog title, some convenient
 * flags, and add simple buttons.
72
 *
73
 * If “dialog” is a newly created dialog, the two primary areas of the
74 75 76
 * window can be accessed through gtk_dialog_get_content_area() and
 * gtk_dialog_get_action_area(), as can be seen from the example below.
 *
77
 * A “modal” dialog (that is, one which freezes the rest of the application
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
 * from user input), can be created by calling gtk_window_set_modal() on the
 * dialog. Use the GTK_WINDOW() macro to cast the widget returned from
 * gtk_dialog_new() into a #GtkWindow. When using gtk_dialog_new_with_buttons()
 * you can also pass the #GTK_DIALOG_MODAL flag to make a dialog modal.
 *
 * If you add buttons to #GtkDialog using gtk_dialog_new_with_buttons(),
 * gtk_dialog_add_button(), gtk_dialog_add_buttons(), or
 * gtk_dialog_add_action_widget(), clicking the button will emit a signal
 * called #GtkDialog::response with a response ID that you specified. GTK+
 * will never assign a meaning to positive response IDs; these are entirely
 * user-defined. But for convenience, you can use the response IDs in the
 * #GtkResponseType enumeration (these all have values less than zero). If
 * a dialog receives a delete event, the #GtkDialog::response signal will
 * be emitted with a response ID of #GTK_RESPONSE_DELETE_EVENT.
 *
 * If you want to block waiting for a dialog to return before returning
 * control flow to your code, you can call gtk_dialog_run(). This function
 * enters a recursive main loop and waits for the user to respond to the
 * dialog, returning the response ID corresponding to the button the user
 * clicked.
 *
99 100
 * For the simple dialog in the following example, in reality you’d probably
 * use #GtkMessageDialog to save yourself some effort. But you’d need to
101 102
 * create the dialog contents manually if you had more than a simple message
 * in the dialog.
103 104
 *
 * An example for simple GtkDialog usage:
105
 * |[<!-- language="C" -->
106
 * // Function to open a dialog box with a message
107
 * void
108
 * quick_message (GtkWindow *parent, gchar *message)
109
 * {
110 111
 *  GtkWidget *dialog, *label, *content_area;
 *  GtkDialogFlags flags;
112
 *
113
 *  // Create the widgets
114 115 116 117 118 119 120 121 122
 *  flags = GTK_DIALOG_DESTROY_WITH_PARENT;
 *  dialog = gtk_dialog_new_with_buttons ("Message",
 *                                        parent,
 *                                        flags,
 *                                        _("_OK"),
 *                                        GTK_RESPONSE_NONE,
 *                                        NULL);
 *  content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
 *  label = gtk_label_new (message);
123
 *
124
 *  // Ensure that the dialog box is destroyed when the user responds
125
 *
126 127 128 129
 *  g_signal_connect_swapped (dialog,
 *                            "response",
 *                            G_CALLBACK (gtk_widget_destroy),
 *                            dialog);
130
 *
131
 *  // Add the label, and show everything we’ve added
132 133 134
 *
 *  gtk_container_add (GTK_CONTAINER (content_area), label);
 *  gtk_widget_show_all (dialog);
135
 * }
136
 * ]|
137
 *
138
 * # GtkDialog as GtkBuildable
139
 *
140
 * The GtkDialog implementation of the #GtkBuildable interface exposes the
141 142
 * @vbox and @action_area as internal children with the names “vbox” and
 * “action_area”.
143
 *
144 145 146
 * GtkDialog supports a custom <action-widgets> element, which can contain
 * multiple <action-widget> elements. The “response” attribute specifies a
 * numeric response, and the content of the element is the id of widget
147 148 149 150 151 152 153 154 155
 * (which should be a child of the dialogs @action_area). To mark a response
 * as default, set the “default“ attribute of the <action-widget> element
 * to true.
 *
 * GtkDialog supports adding action widgets by specifying “action“ as
 * the “type“ attribute of a <child> element. The widget will be added
 * either to the action area or the headerbar of the dialog, depending
 * on the “use-header-bar“ property. The response id has to be associated
 * with the action widget using the <action-widgets> element.
156 157
 *
 * An example of a #GtkDialog UI definition fragment:
158
 * |[
159
 * <object class="GtkDialog" id="dialog1">
160 161 162 163
 *   <child type="action">
 *     <object class="GtkButton" id="button_cancel"/>
 *   </child>
 *   <child type="action">
Matthias Clasen's avatar
Matthias Clasen committed
164 165 166
 *     <object class="GtkButton" id="button_ok">
 *       <property name="can-default">True</property>
 *     </object>
167 168
 *   </child>
 *   <action-widgets>
169
 *     <action-widget response="cancel">button_cancel</action-widget>
170
 *     <action-widget response="ok" default="true">button_ok</action-widget>
171 172
 *   </action-widgets>
 * </object>
173
 * ]|
174
 */
Federico Mena Quintero's avatar
Federico Mena Quintero committed
175

176
struct _GtkDialogPrivate
177 178
{
  GtkWidget *vbox;
179
  GtkWidget *headerbar;
180
  GtkWidget *action_area;
181
  GtkWidget *action_box;
182
  GtkSizeGroup *size_group;
183

184
  gint use_header_bar;
185
  gboolean constructed;
186
};
Federico Mena Quintero's avatar
Federico Mena Quintero committed
187

188 189 190 191 192 193 194
typedef struct _ResponseData ResponseData;

struct _ResponseData
{
  gint response_id;
};

195 196 197 198 199 200 201
static void      gtk_dialog_add_buttons_valist   (GtkDialog    *dialog,
                                                  const gchar  *first_button_text,
                                                  va_list       args);

static gboolean  gtk_dialog_delete_event_handler (GtkWidget    *widget,
                                                  GdkEventAny  *event,
                                                  gpointer      user_data);
202
static void      gtk_dialog_style_updated        (GtkWidget    *widget);
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
static void      gtk_dialog_map                  (GtkWidget    *widget);

static void      gtk_dialog_close                (GtkDialog    *dialog);

static ResponseData * get_response_data          (GtkWidget    *widget,
                                                  gboolean      create);

static void      gtk_dialog_buildable_interface_init     (GtkBuildableIface *iface);
static gboolean  gtk_dialog_buildable_custom_tag_start   (GtkBuildable  *buildable,
                                                          GtkBuilder    *builder,
                                                          GObject       *child,
                                                          const gchar   *tagname,
                                                          GMarkupParser *parser,
                                                          gpointer      *data);
static void      gtk_dialog_buildable_custom_finished    (GtkBuildable  *buildable,
                                                          GtkBuilder    *builder,
                                                          GObject       *child,
                                                          const gchar   *tagname,
                                                          gpointer       user_data);
222 223 224 225
static void      gtk_dialog_buildable_add_child          (GtkBuildable  *buildable,
                                                          GtkBuilder    *builder,
                                                          GObject       *child,
                                                          const gchar   *type);
226

227

228 229
enum {
  PROP_0,
230
  PROP_USE_HEADER_BAR
231
};
232 233 234

enum {
  RESPONSE,
235
  CLOSE,
236 237
  LAST_SIGNAL
};
Elliot Lee's avatar
Elliot Lee committed
238

239
static guint dialog_signals[LAST_SIGNAL];
Elliot Lee's avatar
Elliot Lee committed
240

241
G_DEFINE_TYPE_WITH_CODE (GtkDialog, gtk_dialog, GTK_TYPE_WINDOW,
242
                         G_ADD_PRIVATE (GtkDialog)
243 244
			 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
						gtk_dialog_buildable_interface_init))
Elliot Lee's avatar
Elliot Lee committed
245

246 247
static void
set_use_header_bar (GtkDialog *dialog,
248
                    gint       use_header_bar)
249 250 251
{
  GtkDialogPrivate *priv = dialog->priv;

252 253 254
  if (use_header_bar == -1)
    return;

255
  priv->use_header_bar = use_header_bar;
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
}

/* A convenience helper for built-in dialogs */
void
gtk_dialog_set_use_header_bar_from_setting (GtkDialog *dialog)
{
  GtkDialogPrivate *priv = dialog->priv;

  g_assert (!priv->constructed);

  g_object_get (gtk_widget_get_settings (GTK_WIDGET (dialog)),
                "gtk-dialogs-use-header", &priv->use_header_bar,
                NULL);
}

271 272 273 274 275 276 277 278 279 280 281
static void
gtk_dialog_set_property (GObject      *object,
                         guint         prop_id,
                         const GValue *value,
                         GParamSpec   *pspec)
{
  GtkDialog *dialog = GTK_DIALOG (object);

  switch (prop_id)
    {
    case PROP_USE_HEADER_BAR:
282
      set_use_header_bar (dialog, g_value_get_int (value));
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
gtk_dialog_get_property (GObject      *object,
                         guint         prop_id,
                         GValue       *value,
                         GParamSpec   *pspec)
{
  GtkDialog *dialog = GTK_DIALOG (object);
  GtkDialogPrivate *priv = dialog->priv;

  switch (prop_id)
    {
    case PROP_USE_HEADER_BAR:
303
      g_value_set_int (value, priv->use_header_bar);
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
action_widget_activated (GtkWidget *widget, GtkDialog *dialog)
{
  gint response_id;

  response_id = gtk_dialog_get_response_for_widget (dialog, widget);

  gtk_dialog_response (dialog, response_id);
}

typedef struct {
  GtkWidget *child;
  gint       response_id;
} ActionWidgetData;

static void
add_response_data (GtkDialog *dialog,
                   GtkWidget *child,
                   gint       response_id)
{
  ResponseData *ad;
  guint signal_id;

  ad = get_response_data (child, TRUE);
  ad->response_id = response_id;

  if (GTK_IS_BUTTON (child))
    signal_id = g_signal_lookup ("clicked", GTK_TYPE_BUTTON);
  else
    signal_id = GTK_WIDGET_GET_CLASS (child)->activate_signal;

  if (signal_id)
    {
      GClosure *closure;

      closure = g_cclosure_new_object (G_CALLBACK (action_widget_activated),
                                       G_OBJECT (dialog));
      g_signal_connect_closure_by_id (child, signal_id, 0, closure, FALSE);
    }
  else
    g_warning ("Only 'activatable' widgets can be packed into the action area of a GtkDialog");
}

355 356 357 358 359 360 361 362
static void
apply_response_for_header_bar (GtkDialog *dialog,
                               GtkWidget *child,
                               gint       response_id)
{
  GtkDialogPrivate *priv = dialog->priv;
  GtkPackType pack;

363 364
  g_assert (gtk_widget_get_parent (child) == priv->headerbar);

365 366 367 368 369 370 371 372 373 374 375 376 377
  if (response_id == GTK_RESPONSE_CANCEL || response_id == GTK_RESPONSE_HELP)
    pack = GTK_PACK_START;
  else
    pack = GTK_PACK_END;

  gtk_container_child_set (GTK_CONTAINER (priv->headerbar), child,
                           "pack-type", pack,
                           NULL);

  if (response_id == GTK_RESPONSE_CANCEL || response_id == GTK_RESPONSE_CLOSE)
    gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (priv->headerbar), FALSE);
}

378 379 380 381 382 383 384 385
static void
add_to_header_bar (GtkDialog *dialog,
                   GtkWidget *child,
                   gint       response_id)
{
  GtkDialogPrivate *priv = dialog->priv;

  gtk_widget_set_valign (child, GTK_ALIGN_CENTER);
386 387 388
  gtk_container_add (GTK_CONTAINER (priv->headerbar), child);
  gtk_size_group_add_widget (priv->size_group, child);
  apply_response_for_header_bar (dialog, child, response_id);
389

390
}
391

392 393 394 395 396 397
static void
apply_response_for_action_area (GtkDialog *dialog,
                                GtkWidget *child,
                                gint       response_id)
{
  GtkDialogPrivate *priv = dialog->priv;
398

399 400
  g_assert (gtk_widget_get_parent (child) == priv->action_area);

401 402
  if (response_id == GTK_RESPONSE_HELP)
    gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (priv->action_area), child, TRUE);
403 404 405 406 407 408 409 410 411 412 413
}

static void
add_to_action_area (GtkDialog *dialog,
                    GtkWidget *child,
                    gint       response_id)
{
  GtkDialogPrivate *priv = dialog->priv;

  gtk_widget_set_valign (child, GTK_ALIGN_BASELINE);
  gtk_container_add (GTK_CONTAINER (priv->action_area), child);
414
  apply_response_for_action_area (dialog, child, response_id);
415 416
}

417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440
static void
update_suggested_action (GtkDialog *dialog)
{
  GtkDialogPrivate *priv = dialog->priv;

  if (priv->use_header_bar)
    {
      GList *children, *l;

      children = gtk_container_get_children (GTK_CONTAINER (priv->headerbar));
      for (l = children; l != NULL; l = l->next)
        {
          GtkWidget *child = l->data;
	  GtkStyleContext *context = gtk_widget_get_style_context (child);

          if (gtk_style_context_has_class (context, GTK_STYLE_CLASS_DEFAULT))
            gtk_style_context_add_class (context, GTK_STYLE_CLASS_SUGGESTED_ACTION);
          else
            gtk_style_context_remove_class (context, GTK_STYLE_CLASS_SUGGESTED_ACTION);
        }
      g_list_free (children);
    }
}

441
static void
442 443 444
add_cb (GtkContainer *container,
        GtkWidget    *widget,
        GtkDialog    *dialog)
445 446
{
  GtkDialogPrivate *priv = dialog->priv;
447 448 449 450

  if (priv->use_header_bar)
    g_warning ("Content added to the action area of a dialog using header bars");

451 452
  gtk_widget_set_visible (priv->action_box, TRUE);
  gtk_widget_set_no_show_all (priv->action_box, FALSE);
453 454 455 456 457 458 459 460 461 462 463 464 465
}

static void
gtk_dialog_constructed (GObject *object)
{
  GtkDialog *dialog = GTK_DIALOG (object);
  GtkDialogPrivate *priv = dialog->priv;

  G_OBJECT_CLASS (gtk_dialog_parent_class)->constructed (object);

  priv->constructed = TRUE;
  if (priv->use_header_bar == -1)
    priv->use_header_bar = FALSE;
466 467 468

  if (priv->use_header_bar)
    {
469 470
      GList *children, *l;

471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
      children = gtk_container_get_children (GTK_CONTAINER (priv->action_area));
      for (l = children; l != NULL; l = l->next)
        {
          GtkWidget *child = l->data;
          gboolean has_default;
          ResponseData *rd;
          gint response_id;

          has_default = gtk_widget_has_default (child);
          rd = get_response_data (child, FALSE);
          response_id = rd ? rd->response_id : GTK_RESPONSE_NONE;

          g_object_ref (child);
          gtk_container_remove (GTK_CONTAINER (priv->action_area), child);
          add_to_header_bar (dialog, child, response_id);
          g_object_unref (child);

          if (has_default)
489
            gtk_widget_grab_default (child);
490 491
        }
      g_list_free (children);
492 493

      update_suggested_action (dialog);
494

495 496
      g_signal_connect (priv->action_area, "add", G_CALLBACK (add_cb), dialog);
    }
497 498 499 500 501
  else
    {
      gtk_window_set_titlebar (GTK_WINDOW (dialog), NULL);
      priv->headerbar = NULL;
    }
502

503
  gtk_widget_set_visible (priv->action_box, !priv->use_header_bar);
504
  gtk_widget_set_no_show_all (priv->action_box, priv->use_header_bar);
505 506
}

507 508 509 510 511 512 513 514 515 516
static void
gtk_dialog_finalize (GObject *obj)
{
  GtkDialog *dialog = GTK_DIALOG (obj);

  g_object_unref (dialog->priv->size_group);

  G_OBJECT_CLASS (gtk_dialog_parent_class)->finalize (obj);
}

Elliot Lee's avatar
Elliot Lee committed
517 518 519
static void
gtk_dialog_class_init (GtkDialogClass *class)
{
520
  GObjectClass *gobject_class;
521
  GtkWidgetClass *widget_class;
522
  GtkBindingSet *binding_set;
523

524
  gobject_class = G_OBJECT_CLASS (class);
525
  widget_class = GTK_WIDGET_CLASS (class);
526

527
  gobject_class->constructed  = gtk_dialog_constructed;
528 529
  gobject_class->set_property = gtk_dialog_set_property;
  gobject_class->get_property = gtk_dialog_get_property;
530
  gobject_class->finalize = gtk_dialog_finalize;
531

532
  widget_class->map = gtk_dialog_map;
533
  widget_class->style_updated = gtk_dialog_style_updated;
534

535 536
  gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_DIALOG);

537
  class->close = gtk_dialog_close;
Federico Mena Quintero's avatar
Federico Mena Quintero committed
538

539 540 541 542
  /**
   * GtkDialog::response:
   * @dialog: the object on which the signal is emitted
   * @response_id: the response ID
543 544 545 546
   *
   * Emitted when an action widget is clicked, the dialog receives a
   * delete event, or the application programmer calls gtk_dialog_response().
   * On a delete event, the response ID is #GTK_RESPONSE_DELETE_EVENT.
547 548
   * Otherwise, it depends on which action widget was clicked.
   */
549
  dialog_signals[RESPONSE] =
550
    g_signal_new (I_("response"),
Manish Singh's avatar
Manish Singh committed
551 552 553 554
		  G_OBJECT_CLASS_TYPE (class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (GtkDialogClass, response),
		  NULL, NULL,
555
		  NULL,
Manish Singh's avatar
Manish Singh committed
556 557
		  G_TYPE_NONE, 1,
		  G_TYPE_INT);
558

559 560 561
  /**
   * GtkDialog::close:
   *
562
   * The ::close signal is a
563
   * [keybinding signal][GtkBindingSignal]
564
   * which gets emitted when the user uses a keybinding to close
565 566 567
   * the dialog.
   *
   * The default binding for this signal is the Escape key.
568
   */
569
  dialog_signals[CLOSE] =
570
    g_signal_new (I_("close"),
Manish Singh's avatar
Manish Singh committed
571 572 573 574
		  G_OBJECT_CLASS_TYPE (class),
		  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
		  G_STRUCT_OFFSET (GtkDialogClass, close),
		  NULL, NULL,
575
		  NULL,
Manish Singh's avatar
Manish Singh committed
576
		  G_TYPE_NONE, 0);
577

578 579 580 581 582 583 584 585
  /**
   * GtkDialog:content-area-border:
   *
   * The default border width used around the
   * content area of the dialog, as returned by
   * gtk_dialog_get_content_area(), unless gtk_container_set_border_width()
   * was called on that widget directly.
   */
586
  gtk_widget_class_install_style_property (widget_class,
587
					   g_param_spec_int ("content-area-border",
588 589
                                                             P_("Content area border"),
                                                             P_("Width of border around the main dialog area"),
590 591 592
                                                             0,
                                                             G_MAXINT,
                                                             2,
593
                                                             GTK_PARAM_READABLE));
594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611
  /**
   * GtkDialog:content-area-spacing:
   *
   * The default spacing used between elements of the
   * content area of the dialog, as returned by
   * gtk_dialog_get_content_area(), unless gtk_box_set_spacing()
   * was called on that widget directly.
   *
   * Since: 2.16
   */
  gtk_widget_class_install_style_property (widget_class,
                                           g_param_spec_int ("content-area-spacing",
                                                             P_("Content area spacing"),
                                                             P_("Spacing between elements of the main dialog area"),
                                                             0,
                                                             G_MAXINT,
                                                             0,
                                                             GTK_PARAM_READABLE));
612
  gtk_widget_class_install_style_property (widget_class,
613
                                           g_param_spec_int ("button-spacing",
614 615
                                                             P_("Button spacing"),
                                                             P_("Spacing between buttons"),
616 617
                                                             0,
                                                             G_MAXINT,
618
                                                             6,
619
                                                             GTK_PARAM_READABLE));
620

621 622 623 624 625 626 627 628
  /**
   * GtkDialog:action-area-border:
   *
   * The default border width used around the
   * action area of the dialog, as returned by
   * gtk_dialog_get_action_area(), unless gtk_container_set_border_width()
   * was called on that widget directly.
   */
629
  gtk_widget_class_install_style_property (widget_class,
630
                                           g_param_spec_int ("action-area-border",
631 632
                                                             P_("Action area border"),
                                                             P_("Width of border around the button area at the bottom of the dialog"),
633 634
                                                             0,
                                                             G_MAXINT,
635
                                                             5,
636
                                                             GTK_PARAM_READABLE));
637

638 639 640 641 642 643
  /**
   * GtkDialog:use-header-bar:
   *
   * %TRUE if the dialog uses a #GtkHeaderBar for action buttons
   * instead of the action-area.
   *
644 645 646
   * For technical reasons, this property is declared as an integer
   * property, but you should only set it to %TRUE or %FALSE.
   *
647 648 649 650
   * Since: 3.12
   */
  g_object_class_install_property (gobject_class,
                                   PROP_USE_HEADER_BAR,
651 652 653 654 655
                                   g_param_spec_int ("use-header-bar",
                                                     P_("Use Header Bar"),
                                                     P_("Use Header Bar for actions."),
                                                     -1, 1, -1,
                                                     GTK_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY));
656

657
  binding_set = gtk_binding_set_by_class (class);
658
  gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0, "close", 0);
659 660 661

  /* Bind class to template
   */
662
  gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkdialog.ui");
663
  gtk_widget_class_bind_template_child_internal_private (widget_class, GtkDialog, vbox);
664
  gtk_widget_class_bind_template_child_internal_private (widget_class, GtkDialog, headerbar);
665
  gtk_widget_class_bind_template_child_internal_private (widget_class, GtkDialog, action_area);
666
  gtk_widget_class_bind_template_child_private (widget_class, GtkDialog, action_box);
667
  gtk_widget_class_bind_template_callback (widget_class, gtk_dialog_delete_event_handler);
668 669

  gtk_widget_class_set_css_name (widget_class, "dialog");
670 671 672 673 674
}

static void
update_spacings (GtkDialog *dialog)
{
675
  GtkDialogPrivate *priv = dialog->priv;
676
  gint content_area_border;
677
  gint content_area_spacing;
678 679 680
  gint button_spacing;
  gint action_area_border;

681
  gtk_widget_style_get (GTK_WIDGET (dialog),
682
                        "content-area-border", &content_area_border,
683
                        "content-area-spacing", &content_area_spacing,
684 685
                        "button-spacing", &button_spacing,
                        "action-area-border", &action_area_border,
686
                        NULL);
687 688

  if (!_gtk_container_get_border_width_set (GTK_CONTAINER (priv->vbox)))
689 690 691 692 693
    {
      gtk_container_set_border_width (GTK_CONTAINER (priv->vbox),
                                      content_area_border);
      _gtk_container_set_border_width_set (GTK_CONTAINER (priv->vbox), FALSE);
    }
694

695
  if (!_gtk_box_get_spacing_set (GTK_BOX (priv->vbox)))
696
    {
697 698
      gtk_box_set_spacing (GTK_BOX (priv->vbox), content_area_spacing);
      _gtk_box_set_spacing_set (GTK_BOX (priv->vbox), FALSE);
699
    }
700

701 702 703
  /* don't set spacing when buttons are linked */
  if (gtk_button_box_get_layout (GTK_BUTTON_BOX (priv->action_area)) != GTK_BUTTONBOX_EXPAND)
    gtk_box_set_spacing (GTK_BOX (priv->action_area), button_spacing);
704 705

  if (!_gtk_container_get_border_width_set (GTK_CONTAINER (priv->action_area)))
706 707 708 709 710
    {
      gtk_container_set_border_width (GTK_CONTAINER (priv->action_area),
                                      action_area_border);
      _gtk_container_set_border_width_set (GTK_CONTAINER (priv->action_area), FALSE);
    }
Elliot Lee's avatar
Elliot Lee committed
711 712 713 714 715
}

static void
gtk_dialog_init (GtkDialog *dialog)
{
716
  dialog->priv = gtk_dialog_get_instance_private (dialog);
Elliot Lee's avatar
Elliot Lee committed
717

718
  dialog->priv->use_header_bar = -1;
719
  dialog->priv->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
720

721
  gtk_widget_init_template (GTK_WIDGET (dialog));
722 723

  update_spacings (dialog);
Elliot Lee's avatar
Elliot Lee committed
724 725
}

726 727
static GtkBuildableIface *parent_buildable_iface;

728 729 730
static void
gtk_dialog_buildable_interface_init (GtkBuildableIface *iface)
{
731
  parent_buildable_iface = g_type_interface_peek_parent (iface);
732 733
  iface->custom_tag_start = gtk_dialog_buildable_custom_tag_start;
  iface->custom_finished = gtk_dialog_buildable_custom_finished;
734
  iface->add_child = gtk_dialog_buildable_add_child;
735 736
}

737
static gboolean
738 739 740 741 742
gtk_dialog_delete_event_handler (GtkWidget   *widget,
                                 GdkEventAny *event,
                                 gpointer     user_data)
{
  /* emit response signal */
743
  gtk_dialog_response (GTK_DIALOG (widget), GTK_RESPONSE_DELETE_EVENT);
744 745 746 747 748

  /* Do the destroy by default */
  return FALSE;
}

749 750 751 752 753 754 755 756 757 758 759 760 761 762
static GList *
get_action_children (GtkDialog *dialog)
{
  GtkDialogPrivate *priv = dialog->priv;
  GList *children;

  if (priv->constructed && priv->use_header_bar)
    children = gtk_container_get_children (GTK_CONTAINER (priv->headerbar));
  else
    children = gtk_container_get_children (GTK_CONTAINER (priv->action_area));

  return children;
}

763 764 765 766 767
/* A far too tricky heuristic for getting the right initial
 * focus widget if none was set. What we do is we focus the first
 * widget in the tab chain, but if this results in the focus
 * ending up on one of the response widgets _other_ than the
 * default response, we focus the default response instead.
768 769 770
 *
 * Additionally, skip selectable labels when looking for the
 * right initial focus widget.
771 772 773 774
 */
static void
gtk_dialog_map (GtkWidget *widget)
{
775
  GtkWidget *default_widget, *focus;
776 777
  GtkWindow *window = GTK_WINDOW (widget);
  GtkDialog *dialog = GTK_DIALOG (widget);
778

779
  if (gtk_window_get_transient_for (window) == NULL)
780
    g_message ("GtkDialog mapped without a transient parent. This is discouraged.");
781

Matthias Clasen's avatar
Matthias Clasen committed
782
  GTK_WIDGET_CLASS (gtk_dialog_parent_class)->map (widget);
783

784 785
  focus = gtk_window_get_focus (window);
  if (!focus)
786 787
    {
      GList *children, *tmp_list;
788
      GtkWidget *first_focus = NULL;
789 790 791 792

      do
        {
          g_signal_emit_by_name (window, "move_focus", GTK_DIR_TAB_FORWARD);
793

794
          focus = gtk_window_get_focus (window);
795 796 797 798
          if (GTK_IS_LABEL (focus) &&
              !gtk_label_get_current_uri (GTK_LABEL (focus)))
            gtk_label_select_region (GTK_LABEL (focus), 0, 0);

799
          if (first_focus == NULL)
800
            first_focus = focus;
801
          else if (first_focus == focus)
802 803
            break;

804
          if (!GTK_IS_LABEL (focus))
805
            break;
806
        }
807
      while (TRUE);
808

809
      tmp_list = children = get_action_children (dialog);
810

811 812 813
      while (tmp_list)
	{
	  GtkWidget *child = tmp_list->data;
814 815 816 817 818

          default_widget = gtk_window_get_default_widget (window);
	  if ((focus == NULL || child == focus) &&
	      child != default_widget &&
	      default_widget)
819
	    {
820
	      gtk_widget_grab_focus (default_widget);
821 822
	      break;
	    }
823

824 825
	  tmp_list = tmp_list->next;
	}
826

827 828 829 830
      g_list_free (children);
    }
}

831
static void
832
gtk_dialog_style_updated (GtkWidget *widget)
833
{
834 835
  GTK_WIDGET_CLASS (gtk_dialog_parent_class)->style_updated (widget);

836 837 838
  update_spacings (GTK_DIALOG (widget));
}

839 840
static GtkWidget *
dialog_find_button (GtkDialog *dialog,
841
                   gint       response_id)
842
{
843
  GtkWidget *child = NULL;
844
  GList *children, *tmp_list;
845

846
  children = get_action_children (dialog);
847 848 849

  for (tmp_list = children; tmp_list; tmp_list = tmp_list->next)
    {
850
      ResponseData *rd = get_response_data (tmp_list->data, FALSE);
851

852
      if (rd && rd->response_id == response_id)
853 854 855 856
       {
         child = tmp_list->data;
         break;
       }
857 858 859 860
    }

  g_list_free (children);

861
  return child;
862 863
}

864 865
static void
gtk_dialog_close (GtkDialog *dialog)
866
{
867
  gtk_window_close (GTK_WINDOW (dialog));
868 869
}

870 871 872 873 874 875 876 877 878 879
/**
 * gtk_dialog_new:
 *
 * Creates a new dialog box.
 *
 * Widgets should not be packed into this #GtkWindow
 * directly, but into the @vbox and @action_area, as described above.
 *
 * Returns: the new dialog as a #GtkWidget
 */
Elliot Lee's avatar
Elliot Lee committed
880
GtkWidget*
881
gtk_dialog_new (void)
Elliot Lee's avatar
Elliot Lee committed
882
{
Manish Singh's avatar
Manish Singh committed
883
  return g_object_new (GTK_TYPE_DIALOG, NULL);
Elliot Lee's avatar
Elliot Lee committed
884
}
885 886 887 888 889 890 891 892

static GtkWidget*
gtk_dialog_new_empty (const gchar     *title,
                      GtkWindow       *parent,
                      GtkDialogFlags   flags)
{
  GtkDialog *dialog;

893 894 895
  dialog = g_object_new (GTK_TYPE_DIALOG,
                         "use-header-bar", (flags & GTK_DIALOG_USE_HEADER_BAR) != 0,
                         NULL);
896 897 898 899 900 901 902 903 904

  if (title)
    gtk_window_set_title (GTK_WINDOW (dialog), title);

  if (parent)
    gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);

  if (flags & GTK_DIALOG_MODAL)
    gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
905

906 907 908 909 910 911 912 913
  if (flags & GTK_DIALOG_DESTROY_WITH_PARENT)
    gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE);

  return GTK_WIDGET (dialog);
}

/**
 * gtk_dialog_new_with_buttons:
914 915
 * @title: (allow-none): Title of the dialog, or %NULL
 * @parent: (allow-none): Transient parent of the dialog, or %NULL
916
 * @flags: from #GtkDialogFlags
917
 * @first_button_text: (allow-none): text to go in first button, or %NULL
Matthias Clasen's avatar
Matthias Clasen committed
918
 * @...: response ID for first button, then additional buttons, ending with %NULL
919
 *
920
 * Creates a new #GtkDialog with title @title (or %NULL for the default
921
 * title; see gtk_window_set_title()) and transient parent @parent (or
922 923
 * %NULL for none; see gtk_window_set_transient_for()). The @flags
 * argument can be used to make the dialog modal (#GTK_DIALOG_MODAL)
924
 * and/or to have it destroyed along with its transient parent
925 926
 * (#GTK_DIALOG_DESTROY_WITH_PARENT). After @flags, button
 * text/response ID pairs should be listed, with a %NULL pointer ending
927
 * the list. Button text can be arbitrary text. A response ID can be
928 929
 * any positive number, or one of the values in the #GtkResponseType
 * enumeration. If the user clicks one of these dialog buttons,
930
 * #GtkDialog will emit the #GtkDialog::response signal with the corresponding
931
 * response ID. If a #GtkDialog receives the #GtkWidget::delete-event signal,
932 933
 * it will emit ::response with a response ID of #GTK_RESPONSE_DELETE_EVENT.
 * However, destroying a dialog does not emit the ::response signal;
934
 * so be careful relying on ::response when using the
935
 * #GTK_DIALOG_DESTROY_WITH_PARENT flag. Buttons are from left to right,
936 937
 * so the first button in the list will be the leftmost button in the dialog.
 *
938
 * Here’s a simple example:
939
 * |[<!-- language="C" -->
940
 *  GtkWidget *main_app_window; // Window the dialog should show up on
941 942 943 944 945 946 947 948 949 950
 *  GtkWidget *dialog;
 *  GtkDialogFlags flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT;
 *  dialog = gtk_dialog_new_with_buttons ("My dialog",
 *                                        main_app_window,
 *                                        flags,
 *                                        _("_OK"),
 *                                        GTK_RESPONSE_ACCEPT,
 *                                        _("_Cancel"),
 *                                        GTK_RESPONSE_REJECT,
 *                                        NULL);
Matthias Clasen's avatar
Matthias Clasen committed
951
 * ]|
952
 *
953
 * Returns: a new #GtkDialog
Matthias Clasen's avatar
Matthias Clasen committed
954
 */
955 956 957 958 959 960 961 962 963
GtkWidget*
gtk_dialog_new_with_buttons (const gchar    *title,
                             GtkWindow      *parent,
                             GtkDialogFlags  flags,
                             const gchar    *first_button_text,
                             ...)
{
  GtkDialog *dialog;
  va_list args;
964

965 966 967 968 969 970 971
  dialog = GTK_DIALOG (gtk_dialog_new_empty (title, parent, flags));

  va_start (args, first_button_text);

  gtk_dialog_add_buttons_valist (dialog,
                                 first_button_text,
                                 args);
972

973 974 975 976 977
  va_end (args);

  return GTK_WIDGET (dialog);
}

978
static void
979 980 981 982 983
response_data_free (gpointer data)
{
  g_slice_free (ResponseData, data);
}

984
static ResponseData *
985 986
get_response_data (GtkWidget *widget,
		   gboolean   create)
987
{
Manish Singh's avatar
Manish Singh committed
988 989
  ResponseData *ad = g_object_get_data (G_OBJECT (widget),
                                        "gtk-dialog-response-data");
990

991
  if (ad == NULL && create)
992
    {
993
      ad = g_slice_new (ResponseData);
994

Manish Singh's avatar
Manish Singh committed
995
      g_object_set_data_full (G_OBJECT (widget),
996
                              I_("gtk-dialog-response-data"),
Manish Singh's avatar
Manish Singh committed
997
                              ad,
998
			      response_data_free);
999 1000 1001 1002 1003 1004 1005 1006 1007 1008
    }

  return ad;
}

/**
 * gtk_dialog_add_action_widget:
 * @dialog: a #GtkDialog
 * @child: an activatable widget
 * @response_id: response ID for @child
1009
 *
1010
 * Adds an activatable widget to the action area of a #GtkDialog,
1011 1012
 * connecting a signal handler that will emit the #GtkDialog::response
 * signal on the dialog when the widget is activated. The widget is
1013
 * appended to the end of the dialog’s action area. If you want to add a
1014
 * non-activatable widget, simply pack it into the @action_area field
1015
 * of the #GtkDialog struct.
1016 1017
 **/
void
Manish Singh's avatar
Manish Singh committed
1018 1019 1020
gtk_dialog_add_action_widget (GtkDialog *dialog,
                              GtkWidget *child,
                              gint       response_id)
1021
{
1022
  GtkDialogPrivate *priv = dialog->priv;
1023

1024 1025 1026
  g_return_if_fail (GTK_IS_DIALOG (dialog));
  g_return_if_fail (GTK_IS_WIDGET (child));

1027
  add_response_data (dialog, child, response_id);
1028

1029
  if (priv->constructed && priv->use_header_bar)
1030 1031 1032 1033 1034 1035 1036 1037 1038
    {
      add_to_header_bar (dialog, child, response_id);

      if (gtk_widget_has_default (child))
        {
          gtk_widget_grab_default (child);
          update_suggested_action (dialog);
        }
    }
1039
  else
1040
    add_to_action_area (dialog, child, response_id);
1041 1042 1043 1044 1045
}

/**
 * gtk_dialog_add_button:
 * @dialog: a #GtkDialog
1046
 * @button_text: text of button
1047
 * @response_id: response ID for the button
1048
 *
1049 1050 1051
 * Adds a button with the given text and sets things up so that
 * clicking the button will emit the #GtkDialog::response signal with
 * the given @response_id. The button is appended to the end of the
1052
 * dialog’s action area. The button widget is returned, but usually
1053
 * you don’t need it.
1054
 *
1055
 * Returns: (transfer none): the #GtkButton widget that was added
1056
 **/
1057
GtkWidget*
1058 1059 1060 1061 1062
gtk_dialog_add_button (GtkDialog   *dialog,
                       const gchar *button_text,
                       gint         response_id)
{
  GtkWidget *button;
1063

1064 1065
  g_return_val_if_fail (GTK_IS_DIALOG (dialog), NULL);
  g_return_val_if_fail (button_text != NULL, NULL);
1066

1067 1068 1069
  button = gtk_button_new_with_label (button_text);
  gtk_button_set_use_underline (GTK_BUTTON (button), TRUE);

1070 1071
  G_GNUC_BEGIN_IGNORE_DEPRECATIONS;

1072 1073 1074 1075 1076 1077
  if (button_text)
    {
      GtkStockItem item;
      if (gtk_stock_lookup (button_text, &item))
        g_object_set (button, "use-stock", TRUE, NULL);
    }
1078

1079 1080
  G_GNUC_END_IGNORE_DEPRECATIONS;

1081
  gtk_style_context_add_class (gtk_widget_get_style_context (button), "text-button");
1082
  gtk_widget_set_can_default (button, TRUE);
1083

1084
  gtk_widget_show (button);
1085

1086
  gtk_dialog_add_action_widget (dialog, button, response_id);
1087 1088

  return button;
1089 1090 1091
}

static void
Chema Celorio's avatar
Chema Celorio committed
1092 1093 1094
gtk_dialog_add_buttons_valist (GtkDialog      *dialog,
                               const gchar    *first_button_text,
                               va_list         args)
1095 1096 1097 1098
{
  const gchar* text;
  gint response_id;

1099
  g_return_if_fail (GTK_IS_DIALOG (dialog));
1100

1101 1102
  if (first_button_text == NULL)
    return;
1103

1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120
  text = first_button_text;
  response_id = va_arg (args, gint);

  while (text != NULL)
    {
      gtk_dialog_add_button (dialog, text, response_id);

      text = va_arg (args, gchar*);
      if (text == NULL)
        break;
      response_id = va_arg (args, int);
    }
}

/**
 * gtk_dialog_add_buttons:
 * @dialog: a #GtkDialog
1121
 * @first_button_text: button text
Matthias Clasen's avatar
Matthias Clasen committed
1122
 * @...: response ID for first button, then more text-response_id pairs
1123
 *
1124
 * Adds more buttons, same as calling gtk_dialog_add_button()
1125
 * repeatedly.  The variable argument list should be %NULL-terminated
1126 1127
 * as with gtk_dialog_new_with_buttons(). Each button must have both
 * text and response ID.
Matthias Clasen's avatar
Matthias Clasen committed
1128
 */
1129 1130 1131 1132
void
gtk_dialog_add_buttons (GtkDialog   *dialog,
                        const gchar *first_button_text,
                        ...)
1133
{
1134 1135 1136 1137 1138 1139 1140
  va_list args;

  va_start (args, first_button_text);

  gtk_dialog_add_buttons_valist (dialog,
                                 first_button_text,
                                 args);
1141

1142 1143 1144
  va_end (args);
}

1145 1146 1147 1148 1149 1150
/**
 * gtk_dialog_set_response_sensitive:
 * @dialog: a #GtkDialog
 * @response_id: a response ID
 * @setting: %TRUE for sensitive
 *
1151
 * Calls `gtk_widget_set_sensitive (widget, @setting)`
1152
 * for each widget in the dialog’s action area with the given @response_id.
1153 1154 1155 1156 1157 1158 1159 1160 1161 1162
 * A convenient way to sensitize/desensitize dialog buttons.
 **/
void
gtk_dialog_set_response_sensitive (GtkDialog *dialog,
                                   gint       response_id,
                                   gboolean   setting)
{
  GList *children;
  GList *tmp_list;

Darin Adler's avatar
Darin Adler committed
1163 1164
  g_return_if_fail (GTK_IS_DIALOG (dialog));

1165
  children = get_action_children (dialog);
1166 1167 1168 1169 1170

  tmp_list = children;
  while (tmp_list != NULL)
    {
      GtkWidget *widget = tmp_list->data;
1171
      ResponseData *rd = get_response_data (widget, FALSE);
1172 1173 1174 1175

      if (rd && rd->response_id == response_id)
        gtk_widget_set_sensitive (widget, setting);

1176
      tmp_list = tmp_list->next;
1177 1178 1179 1180 1181 1182 1183 1184 1185
    }

  g_list_free (children);
}

/**
 * gtk_dialog_set_default_response:
 * @dialog: a #GtkDialog
 * @response_id: a response ID
1186
 *
1187
 * Sets the last widget in the dialog’s action area with the given @response_id
1188
 * as the default widget for the dialog. Pressing “Enter” normally activates
1189 1190 1191 1192 1193 1194 1195 1196 1197
 * the default widget.
 **/
void
gtk_dialog_set_default_response (GtkDialog *dialog,
                                 gint       response_id)
{
  GList *children;
  GList *tmp_list;

Darin Adler's avatar
Darin Adler committed
1198 1199
  g_return_if_fail (GTK_IS_DIALOG (dialog));

1200
  children = get_action_children (dialog);
1201 1202 1203 1204 1205

  tmp_list = children;
  while (tmp_list != NULL)
    {
      GtkWidget *widget = tmp_list->data;
1206
      ResponseData *rd = get_response_data (widget, FALSE);
1207 1208

      if (rd && rd->response_id == response_id)
1209
	gtk_widget_grab_default (widget);
1210

1211
      tmp_list = tmp_list->next;
1212 1213 1214
    }

  g_list_free (children);
1215 1216 1217

  if (dialog->priv->use_header_bar)
    update_suggested_action (dialog);
1218 1219
}

1220 1221 1222
/**
 * gtk_dialog_response:
 * @dialog: a #GtkDialog
1223 1224 1225
 * @response_id: response ID
 *
 * Emits the #GtkDialog::response signal with the given response ID.
1226
 * Used to indicate that the user has responded to the dialog in some way;
1227
 * typically either you or gtk_dialog_run() will be monitoring the
1228
 * ::response signal and take appropriate action.
1229 1230 1231 1232 1233 1234 1235
 **/
void
gtk_dialog_response (GtkDialog *dialog,
                     gint       response_id)
{
  g_return_if_fail (GTK_IS_DIALOG (dialog));

Manish Singh's avatar
Manish Singh committed
1236 1237 1238 1239
  g_signal_emit (dialog,
		 dialog_signals[RESPONSE],
		 0,
		 response_id);
1240 1241 1242 1243 1244 1245 1246
}

typedef struct
{
  GtkDialog *dialog;
  gint response_id;
  GMainLoop *loop;
1247
  gboolean destroyed;
1248 1249 1250 1251 1252
} RunInfo;

static void
shutdown_loop (RunInfo *ri)
{
1253 1254
  if (g_main_loop_is_running (ri->loop))
    g_main_loop_quit (ri->loop);
1255 1256 1257
}

static void
1258
run_unmap_handler (GtkDialog *dialog, gpointer data)
1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284
{
  RunInfo *ri = data;

  shutdown_loop (ri);
}

static void
run_response_handler (GtkDialog *dialog,
                      gint response_id,
                      gpointer data)
{
  RunInfo *ri;

  ri = data;

  ri->response_id = response_id;

  shutdown_loop (ri);
}

static gint
run_delete_handler (GtkDialog *dialog,
                    GdkEventAny *event,
                    gpointer data)
{
  RunInfo *ri = data;
1285

1286
  shutdown_loop (ri);
1287

1288 1289 1290
  return TRUE; /* Do not destroy */
}

1291 1292 1293 1294 1295 1296
static void
run_destroy_handler (GtkDialog *dialog, gpointer data)
{
  RunInfo *ri = data;

  /* shutdown_loop will be called by run_unmap_handler */
1297

1298 1299 1300
  ri->destroyed = TRUE;
}

1301 1302 1303
/**
 * gtk_dialog_run:
 * @dialog: a #GtkDialog
1304
 *
1305
 * Blocks in a recursive main loop until the @dialog either emits the
1306 1307 1308
 * #GtkDialog::response signal, or is destroyed. If the dialog is
 * destroyed during the call to gtk_dialog_run(), gtk_dialog_run() returns
 * #GTK_RESPONSE_NONE. Otherwise, it returns the response ID from the
1309 1310
 * ::response signal emission.
 *
Soeren Sandmann's avatar
Soeren Sandmann committed
1311
 * Before entering the recursive main loop, gtk_dialog_run() calls
1312 1313 1314
 * gtk_widget_show() on the dialog for you. Note that you still
 * need to show any children of the dialog yourself.
 *
1315
 * During gtk_dialog_run(), the default behavior of #GtkWidget::delete-event
1316
 * is disabled; if the dialog receives ::delete_event, it will not be
1317
 * destroyed as windows usually are, and gtk_dialog_run() will return
1318
 * #GTK_RESPONSE_DELETE_EVENT. Also, during gtk_dialog_run() the dialog
1319
 * will be modal. You can force gtk_dialog_run() to return at any time by
1320 1321
 * calling gtk_dialog_response() to emit the ::response signal. Destroying
 * the dialog during gtk_dialog_run() is a very bad idea, because your
1322
 * post-run code won’t know whether the dialog was destroyed or not.
1323 1324 1325 1326 1327
 *
 * After gtk_dialog_run() returns, you are responsible for hiding or
 * destroying the dialog if you wish to do so.
 *
 * Typical usage of this function might be:
1328
 * |[<!-- language="C" -->
1329 1330 1331 1332
 *   GtkWidget *dialog = gtk_dialog_new ();
 *   // Set up dialog...
 *
 *   int result = gtk_dialog_run (GTK_DIALOG (dialog));
1333 1334 1335
 *   switch (result)
 *     {
 *       case GTK_RESPONSE_ACCEPT:
1336
 *          // do_application_specific_something ();
1337 1338
 *          break;
 *       default:
1339
 *          // do_nothing_since_dialog_was_cancelled ();
1340 1341 1342
 *          break;
 *     }
 *   gtk_widget_destroy (dialog);
Matthias Clasen's avatar
Matthias Clasen committed
1343
 * ]|
1344
 *
1345
 * Note that even though the recursive main loop gives the effect of a
1346 1347
 * modal dialog (it prevents the user from interacting with other
 * windows in the same window group while the dialog is run), callbacks
1348
 * such as timeouts, IO channel watches, DND drops, etc, will
1349
 * be triggered during a gtk_dialog_run() call.
1350
 *
1351
 * Returns: response ID
1352 1353 1354 1355
 **/
gint
gtk_dialog_run (GtkDialog *dialog)
{
1356
  RunInfo ri = { NULL, GTK_RESPONSE_NONE, NULL, FALSE };