gtkdialog.c 45.9 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
15 16 17
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
Elliot Lee's avatar
Elliot Lee committed
18
 */
19 20

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

27 28
#include <stdlib.h>
#include <string.h>
29
#include "config.h"
Elliot Lee's avatar
Elliot Lee committed
30 31
#include "gtkbutton.h"
#include "gtkdialog.h"
32
#include "gtkhbbox.h"
33
#include "gtklabel.h"
Elliot Lee's avatar
Elliot Lee committed
34
#include "gtkhseparator.h"
35
#include "gtkmarshalers.h"
Elliot Lee's avatar
Elliot Lee committed
36
#include "gtkvbox.h"
37 38
#include "gdkkeysyms.h"
#include "gtkmain.h"
39
#include "gtkintl.h"
40
#include "gtkbindings.h"
41
#include "gtkprivate.h"
42
#include "gtkbuildable.h"
Elliot Lee's avatar
Elliot Lee committed
43

Federico Mena Quintero's avatar
Federico Mena Quintero committed
44

45 46 47 48 49 50 51
struct _GtkDialogPriv
{
  GtkWidget *vbox;
  GtkWidget *action_area;

  GtkWidget *separator;

Federico Mena Quintero's avatar
Federico Mena Quintero committed
52
  guint ignore_separator : 1;
53
};
Federico Mena Quintero's avatar
Federico Mena Quintero committed
54

55 56 57 58 59 60 61
typedef struct _ResponseData ResponseData;

struct _ResponseData
{
  gint response_id;
};

62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
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);

static void      gtk_dialog_set_property         (GObject      *object,
                                                  guint         prop_id,
                                                  const GValue *value,
                                                  GParamSpec   *pspec);
static void      gtk_dialog_get_property         (GObject      *object,
                                                  guint         prop_id,
                                                  GValue       *value,
                                                  GParamSpec   *pspec);
static void      gtk_dialog_style_set            (GtkWidget    *widget,
                                                  GtkStyle     *prev_style);
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 GObject * gtk_dialog_buildable_get_internal_child (GtkBuildable  *buildable,
                                                          GtkBuilder    *builder,
                                                          const gchar   *childname);
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);
102

103

104 105 106 107
enum {
  PROP_0,
  PROP_HAS_SEPARATOR
};
108 109 110

enum {
  RESPONSE,
111
  CLOSE,
112 113
  LAST_SIGNAL
};
Elliot Lee's avatar
Elliot Lee committed
114

115
static guint dialog_signals[LAST_SIGNAL];
Elliot Lee's avatar
Elliot Lee committed
116

117 118 119
G_DEFINE_TYPE_WITH_CODE (GtkDialog, gtk_dialog, GTK_TYPE_WINDOW,
			 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
						gtk_dialog_buildable_interface_init))
Elliot Lee's avatar
Elliot Lee committed
120 121 122 123

static void
gtk_dialog_class_init (GtkDialogClass *class)
{
124
  GObjectClass *gobject_class;
125
  GtkWidgetClass *widget_class;
126 127
  GtkBindingSet *binding_set;
  
128 129
  gobject_class = G_OBJECT_CLASS (class);
  widget_class = GTK_WIDGET_CLASS (class);
130
  
131 132
  gobject_class->set_property = gtk_dialog_set_property;
  gobject_class->get_property = gtk_dialog_get_property;
133
  
134
  widget_class->map = gtk_dialog_map;
135
  widget_class->style_set = gtk_dialog_style_set;
136 137

  class->close = gtk_dialog_close;
138
  
139
  g_type_class_add_private (gobject_class, sizeof (GtkDialogPriv));
Federico Mena Quintero's avatar
Federico Mena Quintero committed
140

141 142 143 144 145
  /**
   * GtkDialog:has-separator:
   *
   * When %TRUE, the dialog has a separator bar above its buttons.
   */
146 147
  g_object_class_install_property (gobject_class,
                                   PROP_HAS_SEPARATOR,
148
                                   g_param_spec_boolean ("has-separator",
149 150
							 P_("Has separator"),
							 P_("The dialog has a separator bar above its buttons"),
151
                                                         TRUE,
152
                                                         GTK_PARAM_READWRITE));
153 154 155 156 157 158 159 160 161 162 163

  /**
   * GtkDialog::response:
   * @dialog: the object on which the signal is emitted
   * @response_id: the response ID
   * 
   * 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. 
   * Otherwise, it depends on which action widget was clicked.
   */
164
  dialog_signals[RESPONSE] =
165
    g_signal_new (I_("response"),
Manish Singh's avatar
Manish Singh committed
166 167 168 169
		  G_OBJECT_CLASS_TYPE (class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (GtkDialogClass, response),
		  NULL, NULL,
170
		  _gtk_marshal_VOID__INT,
Manish Singh's avatar
Manish Singh committed
171 172
		  G_TYPE_NONE, 1,
		  G_TYPE_INT);
173

174 175 176 177 178
  /**
   * GtkDialog::close:
   *
   * The ::close signal is a 
   * <link linkend="keybinding-signals">keybinding signal</link>
179
   * which gets emitted when the user uses a keybinding to close
180 181 182 183
   * the dialog.
   *
   * The default binding for this signal is the Escape key.
   */ 
184
  dialog_signals[CLOSE] =
185
    g_signal_new (I_("close"),
Manish Singh's avatar
Manish Singh committed
186 187 188 189
		  G_OBJECT_CLASS_TYPE (class),
		  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
		  G_STRUCT_OFFSET (GtkDialogClass, close),
		  NULL, NULL,
190
		  _gtk_marshal_VOID__VOID,
Manish Singh's avatar
Manish Singh committed
191
		  G_TYPE_NONE, 0);
192
  
193
  gtk_widget_class_install_style_property (widget_class,
194
					   g_param_spec_int ("content-area-border",
195 196
                                                             P_("Content area border"),
                                                             P_("Width of border around the main dialog area"),
197 198 199
                                                             0,
                                                             G_MAXINT,
                                                             2,
200
                                                             GTK_PARAM_READABLE));
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
  /**
   * 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));
219
  gtk_widget_class_install_style_property (widget_class,
220
                                           g_param_spec_int ("button-spacing",
221 222
                                                             P_("Button spacing"),
                                                             P_("Spacing between buttons"),
223 224
                                                             0,
                                                             G_MAXINT,
225
                                                             6,
226
                                                             GTK_PARAM_READABLE));
227 228
  
  gtk_widget_class_install_style_property (widget_class,
229
                                           g_param_spec_int ("action-area-border",
230 231
                                                             P_("Action area border"),
                                                             P_("Width of border around the button area at the bottom of the dialog"),
232 233
                                                             0,
                                                             G_MAXINT,
234
                                                             5,
235
                                                             GTK_PARAM_READABLE));
236 237 238

  binding_set = gtk_binding_set_by_class (class);
  
239
  gtk_binding_entry_add_signal (binding_set, GDK_Escape, 0, "close", 0);
240 241 242 243 244
}

static void
update_spacings (GtkDialog *dialog)
{
245
  GtkDialogPriv *priv = dialog->priv;
246
  gint content_area_border;
247
  gint content_area_spacing;
248 249 250
  gint button_spacing;
  gint action_area_border;

251
  gtk_widget_style_get (GTK_WIDGET (dialog),
252
                        "content-area-border", &content_area_border,
253
                        "content-area-spacing", &content_area_spacing,
254 255
                        "button-spacing", &button_spacing,
                        "action-area-border", &action_area_border,
256 257
                        NULL);

258
  gtk_container_set_border_width (GTK_CONTAINER (priv->vbox),
259
                                  content_area_border);
260
  if (!_gtk_box_get_spacing_set (GTK_BOX (priv->vbox)))
261
    {
262 263
      gtk_box_set_spacing (GTK_BOX (priv->vbox), content_area_spacing);
      _gtk_box_set_spacing_set (GTK_BOX (priv->vbox), FALSE);
264
    }
265
  gtk_box_set_spacing (GTK_BOX (priv->action_area),
266
                       button_spacing);
267
  gtk_container_set_border_width (GTK_CONTAINER (priv->action_area),
268
                                  action_area_border);
Elliot Lee's avatar
Elliot Lee committed
269 270 271 272 273
}

static void
gtk_dialog_init (GtkDialog *dialog)
{
274 275 276 277 278 279
  GtkDialogPriv *priv;

  dialog->priv = G_TYPE_INSTANCE_GET_PRIVATE (dialog,
                                              GTK_TYPE_DIALOG,
                                              GtkDialogPriv);
  priv = dialog->priv;
Federico Mena Quintero's avatar
Federico Mena Quintero committed
280 281 282

  priv->ignore_separator = FALSE;

283 284 285 286
  /* To avoid breaking old code that prevents destroy on delete event
   * by connecting a handler, we have to have the FIRST signal
   * connection on the dialog.
   */
Manish Singh's avatar
Manish Singh committed
287
  g_signal_connect (dialog,
288
                    "delete-event",
Manish Singh's avatar
Manish Singh committed
289 290
                    G_CALLBACK (gtk_dialog_delete_event_handler),
                    NULL);
291

292
  priv->vbox = gtk_vbox_new (FALSE, 0);
293

294 295
  gtk_container_add (GTK_CONTAINER (dialog), priv->vbox);
  gtk_widget_show (priv->vbox);
Elliot Lee's avatar
Elliot Lee committed
296

297
  priv->action_area = gtk_hbutton_box_new ();
298

299
  gtk_button_box_set_layout (GTK_BUTTON_BOX (priv->action_area),
300
                             GTK_BUTTONBOX_END);
301

302
  gtk_box_pack_end (GTK_BOX (priv->vbox), priv->action_area,
303
                    FALSE, TRUE, 0);
304
  gtk_widget_show (priv->action_area);
Elliot Lee's avatar
Elliot Lee committed
305

306 307 308
  priv->separator = gtk_hseparator_new ();
  gtk_box_pack_end (GTK_BOX (priv->vbox), priv->separator, FALSE, TRUE, 0);
  gtk_widget_show (priv->separator);
309 310 311

  gtk_window_set_type_hint (GTK_WINDOW (dialog),
			    GDK_WINDOW_TYPE_HINT_DIALOG);
312
  gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER_ON_PARENT);
Elliot Lee's avatar
Elliot Lee committed
313 314
}

315 316
static GtkBuildableIface *parent_buildable_iface;

317 318 319
static void
gtk_dialog_buildable_interface_init (GtkBuildableIface *iface)
{
320
  parent_buildable_iface = g_type_interface_peek_parent (iface);
321 322 323 324 325 326 327 328 329 330
  iface->get_internal_child = gtk_dialog_buildable_get_internal_child;
  iface->custom_tag_start = gtk_dialog_buildable_custom_tag_start;
  iface->custom_finished = gtk_dialog_buildable_custom_finished;
}

static GObject *
gtk_dialog_buildable_get_internal_child (GtkBuildable *buildable,
					 GtkBuilder   *builder,
					 const gchar  *childname)
{
331 332 333 334 335 336 337 338 339 340
  GtkDialogPriv *priv = GTK_DIALOG (buildable)->priv;

  if (strcmp (childname, "vbox") == 0)
    return G_OBJECT (priv->vbox);
  else if (strcmp (childname, "action_area") == 0)
    return G_OBJECT (priv->action_area);

  return parent_buildable_iface->get_internal_child (buildable,
                                                     builder,
                                                     childname);
341
}
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370

static void 
gtk_dialog_set_property (GObject      *object,
                         guint         prop_id,
                         const GValue *value,
                         GParamSpec   *pspec)
{
  GtkDialog *dialog;
  
  dialog = GTK_DIALOG (object);
  
  switch (prop_id)
    {
    case PROP_HAS_SEPARATOR:
      gtk_dialog_set_has_separator (dialog, g_value_get_boolean (value));
      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)
{
371 372
  GtkDialog *dialog = GTK_DIALOG (object);
  GtkDialogPriv *priv = dialog->priv;
373 374 375 376
  
  switch (prop_id)
    {
    case PROP_HAS_SEPARATOR:
377
      g_value_set_boolean (value, priv->separator != NULL);
378 379 380 381 382 383 384 385
      break;

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

386
static gboolean
387 388 389 390 391
gtk_dialog_delete_event_handler (GtkWidget   *widget,
                                 GdkEventAny *event,
                                 gpointer     user_data)
{
  /* emit response signal */
392
  gtk_dialog_response (GTK_DIALOG (widget), GTK_RESPONSE_DELETE_EVENT);
393 394 395 396 397

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

398 399 400 401 402
/* 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.
403 404 405
 *
 * Additionally, skip selectable labels when looking for the
 * right initial focus widget.
406 407 408 409 410 411
 */
static void
gtk_dialog_map (GtkWidget *widget)
{
  GtkWindow *window = GTK_WINDOW (widget);
  GtkDialog *dialog = GTK_DIALOG (widget);
412
  GtkDialogPriv *priv = dialog->priv;
413
  
Matthias Clasen's avatar
Matthias Clasen committed
414
  GTK_WIDGET_CLASS (gtk_dialog_parent_class)->map (widget);
415 416 417 418

  if (!window->focus_widget)
    {
      GList *children, *tmp_list;
419
      GtkWidget *first_focus = NULL;
420
      
421 422 423
      do 
	{
	  g_signal_emit_by_name (window, "move_focus", GTK_DIR_TAB_FORWARD);
424 425 426 427

	  if (first_focus == NULL)
	    first_focus = window->focus_widget;
	  else if (first_focus == window->focus_widget)
428
            break;
429 430
	  if (!GTK_IS_LABEL (window->focus_widget))
	    break;
431 432
          if (!gtk_label_get_current_uri (GTK_LABEL (window->focus_widget)))
            gtk_label_select_region (GTK_LABEL (window->focus_widget), 0, 0);
433
	}
434
      while (TRUE);
435

436
      tmp_list = children = gtk_container_get_children (GTK_CONTAINER (priv->action_area));
437
      
438 439 440
      while (tmp_list)
	{
	  GtkWidget *child = tmp_list->data;
441 442 443 444 445
	  
	  if ((window->focus_widget == NULL || 
	       child == window->focus_widget) && 
	      child != window->default_widget &&
	      window->default_widget)
446 447 448 449 450 451 452
	    {
	      gtk_widget_grab_focus (window->default_widget);
	      break;
	    }
	  
	  tmp_list = tmp_list->next;
	}
453
      
454 455 456 457
      g_list_free (children);
    }
}

458 459 460 461 462 463 464
static void
gtk_dialog_style_set (GtkWidget *widget,
                      GtkStyle  *prev_style)
{
  update_spacings (GTK_DIALOG (widget));
}

465 466 467
static GtkWidget *
dialog_find_button (GtkDialog *dialog,
		    gint       response_id)
468
{
469
  GtkDialogPriv *priv = dialog->priv;
470
  GtkWidget *child = NULL;
471
  GList *children, *tmp_list;
472
      
473
  children = gtk_container_get_children (GTK_CONTAINER (priv->action_area));
474 475 476

  for (tmp_list = children; tmp_list; tmp_list = tmp_list->next)
    {
477
      ResponseData *rd = get_response_data (tmp_list->data, FALSE);
478
      
479
      if (rd && rd->response_id == response_id)
480
	{
481
	  child = tmp_list->data;
482 483 484 485 486 487
	  break;
	}
    }

  g_list_free (children);

488
  return child;
489 490
}

491 492
static void
gtk_dialog_close (GtkDialog *dialog)
493
{
494 495
  /* Synthesize delete_event to close dialog. */
  
496 497
  GtkWidget *widget = GTK_WIDGET (dialog);
  GdkEvent *event;
498

499
  event = gdk_event_new (GDK_DELETE);
500
  
501 502
  event->any.window = g_object_ref (widget->window);
  event->any.send_event = TRUE;
503
  
504 505
  gtk_main_do_event (event);
  gdk_event_free (event);
506 507
}

Elliot Lee's avatar
Elliot Lee committed
508
GtkWidget*
509
gtk_dialog_new (void)
Elliot Lee's avatar
Elliot Lee committed
510
{
Manish Singh's avatar
Manish Singh committed
511
  return g_object_new (GTK_TYPE_DIALOG, NULL);
Elliot Lee's avatar
Elliot Lee committed
512
}
513 514 515 516 517 518 519 520

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

Manish Singh's avatar
Manish Singh committed
521
  dialog = g_object_new (GTK_TYPE_DIALOG, NULL);
522 523 524 525 526 527 528 529 530

  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);
531
  
532 533 534
  if (flags & GTK_DIALOG_DESTROY_WITH_PARENT)
    gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE);

535 536 537
  if (flags & GTK_DIALOG_NO_SEPARATOR)
    gtk_dialog_set_has_separator (dialog, FALSE);
  
538 539 540 541 542
  return GTK_WIDGET (dialog);
}

/**
 * gtk_dialog_new_with_buttons:
543 544
 * @title: (allow-none): Title of the dialog, or %NULL
 * @parent: (allow-none): Transient parent of the dialog, or %NULL
545
 * @flags: from #GtkDialogFlags
546
 * @first_button_text: (allow-none): stock ID or text to go in first button, or %NULL
547
 * @Varargs: response ID for first button, then additional buttons, ending with %NULL
548
 *
549
 * Creates a new #GtkDialog with title @title (or %NULL for the default
550
 * title; see gtk_window_set_title()) and transient parent @parent (or
551 552
 * %NULL for none; see gtk_window_set_transient_for()). The @flags
 * argument can be used to make the dialog modal (#GTK_DIALOG_MODAL)
553
 * and/or to have it destroyed along with its transient parent
554 555
 * (#GTK_DIALOG_DESTROY_WITH_PARENT). After @flags, button
 * text/response ID pairs should be listed, with a %NULL pointer ending
556
 * the list. Button text can be either a stock ID such as
557
 * #GTK_STOCK_OK, or some arbitrary text. A response ID can be
558 559
 * any positive number, or one of the values in the #GtkResponseType
 * enumeration. If the user clicks one of these dialog buttons,
560 561 562 563 564 565
 * #GtkDialog will emit the #GtkDialog::response signal with the corresponding
 * response ID. If a #GtkDialog receives the #GtkWidget::delete-event signal, 
 * it will emit ::response with a response ID of #GTK_RESPONSE_DELETE_EVENT.
 * However, destroying a dialog does not emit the ::response signal;
 * so be careful relying on ::response when using the 
 * #GTK_DIALOG_DESTROY_WITH_PARENT flag. Buttons are from left to right,
566 567 568
 * so the first button in the list will be the leftmost button in the dialog.
 *
 * Here's a simple example:
Matthias Clasen's avatar
Matthias Clasen committed
569
 * |[
570 571 572 573 574 575 576 577
 *  GtkWidget *dialog = gtk_dialog_new_with_buttons ("My dialog",
 *                                                   main_app_window,
 *                                                   GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
 *                                                   GTK_STOCK_OK,
 *                                                   GTK_RESPONSE_ACCEPT,
 *                                                   GTK_STOCK_CANCEL,
 *                                                   GTK_RESPONSE_REJECT,
 *                                                   NULL);
Matthias Clasen's avatar
Matthias Clasen committed
578
 * ]|
579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604
 * 
 * Return value: a new #GtkDialog
 **/
GtkWidget*
gtk_dialog_new_with_buttons (const gchar    *title,
                             GtkWindow      *parent,
                             GtkDialogFlags  flags,
                             const gchar    *first_button_text,
                             ...)
{
  GtkDialog *dialog;
  va_list args;
  
  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);
  
  va_end (args);

  return GTK_WIDGET (dialog);
}

605 606 607 608 609 610
static void 
response_data_free (gpointer data)
{
  g_slice_free (ResponseData, data);
}

611
static ResponseData*
612 613
get_response_data (GtkWidget *widget,
		   gboolean   create)
614
{
Manish Singh's avatar
Manish Singh committed
615 616
  ResponseData *ad = g_object_get_data (G_OBJECT (widget),
                                        "gtk-dialog-response-data");
617

618
  if (ad == NULL && create)
619
    {
620
      ad = g_slice_new (ResponseData);
621
      
Manish Singh's avatar
Manish Singh committed
622
      g_object_set_data_full (G_OBJECT (widget),
623
                              I_("gtk-dialog-response-data"),
Manish Singh's avatar
Manish Singh committed
624
                              ad,
625
			      response_data_free);
626 627 628 629 630 631 632 633 634 635
    }

  return ad;
}

static void
action_widget_activated (GtkWidget *widget, GtkDialog *dialog)
{
  gint response_id;
  
636
  response_id = gtk_dialog_get_response_for_widget (dialog, widget);
637 638 639

  gtk_dialog_response (dialog, response_id);
}
Manish Singh's avatar
Manish Singh committed
640

641 642 643 644 645 646 647
/**
 * gtk_dialog_add_action_widget:
 * @dialog: a #GtkDialog
 * @child: an activatable widget
 * @response_id: response ID for @child
 * 
 * Adds an activatable widget to the action area of a #GtkDialog,
648 649 650 651 652
 * connecting a signal handler that will emit the #GtkDialog::response 
 * signal on the dialog when the widget is activated. The widget is 
 * appended to the end of the dialog's action area. If you want to add a
 * non-activatable widget, simply pack it into the @action_area field 
 * of the #GtkDialog struct.
653 654
 **/
void
Manish Singh's avatar
Manish Singh committed
655 656 657
gtk_dialog_add_action_widget (GtkDialog *dialog,
                              GtkWidget *child,
                              gint       response_id)
658
{
659
  GtkDialogPriv *priv;
660
  ResponseData *ad;
661
  guint signal_id;
662 663 664 665
  
  g_return_if_fail (GTK_IS_DIALOG (dialog));
  g_return_if_fail (GTK_IS_WIDGET (child));

666 667
  priv = dialog->priv;

668
  ad = get_response_data (child, TRUE);
669 670 671

  ad->response_id = response_id;

672
  if (GTK_IS_BUTTON (child))
Manish Singh's avatar
Manish Singh committed
673
    signal_id = g_signal_lookup ("clicked", GTK_TYPE_BUTTON);
674
  else
675
    signal_id = GTK_WIDGET_GET_CLASS (child)->activate_signal;
676 677 678

  if (signal_id)
    {
Manish Singh's avatar
Manish Singh committed
679 680 681 682 683 684 685 686 687
      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);
688 689 690 691
    }
  else
    g_warning ("Only 'activatable' widgets can be packed into the action area of a GtkDialog");

692
  gtk_box_pack_end (GTK_BOX (priv->action_area),
693
                    child,
694 695 696
                    FALSE, TRUE, 0);
  
  if (response_id == GTK_RESPONSE_HELP)
697
    gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (priv->action_area), child, TRUE);
698 699 700 701 702 703 704 705 706 707
}

/**
 * gtk_dialog_add_button:
 * @dialog: a #GtkDialog
 * @button_text: text of button, or stock ID
 * @response_id: response ID for the button
 * 
 * Adds a button with the given text (or a stock button, if @button_text is a
 * stock ID) and sets things up so that clicking the button will emit the
708 709 710
 * #GtkDialog::response signal with the given @response_id. The button is 
 * appended to the end of the dialog's action area. The button widget is 
 * returned, but usually you don't need it.
711 712
 *
 * Return value: the button widget that was added
713
 **/
714
GtkWidget*
715 716 717 718 719 720
gtk_dialog_add_button (GtkDialog   *dialog,
                       const gchar *button_text,
                       gint         response_id)
{
  GtkWidget *button;
  
721 722
  g_return_val_if_fail (GTK_IS_DIALOG (dialog), NULL);
  g_return_val_if_fail (button_text != NULL, NULL);
723

724
  button = gtk_button_new_from_stock (button_text);
725

726
  gtk_widget_set_can_default (button, TRUE);
727
  
728 729 730 731 732
  gtk_widget_show (button);
  
  gtk_dialog_add_action_widget (dialog,
                                button,
                                response_id);
733 734

  return button;
735 736 737
}

static void
Chema Celorio's avatar
Chema Celorio committed
738 739 740
gtk_dialog_add_buttons_valist (GtkDialog      *dialog,
                               const gchar    *first_button_text,
                               va_list         args)
741 742 743 744
{
  const gchar* text;
  gint response_id;

745 746
  g_return_if_fail (GTK_IS_DIALOG (dialog));
  
747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770
  if (first_button_text == NULL)
    return;
  
  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
 * @first_button_text: button text or stock ID
 * @Varargs: response ID for first button, then more text-response_id pairs
 * 
 * Adds more buttons, same as calling gtk_dialog_add_button()
771
 * repeatedly.  The variable argument list should be %NULL-terminated
772 773 774 775 776 777 778
 * as with gtk_dialog_new_with_buttons(). Each button must have both
 * text and response ID.
 **/
void
gtk_dialog_add_buttons (GtkDialog   *dialog,
                        const gchar *first_button_text,
                        ...)
779
{  
780 781 782 783 784 785 786 787 788 789 790
  va_list args;

  va_start (args, first_button_text);

  gtk_dialog_add_buttons_valist (dialog,
                                 first_button_text,
                                 args);
  
  va_end (args);
}

791 792 793 794 795 796
/**
 * gtk_dialog_set_response_sensitive:
 * @dialog: a #GtkDialog
 * @response_id: a response ID
 * @setting: %TRUE for sensitive
 *
Matthias Clasen's avatar
Matthias Clasen committed
797 798
 * Calls <literal>gtk_widget_set_sensitive (widget, @setting)</literal> 
 * for each widget in the dialog's action area with the given @response_id.
799 800 801 802 803 804 805
 * A convenient way to sensitize/desensitize dialog buttons.
 **/
void
gtk_dialog_set_response_sensitive (GtkDialog *dialog,
                                   gint       response_id,
                                   gboolean   setting)
{
806
  GtkDialogPriv *priv;
807 808 809
  GList *children;
  GList *tmp_list;

Darin Adler's avatar
Darin Adler committed
810 811
  g_return_if_fail (GTK_IS_DIALOG (dialog));

812 813 814
  priv = dialog->priv;

  children = gtk_container_get_children (GTK_CONTAINER (priv->action_area));
815 816 817 818 819

  tmp_list = children;
  while (tmp_list != NULL)
    {
      GtkWidget *widget = tmp_list->data;
820
      ResponseData *rd = get_response_data (widget, FALSE);
821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843

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

      tmp_list = g_list_next (tmp_list);
    }

  g_list_free (children);
}

/**
 * gtk_dialog_set_default_response:
 * @dialog: a #GtkDialog
 * @response_id: a response ID
 * 
 * Sets the last widget in the dialog's action area with the given @response_id
 * as the default widget for the dialog. Pressing "Enter" normally activates
 * the default widget.
 **/
void
gtk_dialog_set_default_response (GtkDialog *dialog,
                                 gint       response_id)
{
844
  GtkDialogPriv *priv;
845 846 847
  GList *children;
  GList *tmp_list;

Darin Adler's avatar
Darin Adler committed
848 849
  g_return_if_fail (GTK_IS_DIALOG (dialog));

850 851 852
  priv = dialog->priv;

  children = gtk_container_get_children (GTK_CONTAINER (priv->action_area));
853 854 855 856 857

  tmp_list = children;
  while (tmp_list != NULL)
    {
      GtkWidget *widget = tmp_list->data;
858
      ResponseData *rd = get_response_data (widget, FALSE);
859 860

      if (rd && rd->response_id == response_id)
861
	gtk_widget_grab_default (widget);
Owen Taylor's avatar
Owen Taylor committed
862
	    
863 864 865 866 867 868
      tmp_list = g_list_next (tmp_list);
    }

  g_list_free (children);
}

869 870 871 872 873 874 875 876
/**
 * gtk_dialog_set_has_separator:
 * @dialog: a #GtkDialog
 * @setting: %TRUE to have a separator
 *
 * Sets whether the dialog has a separator above the buttons.
 * %TRUE by default.
 **/
877 878 879 880
void
gtk_dialog_set_has_separator (GtkDialog *dialog,
                              gboolean   setting)
{
881
  GtkDialogPriv *priv;
Federico Mena Quintero's avatar
Federico Mena Quintero committed
882

883 884
  g_return_if_fail (GTK_IS_DIALOG (dialog));

885
  priv = dialog->priv;
Federico Mena Quintero's avatar
Federico Mena Quintero committed
886

887
  /* this might fail if we get called before _init() somehow */
888
  g_assert (priv->vbox != NULL);
Federico Mena Quintero's avatar
Federico Mena Quintero committed
889 890 891 892 893 894

  if (priv->ignore_separator)
    {
      g_warning ("Ignoring the separator setting");
      return;
    }
895
  
896
  if (setting && priv->separator == NULL)
897
    {
898 899
      priv->separator = gtk_hseparator_new ();
      gtk_box_pack_end (GTK_BOX (priv->vbox), priv->separator, FALSE, TRUE, 0);
900 901 902 903

      /* The app programmer could screw this up, but, their own fault.
       * Moves the separator just above the action area.
       */
904 905
      gtk_box_reorder_child (GTK_BOX (priv->vbox), priv->separator, 1);
      gtk_widget_show (priv->separator);
906
    }
907
  else if (!setting && priv->separator != NULL)
908
    {
909 910
      gtk_widget_destroy (priv->separator);
      priv->separator = NULL;
911 912
    }

913
  g_object_notify (G_OBJECT (dialog), "has-separator");
914 915
}

916 917 918 919 920 921 922 923
/**
 * gtk_dialog_get_has_separator:
 * @dialog: a #GtkDialog
 * 
 * Accessor for whether the dialog has a separator.
 * 
 * Return value: %TRUE if the dialog has a separator
 **/
924 925 926 927 928
gboolean
gtk_dialog_get_has_separator (GtkDialog *dialog)
{
  g_return_val_if_fail (GTK_IS_DIALOG (dialog), FALSE);

929
  return dialog->priv->separator != NULL;
930 931
}

932 933 934 935 936
/**
 * gtk_dialog_response:
 * @dialog: a #GtkDialog
 * @response_id: response ID 
 * 
937 938
 * Emits the #GtkDialog::response signal with the given response ID. 
 * Used to indicate that the user has responded to the dialog in some way;
939
 * typically either you or gtk_dialog_run() will be monitoring the
940
 * ::response signal and take appropriate action.
941 942 943 944 945 946 947
 **/
void
gtk_dialog_response (GtkDialog *dialog,
                     gint       response_id)
{
  g_return_if_fail (GTK_IS_DIALOG (dialog));

Manish Singh's avatar
Manish Singh committed
948 949 950 951
  g_signal_emit (dialog,
		 dialog_signals[RESPONSE],
		 0,
		 response_id);
952 953 954 955 956 957 958
}

typedef struct
{
  GtkDialog *dialog;
  gint response_id;
  GMainLoop *loop;
959
  gboolean destroyed;
960 961 962 963 964
} RunInfo;

static void
shutdown_loop (RunInfo *ri)
{
965 966
  if (g_main_loop_is_running (ri->loop))
    g_main_loop_quit (ri->loop);
967 968 969
}

static void
970
run_unmap_handler (GtkDialog *dialog, gpointer data)
971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002
{
  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;
    
  shutdown_loop (ri);
  
  return TRUE; /* Do not destroy */
}

1003 1004 1005 1006 1007 1008 1009 1010 1011 1012
static void
run_destroy_handler (GtkDialog *dialog, gpointer data)
{
  RunInfo *ri = data;

  /* shutdown_loop will be called by run_unmap_handler */
  
  ri->destroyed = TRUE;
}

1013 1014 1015 1016 1017
/**
 * gtk_dialog_run:
 * @dialog: a #GtkDialog
 * 
 * Blocks in a recursive main loop until the @dialog either emits the
1018 1019 1020 1021 1022
 * #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 
 * ::response signal emission.
 *
Soeren Sandmann's avatar
Soeren Sandmann committed
1023
 * Before entering the recursive main loop, gtk_dialog_run() calls
1024 1025 1026
 * gtk_widget_show() on the dialog for you. Note that you still
 * need to show any children of the dialog yourself.
 *
1027 1028
 * During gtk_dialog_run(), the default behavior of #GtkWidget::delete-event 
 * is disabled; if the dialog receives ::delete_event, it will not be
1029
 * destroyed as windows usually are, and gtk_dialog_run() will return
1030 1031 1032 1033 1034
 * #GTK_RESPONSE_DELETE_EVENT. Also, during gtk_dialog_run() the dialog 
 * will be modal. You can force gtk_dialog_run() to return at any time by
 * calling gtk_dialog_response() to emit the ::response signal. Destroying 
 * the dialog during gtk_dialog_run() is a very bad idea, because your 
 * post-run code won't know whether the dialog was destroyed or not.
1035 1036 1037 1038 1039
 *
 * 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:
Matthias Clasen's avatar
Matthias Clasen committed
1040
 * |[
1041 1042 1043 1044
 *   gint result = gtk_dialog_run (GTK_DIALOG (dialog));
 *   switch (result)
 *     {
 *       case GTK_RESPONSE_ACCEPT:
Matthias Clasen's avatar
Matthias Clasen committed
1045
 *          do_application_specific_something ();
1046 1047
 *          break;
 *       default:
Matthias Clasen's avatar
Matthias Clasen committed
1048
 *          do_nothing_since_dialog_was_cancelled ();
1049 1050 1051
 *          break;
 *     }
 *   gtk_widget_destroy (dialog);
Matthias Clasen's avatar
Matthias Clasen committed
1052
 * ]|
1053
 * 
1054 1055
 * Note that even though the recursive main loop gives the effect of a
 * modal dialog (it prevents the user from interacting with other 
1056 1057 1058
 * windows in the same window group while the dialog is run), callbacks 
 * such as timeouts, IO channel watches, DND drops, etc, <emphasis>will</emphasis> 
 * be triggered during a gtk_dialog_run() call.
1059
 * 
1060 1061 1062 1063 1064
 * Return value: response ID
 **/
gint
gtk_dialog_run (GtkDialog *dialog)
{
1065
  RunInfo ri = { NULL, GTK_RESPONSE_NONE, NULL, FALSE };
1066
  gboolean was_modal;
1067 1068 1069 1070
  gulong response_handler;
  gulong unmap_handler;
  gulong destroy_handler;
  gulong delete_handler;
1071 1072 1073
  
  g_return_val_if_fail (GTK_IS_DIALOG (dialog), -1);

Manish Singh's avatar
Manish Singh committed
1074
  g_object_ref (dialog);
1075 1076 1077 1078 1079

  was_modal = GTK_WINDOW (dialog)->modal;
  if (!was_modal)
    gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);

1080
  if (!gtk_widget_get_visible (GTK_WIDGET (dialog)))
1081 1082
    gtk_widget_show (GTK_WIDGET (dialog));
  
1083
  response_handler =
Manish Singh's avatar
Manish Singh committed
1084 1085 1086 1087
    g_signal_connect (dialog,
                      "response",
                      G_CALLBACK (run_response_handler),
                      &ri);
1088
  
1089
  unmap_handler =
Manish Singh's avatar
Manish Singh committed
1090 1091 1092 1093
    g_signal_connect (dialog,
                      "unmap",
                      G_CALLBACK (run_unmap_handler),
                      &ri);
1094 1095
  
  delete_handler =
Manish Singh's avatar
Manish Singh committed
1096
    g_signal_connect (dialog,
1097
                      "delete-event",
Manish Singh's avatar
Manish Singh committed
1098 1099
                      G_CALLBACK (run_delete_handler),
                      &ri);
1100
  
1101
  destroy_handler =
Manish Singh's avatar
Manish Singh committed
1102 1103 1104 1105
    g_signal_connect (dialog,
                      "destroy",
                      G_CALLBACK (run_destroy_handler),
                      &ri);
1106
  
Manish Singh's avatar
Manish Singh committed
1107
  ri.loop = g_main_loop_new (NULL, FALSE);
1108

1109
  GDK_THREADS_LEAVE ();  
1110
  g_main_loop_run (ri.loop);
1111
  GDK_THREADS_ENTER ();  
1112 1113 1114 1115

  g_main_loop_unref (ri.loop);

  ri.loop = NULL;
1116
  
1117
  if (!ri.destroyed)
1118 1119 1120 1121
    {
      if (!was_modal)
        gtk_window_set_modal (GTK_WINDOW(dialog), FALSE);
      
Manish Singh's avatar
Manish Singh committed
1122 1123 1124 1125
      g_signal_handler_disconnect (dialog, response_handler);
      g_signal_handler_disconnect (dialog, unmap_handler);
      g_signal_handler_disconnect (dialog, delete_handler);
      g_signal_handler_disconnect (dialog, destroy_handler);
1126 1127
    }

Manish Singh's avatar
Manish Singh committed
1128
  g_object_unref (dialog);
1129 1130 1131

  return ri.response_id;
}
Federico Mena Quintero's avatar
Federico Mena Quintero committed
1132 1133 1134 1135 1136

void
_gtk_dialog_set_ignore_separator (GtkDialog *dialog,
				  gboolean   ignore_separator)
{
1137
  GtkDialogPriv *priv = dialog->priv;
Federico Mena Quintero's avatar
Federico Mena Quintero committed
1138 1139 1140

  priv->ignore_separator = ignore_separator;
}
1141

1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157
/**
 * gtk_dialog_get_widget_for_response:
 * @dialog: a #GtkDialog
 * @response_id: the response ID used by the @dialog widget
 *
 * Gets the widget button that uses the given response ID in the action area
 * of a dialog.
 *
 * Returns: the @widget button that uses the given @response_id, or %NULL.
 *
 * Since: 2.20
 */
GtkWidget*
gtk_dialog_get_widget_for_response (GtkDialog *dialog,
				    gint       response_id)
{
1158
  GtkDialogPriv *priv;
1159 1160 1161 1162 1163
  GList *children;
  GList *tmp_list;

  g_return_val_if_fail (GTK_IS_DIALOG (dialog), NULL);

1164 1165 1166
  priv = dialog->priv;

  children = gtk_container_get_children (GTK_CONTAINER (priv->action_area));
1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187

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

      if (rd && rd->response_id == response_id)
        {
          g_list_free (children);
          return widget;
        }

      tmp_list = g_list_next (tmp_list);
    }

  g_list_free (children);

  return NULL;
}

1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200
/**
 * gtk_dialog_get_response_for_widget:
 * @dialog: a #GtkDialog
 * @widget: a widget in the action area of @dialog
 *
 * Gets the response id of a widget in the action area
 * of a dialog.
 *
 * Returns: the response id of @widget, or %GTK_RESPONSE_NONE
 *  if @widget doesn't have a response id set.
 *
 * Since: 2.8
 */
1201
gint
1202 1203
gtk_dialog_get_response_for_widget (GtkDialog *dialog,
				    GtkWidget *widget)
1204 1205 1206 1207 1208 1209 1210 1211 1212
{
  ResponseData *rd;

  rd = get_response_data (widget, FALSE);
  if (!rd)
    return GTK_RESPONSE_NONE;
  else
    return rd->response_id;
}
1213 1214 1215

/**
 * gtk_alternative_dialog_button_order:
1216
 * @screen: (allow-none): a #GdkScreen, or %NULL to use the default screen
1217 1218
 *
 * Returns %TRUE if dialogs are expected to use an alternative
1219
 * button order on the screen @screen. See
1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253
 * gtk_dialog_set_alternative_button_order() for more details
 * about alternative button order. 
 *
 * If you need to use this function, you should probably connect
 * to the ::notify:gtk-alternative-button-order signal on the
 * #GtkSettings object associated to @screen, in order to be 
 * notified if the button order setting changes.
 *
 * Returns: Whether the alternative button order should be used
 *
 * Since: 2.6
 */
gboolean 
gtk_alternative_dialog_button_order (GdkScreen *screen)
{
  GtkSettings *settings;
  gboolean result;

  if (screen)
    settings = gtk_settings_get_for_screen (screen);
  else
    settings = gtk_settings_get_default ();
  
  g_object_get (settings,
		"gtk-alternative-button-order", &result, NULL);

  return result;
}

static void
gtk_dialog_set_alternative_button_order_valist (GtkDialog *dialog,
						gint       first_response_id,
						va_list    args)
{
1254
  GtkDialogPriv *priv = dialog->priv;
1255 1256 1257 1258 1259 1260 1261 1262 1263 1264
  GtkWidget *child;
  gint response_id;
  gint position;

  response_id = first_response_id;
  position = 0;
  while (response_id != -1)
    {
      /* reorder child with response_id to position */
      child = dialog_find_button (dialog, response_id);
1265
      gtk_box_reorder_child (GTK_BOX (priv->action_area), child, position);
1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277

      response_id = va_arg (args, gint);
      position++;
    }
}

/**
 * gtk_dialog_set_alternative_button_order:
 * @dialog: a #GtkDialog
 * @first_response_id: a response id used by one @dialog's buttons
 * @Varargs: a list of more response ids of @dialog's buttons, terminated by -1
 *
1278 1279 1280 1281
 * Sets an alternative button order. If the 
 * #GtkSettings:gtk-alternative-button-order setting is set to %TRUE, 
 * the dialog buttons are reordered according to the order of the 
 * response ids passed to this function.
1282 1283 1284 1285 1286 1287 1288 1289 1290 1291
 *
 * By default, GTK+ dialogs use the button order advocated by the Gnome 
 * <ulink url="http://developer.gnome.org/projects/gup/hig/2.0/">Human 
 * Interface Guidelines</ulink> with the affirmative button at the far 
 * right, and the cancel button left of it. But the builtin GTK+ dialogs
 * and #GtkMessageDialog<!-- -->s do provide an alternative button order,
 * which is more suitable on some platforms, e.g. Windows.
 *
 * Use this function after adding all the buttons to your dialog, as the 
 * following example shows:
Matthias Clasen's avatar
Matthias Clasen committed
1292
 * |[
1293 1294 1295
 * cancel_button = gtk_dialog_add_button (GTK_DIALOG (dialog),
 *                                        GTK_STOCK_CANCEL,
 *                                        GTK_RESPONSE_CANCEL);
1296
 *  
1297 1298 1299
 * ok_button = gtk_dialog_add_button (GTK_DIALOG (dialog),
 *                                    GTK_STOCK_OK,
 *                                    GTK_RESPONSE_OK);
1300
 *   
1301
 * gtk_widget_grab_default (ok_button);
1302
 *   
1303 1304 1305
 * help_button = gtk_dialog_add_button (GTK_DIALOG (dialog),
 *                                      GTK_STOCK_HELP,
 *                                      GTK_RESPONSE_HELP);
1306
 *  
1307 1308 1309 1310 1311
 * gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
 *                                          GTK_RESPONSE_OK,
 *                                          GTK_RESPONSE_CANCEL,
 *                                          GTK_RESPONSE_HELP,
 *                                          -1);
Matthias Clasen's avatar
Matthias Clasen committed
1312
 * ]|
1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336
 * 
 * Since: 2.6
 */
void 
gtk_dialog_set_alternative_button_order (GtkDialog *dialog,
					 gint       first_response_id,
					 ...)
{
  GdkScreen *screen;
  va_list args;
  
  g_return_if_fail (GTK_IS_DIALOG (dialog));

  screen = gtk_widget_get_screen (GTK_WIDGET (dialog));
  if (!gtk_alternative_dialog_button_order (screen))
      return;

  va_start (args, first_response_id);

  gtk_dialog_set_alternative_button_order_valist (dialog,
						  first_response_id,
						  args);
  va_end (args);
}
1337 1338 1339 1340 1341 1342
/**
 * gtk_dialog_set_alternative_button_order_from_array:
 * @dialog: a #GtkDialog
 * @n_params: the number of response ids in @new_order
 * @new_order: an array of response ids of @dialog's buttons
 *
1343 1344 1345 1346
 * Sets an alternative button order. If the 
 * #GtkSettings:gtk-alternative-button-order setting is set to %TRUE, 
 * the dialog buttons are reordered according to the order of the 
 * response ids in @new_order.
1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358
 *
 * See gtk_dialog_set_alternative_button_order() for more information.
 *
 * This function is for use by language bindings.
 * 
 * Since: 2.6
 */
void 
gtk_dialog_set_alternative_button_order_from_array (GtkDialog *dialog,
                                                    gint       n_params,
                                                    gint      *new_order)
{
1359
  GtkDialogPriv *priv = dialog->priv;
1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374
  GdkScreen *screen;
  GtkWidget *child;
  gint position;

  g_return_if_fail (GTK_IS_DIALOG (dialog));
  g_return_if_fail (new_order != NULL);

  screen = gtk_widget_get_screen (GTK_WIDGET (dialog));
  if (!gtk_alternative_dialog_button_order (screen))
      return;

  for (position = 0; position < n_params; position++)
  {
      /* reorder child with response_id to position */
      child = dialog_find_button (dialog, new_order[position]);
1375
      gtk_box_reorder_child (GTK_BOX (priv->action_area), child, position);
1376 1377
    }
}
1378

1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402
typedef struct {
  gchar *widget_name;
  gchar *response_id;
} ActionWidgetInfo;

typedef struct {
  GtkDialog *dialog;
  GtkBuilder *builder;
  GSList *items;
  gchar *response;
} ActionWidgetsSubParserData;

static void
attributes_start_element (GMarkupParseContext *context,
			  const gchar         *element_name,
			  const gchar        **names,
			  const gchar        **values,
			  gpointer             user_data,
			  GError             **error)
{
  ActionWidgetsSubParserData *parser_data = (ActionWidgetsSubParserData*)user_data;
  guint i;

  if (strcmp (element_name, "action-widget") == 0)
1403 1404 1405 1406 1407
    {
      for (i = 0; names[i]; i++)
	if (strcmp (names[i], "response") == 0)
	  parser_data->response = g_strdup (values[i]);
    }
1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440
  else if (strcmp (element_name, "action-widgets") == 0)
    return;
  else
    g_warning ("Unsupported tag for GtkDialog: %s\n", element_name);
}

static void
attributes_text_element (GMarkupParseContext *context,
			 const gchar         *text,
			 gsize                text_len,
			 gpointer             user_data,
			 GError             **error)
{
  ActionWidgetsSubParserData *parser_data = (ActionWidgetsSubParserData*)user_data;
  ActionWidgetInfo *item;

  if (!parser_data->response)
    return;

  item = g_new (ActionWidgetInfo, 1);
  item->widget_name = g_strndup (text, text_len);
  item->response_id = parser_data->response;
  parser_data->items = g_slist_prepend (parser_data->items, item);
  parser_data->response = NULL;
}

static const GMarkupParser attributes_parser =
  {
    attributes_start_element,
    NULL,
    attributes_text_element,
  };

1441
static gboolean
1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464
gtk_dialog_buildable_custom_tag_start (GtkBuildable  *buildable,
				       GtkBuilder    *builder,
				       GObject       *child,
				       const gchar   *tagname,
				       GMarkupParser *parser,
				       gpointer      *data)
{
  ActionWidgetsSubParserData *parser_data;

  if (child)
    return FALSE;

  if (strcmp (tagname, "action-widgets") == 0)
    {
      parser_data = g_slice_new0 (ActionWidgetsSubParserData);
      parser_data->dialog = GTK_DIALOG (buildable);
      parser_data->items = NULL;

      *parser = attributes_parser;
      *data = parser_data;
      return TRUE;
    }

1465 1466
  return parent_buildable_iface->custom_tag_start (buildable, builder, child,
						   tagname, parser, data);
1467 1468 1469 1470 1471 1472 1473 1474 1475
}

static void
gtk_dialog_buildable_custom_finished (GtkBuildable *buildable,
				      GtkBuilder   *builder,
				      GObject      *child,
				      const gchar  *tagname,
				      gpointer      user_data)
{
1476 1477
  GtkDialog *dialog = GTK_DIALOG (buildable);
  GtkDialogPriv *priv = dialog->priv;
1478 1479 1480
  GSList *l;
  ActionWidgetsSubParserData *parser_data;
  GObject *object;
1481 1482 1483
  ResponseData *ad;
  guint signal_id;
  
1484
  if (strcmp (tagname, "action-widgets"))
1485 1486 1487
    {
    parent_buildable_iface->custom_finished (buildable, builder, child,
					     tagname, user_data);
1488
    return;
1489
    }
1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500

  parser_data = (ActionWidgetsSubParserData*)user_data;
  parser_data->items = g_slist_reverse (parser_data->items);

  for (l = parser_data->items; l; l = l->next)
    {
      ActionWidgetInfo *item = l->data;

      object = gtk_builder_get_object (builder, item->widget_name);
      if (!object)
	{
1501
	  g_warning ("Unknown object %s specified in action-widgets of %s",
1502
		     item->widget_name,
1503
		     gtk_buildable_get_name (GTK_BUILDABLE (buildable)));
1504 1505 1506
	  continue;
	}

1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528
      ad = get_response_data (GTK_WIDGET (object), TRUE);
      ad->response_id = atoi (item->response_id);

      if (GTK_IS_BUTTON (object))
	signal_id = g_signal_lookup ("clicked", GTK_TYPE_BUTTON);
      else
	signal_id = GTK_WIDGET_GET_CLASS (object)->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 (object,
					  signal_id,
					  0,
					  closure,
					  FALSE);
	}

      if (ad->response_id == GTK_RESPONSE_HELP)
1529
	gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (priv->action_area),
1530
					    GTK_WIDGET (object), TRUE);
1531 1532 1533 1534 1535 1536 1537 1538 1539

      g_free (item->widget_name);
      g_free (item->response_id);
      g_free (item);
    }
  g_slist_free (parser_data->items);
  g_slice_free (ActionWidgetsSubParserData, parser_data);
}

1540 1541 1542 1543 1544 1545
/**
 * gtk_dialog_get_action_area:
 * @dialog: a #GtkDialog
 *
 * Returns the action area of @dialog.
 *
1546
 * Returns: (transfer none): the action area.
1547
 *