gtkfilechooserbutton.c 86.8 KB
Newer Older
1
/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
2 3

/* GTK+: gtkfilechooserbutton.c
4
 *
5 6 7 8 9 10 11 12 13 14 15 16 17
 * Copyright (c) 2004 James M. Cape <jcape@ignore-your.tv>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
Javier Jardon's avatar
Javier Jardon committed
18
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 20
 */

21
#include "config.h"
22 23 24

#include <sys/types.h>
#include <sys/stat.h>
25
#ifdef HAVE_UNISTD_H
26
#include <unistd.h>
27
#endif
28 29 30 31

#include <string.h>

#include "gtkintl.h"
32
#include "gtkbutton.h"
Matthias Clasen's avatar
Matthias Clasen committed
33
#include "gtkcelllayout.h"
Matthias Clasen's avatar
Matthias Clasen committed
34
#include "gtkcellrenderertext.h"
Matthias Clasen's avatar
Matthias Clasen committed
35
#include "gtkcellrendererpixbuf.h"
36
#include "gtkcombobox.h"
37 38
#include "gtkdnd.h"
#include "gtkicontheme.h"
39
#include "deprecated/gtkiconfactory.h"
40 41
#include "gtkimage.h"
#include "gtklabel.h"
42
#include "gtkliststore.h"
43
#include "deprecated/gtkstock.h"
44
#include "gtktreemodelfilter.h"
Javier Jardon's avatar
Javier Jardon committed
45
#include "gtkseparator.h"
46 47 48
#include "gtkfilechooserdialog.h"
#include "gtkfilechooserprivate.h"
#include "gtkfilechooserutils.h"
49
#include "gtkmarshalers.h"
50 51 52

#include "gtkfilechooserbutton.h"

53 54
#include "gtkorientable.h"

55
#include "gtktypebuiltins.h"
56
#include "gtkprivate.h"
57
#include "gtksettings.h"
58

59 60 61 62 63 64 65 66 67 68 69 70 71 72

/**
 * SECTION:gtkfilechooserbutton
 * @Short_description: A button to launch a file selection dialog
 * @Title: GtkFileChooserButton
 * @See_also:#GtkFileChooserDialog
 *
 * The #GtkFileChooserButton is a widget that lets the user select a
 * file.  It implements the #GtkFileChooser interface.  Visually, it is a
 * file name with a button to bring up a #GtkFileChooserDialog.
 * The user can then use that dialog to change the file associated with
 * that button.  This widget does not support setting the
 * #GtkFileChooser:select-multiple property to %TRUE.
 *
73 74
 * ## Create a button to let the user select a file in /etc
 *
75
 * |[<!-- language="C" -->
76 77 78 79 80 81 82 83
 * {
 *   GtkWidget *button;
 *
 *   button = gtk_file_chooser_button_new (_("Select a file"),
 *                                         GTK_FILE_CHOOSER_ACTION_OPEN);
 *   gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (button),
 *                                        "/etc");
 * }
84
 * ]|
85
 *
86
 * The #GtkFileChooserButton supports the #GtkFileChooserActions
87 88
 * %GTK_FILE_CHOOSER_ACTION_OPEN and %GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER.
 *
89 90 91 92 93 94
 * > The #GtkFileChooserButton will ellipsize the label, and will thus
 * > request little horizontal space.  To give the button more space,
 * > you should call gtk_widget_get_preferred_size(),
 * > gtk_file_chooser_button_set_width_chars(), or pack the button in
 * > such a way that other interface elements give space to the
 * > widget.
95 96 97
 */


98 99 100 101
/* **************** *
 *  Private Macros  *
 * **************** */

102
#define FALLBACK_ICON_SIZE	16
103
#define DEFAULT_TITLE		N_("Select a File")
104
#define DESKTOP_DISPLAY_NAME	N_("Desktop")
105
#define FALLBACK_DISPLAY_NAME	N_("(None)") /* this string is used in gtk+/gtk/tests/filechooser.c - change it there if you change it here */
106

107 108 109 110 111 112 113 114 115 116 117

/* ********************** *
 *  Private Enumerations  *
 * ********************** */

/* Property IDs */
enum
{
  PROP_0,

  PROP_DIALOG,
118
  PROP_FOCUS_ON_CLICK,
119
  PROP_TITLE,
120
  PROP_WIDTH_CHARS
121 122
};

123 124 125 126 127 128 129
/* Signals */
enum
{
  FILE_SET,
  LAST_SIGNAL
};

130 131 132 133
/* TreeModel Columns
 *
 * keep in line with the store defined in gtkfilechooserbutton.ui
 */
134 135 136 137 138 139
enum
{
  ICON_COLUMN,
  DISPLAY_NAME_COLUMN,
  TYPE_COLUMN,
  DATA_COLUMN,
140
  IS_FOLDER_COLUMN,
141
  CANCELLABLE_COLUMN,
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
  NUM_COLUMNS
};

/* TreeModel Row Types */
typedef enum
{
  ROW_TYPE_SPECIAL,
  ROW_TYPE_VOLUME,
  ROW_TYPE_SHORTCUT,
  ROW_TYPE_BOOKMARK_SEPARATOR,
  ROW_TYPE_BOOKMARK,
  ROW_TYPE_CURRENT_FOLDER_SEPARATOR,
  ROW_TYPE_CURRENT_FOLDER,
  ROW_TYPE_OTHER_SEPARATOR,
  ROW_TYPE_OTHER,
157
  ROW_TYPE_EMPTY_SELECTION,
158 159 160 161 162

  ROW_TYPE_INVALID = -1
}
RowType;

163 164 165 166 167 168 169 170 171

/* ******************** *
 *  Private Structures  *
 * ******************** */

struct _GtkFileChooserButtonPrivate
{
  GtkWidget *dialog;
  GtkWidget *button;
172 173
  GtkWidget *image;
  GtkWidget *label;
174 175 176 177 178 179
  GtkWidget *combo_box;
  GtkCellRenderer *icon_cell;
  GtkCellRenderer *name_cell;

  GtkTreeModel *model;
  GtkTreeModel *filter_model;
180

181
  GtkFileSystem *fs;
182
  GFile *selection_while_inactive;
183
  GFile *current_folder_while_inactive;
184 185 186

  gulong fs_volumes_changed_id;

187 188 189
  GCancellable *dnd_select_folder_cancellable;
  GCancellable *update_button_cancellable;
  GSList *change_icon_theme_cancellables;
190

191 192
  GtkBookmarksManager *bookmarks_manager;

193
  gint icon_size;
194

195 196 197 198
  guint8 n_special;
  guint8 n_volumes;
  guint8 n_shortcuts;
  guint8 n_bookmarks;
199 200 201 202
  guint  has_bookmark_separator       : 1;
  guint  has_current_folder_separator : 1;
  guint  has_current_folder           : 1;
  guint  has_other_separator          : 1;
203

204
  /* Used for hiding/showing the dialog when the button is hidden */
205
  guint  active                       : 1;
Federico Mena Quintero's avatar
Federico Mena Quintero committed
206

207
  guint  focus_on_click               : 1;
208 209 210

  /* Whether the next async callback from GIO should emit the "selection-changed" signal */
  guint  is_changing_selection        : 1;
211 212 213 214 215 216 217 218 219
};


/* ************* *
 *  DnD Support  *
 * ************* */

enum
{
220 221
  TEXT_PLAIN,
  TEXT_URI_LIST
222 223
};

224

225 226 227 228
/* ********************* *
 *  Function Prototypes  *
 * ********************* */

229 230
/* GtkFileChooserIface Functions */
static void     gtk_file_chooser_button_file_chooser_iface_init (GtkFileChooserIface *iface);
231 232 233 234
static gboolean gtk_file_chooser_button_set_current_folder (GtkFileChooser    *chooser,
							    GFile             *file,
							    GError           **error);
static GFile *gtk_file_chooser_button_get_current_folder (GtkFileChooser    *chooser);
235 236 237 238 239 240 241
static gboolean gtk_file_chooser_button_select_file (GtkFileChooser *chooser,
						     GFile          *file,
						     GError        **error);
static void gtk_file_chooser_button_unselect_file (GtkFileChooser *chooser,
						   GFile          *file);
static void gtk_file_chooser_button_unselect_all (GtkFileChooser *chooser);
static GSList *gtk_file_chooser_button_get_files (GtkFileChooser *chooser);
242
static gboolean gtk_file_chooser_button_add_shortcut_folder     (GtkFileChooser      *chooser,
243
								 GFile               *file,
244 245
								 GError             **error);
static gboolean gtk_file_chooser_button_remove_shortcut_folder  (GtkFileChooser      *chooser,
246
								 GFile               *file,
247 248
								 GError             **error);

249
/* GObject Functions */
250
static void     gtk_file_chooser_button_constructed        (GObject          *object);
251
static void     gtk_file_chooser_button_set_property       (GObject          *object,
252
							    guint             param_id,
253 254 255
							    const GValue     *value,
							    GParamSpec       *pspec);
static void     gtk_file_chooser_button_get_property       (GObject          *object,
256
							    guint             param_id,
257 258
							    GValue           *value,
							    GParamSpec       *pspec);
259
static void     gtk_file_chooser_button_finalize           (GObject          *object);
260 261

/* GtkWidget Functions */
262
static void     gtk_file_chooser_button_destroy            (GtkWidget        *widget);
263 264 265 266 267
static void     gtk_file_chooser_button_drag_data_received (GtkWidget        *widget,
							    GdkDragContext   *context,
							    gint              x,
							    gint              y,
							    GtkSelectionData *data,
268
							    guint             type,
269 270
							    guint             drag_time);
static void     gtk_file_chooser_button_show_all           (GtkWidget        *widget);
271 272
static void     gtk_file_chooser_button_show               (GtkWidget        *widget);
static void     gtk_file_chooser_button_hide               (GtkWidget        *widget);
Federico Mena Quintero's avatar
Federico Mena Quintero committed
273
static void     gtk_file_chooser_button_map                (GtkWidget        *widget);
Matthias Clasen's avatar
2.5.3  
Matthias Clasen committed
274 275
static gboolean gtk_file_chooser_button_mnemonic_activate  (GtkWidget        *widget,
							    gboolean          group_cycling);
276
static void     gtk_file_chooser_button_style_updated      (GtkWidget        *widget);
277 278
static void     gtk_file_chooser_button_screen_changed     (GtkWidget        *widget,
							    GdkScreen        *old_screen);
279 280

/* Utility Functions */
281
static GtkIconTheme *get_icon_theme               (GtkWidget            *widget);
282 283
static void          set_info_for_file_at_iter         (GtkFileChooserButton *fs,
							GFile                *file,
284
							GtkTreeIter          *iter);
285 286 287 288 289

static gint          model_get_type_position      (GtkFileChooserButton *button,
						   RowType               row_type);
static void          model_free_row_data          (GtkFileChooserButton *button,
						   GtkTreeIter          *iter);
290 291 292
static void          model_add_special            (GtkFileChooserButton *button);
static void          model_add_other              (GtkFileChooserButton *button);
static void          model_add_empty_selection    (GtkFileChooserButton *button);
293 294 295 296 297
static void          model_add_volumes            (GtkFileChooserButton *button,
						   GSList               *volumes);
static void          model_add_bookmarks          (GtkFileChooserButton *button,
						   GSList               *bookmarks);
static void          model_update_current_folder  (GtkFileChooserButton *button,
298
						   GFile                *file);
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
static void          model_remove_rows            (GtkFileChooserButton *button,
						   gint                  pos,
						   gint                  n_rows);

static gboolean      filter_model_visible_func    (GtkTreeModel         *model,
						   GtkTreeIter          *iter,
						   gpointer              user_data);

static gboolean      combo_box_row_separator_func (GtkTreeModel         *model,
						   GtkTreeIter          *iter,
						   gpointer              user_data);
static void          name_cell_data_func          (GtkCellLayout        *layout,
						   GtkCellRenderer      *cell,
						   GtkTreeModel         *model,
						   GtkTreeIter          *iter,
						   gpointer              user_data);
static void          open_dialog                  (GtkFileChooserButton *button);
static void          update_combo_box             (GtkFileChooserButton *button);
static void          update_label_and_image       (GtkFileChooserButton *button);

/* Child Object Callbacks */
static void     fs_volumes_changed_cb            (GtkFileSystem  *fs,
						  gpointer        user_data);
322
static void     bookmarks_changed_cb             (gpointer        user_data);
323 324 325

static void     combo_box_changed_cb             (GtkComboBox    *combo_box,
						  gpointer        user_data);
326 327 328
static void     combo_box_notify_popup_shown_cb  (GObject        *object,
						  GParamSpec     *pspec,
						  gpointer        user_data);
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343

static void     button_clicked_cb                (GtkButton      *real_button,
						  gpointer        user_data);

static void     dialog_update_preview_cb         (GtkFileChooser *dialog,
						  gpointer        user_data);
static void     dialog_notify_cb                 (GObject        *dialog,
						  GParamSpec     *pspec,
						  gpointer        user_data);
static gboolean dialog_delete_event_cb           (GtkWidget      *dialog,
						  GdkEvent       *event,
						  gpointer        user_data);
static void     dialog_response_cb               (GtkDialog      *dialog,
						  gint            response,
						  gpointer        user_data);
344

345
static guint file_chooser_button_signals[LAST_SIGNAL] = { 0 };
346 347 348 349 350

/* ******************* *
 *  GType Declaration  *
 * ******************* */

351 352 353 354
G_DEFINE_TYPE_WITH_CODE (GtkFileChooserButton, gtk_file_chooser_button, GTK_TYPE_BOX,
                         G_ADD_PRIVATE (GtkFileChooserButton)
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_CHOOSER,
                                                gtk_file_chooser_button_file_chooser_iface_init))
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369


/* ***************** *
 *  GType Functions  *
 * ***************** */

static void
gtk_file_chooser_button_class_init (GtkFileChooserButtonClass * class)
{
  GObjectClass *gobject_class;
  GtkWidgetClass *widget_class;

  gobject_class = G_OBJECT_CLASS (class);
  widget_class = GTK_WIDGET_CLASS (class);

370
  gobject_class->constructed = gtk_file_chooser_button_constructed;
371 372
  gobject_class->set_property = gtk_file_chooser_button_set_property;
  gobject_class->get_property = gtk_file_chooser_button_get_property;
373
  gobject_class->finalize = gtk_file_chooser_button_finalize;
374

375
  widget_class->destroy = gtk_file_chooser_button_destroy;
376 377
  widget_class->drag_data_received = gtk_file_chooser_button_drag_data_received;
  widget_class->show_all = gtk_file_chooser_button_show_all;
378 379
  widget_class->show = gtk_file_chooser_button_show;
  widget_class->hide = gtk_file_chooser_button_hide;
Federico Mena Quintero's avatar
Federico Mena Quintero committed
380
  widget_class->map = gtk_file_chooser_button_map;
381
  widget_class->style_updated = gtk_file_chooser_button_style_updated;
382
  widget_class->screen_changed = gtk_file_chooser_button_screen_changed;
Matthias Clasen's avatar
2.5.3  
Matthias Clasen committed
383
  widget_class->mnemonic_activate = gtk_file_chooser_button_mnemonic_activate;
384

385
  /**
386
   * GtkFileChooserButton::file-set:
387 388 389
   * @widget: the object which received the signal.
   *
   * The ::file-set signal is emitted when the user selects a file.
390
   *
391
   * Note that this signal is only emitted when the user
392
   * changes the file.
393 394 395 396 397 398
   *
   * Since: 2.12
   */
  file_chooser_button_signals[FILE_SET] =
    g_signal_new (I_("file-set"),
		  G_TYPE_FROM_CLASS (gobject_class),
399
		  G_SIGNAL_RUN_FIRST,
400 401 402 403
		  G_STRUCT_OFFSET (GtkFileChooserButtonClass, file_set),
		  NULL, NULL,
		  _gtk_marshal_VOID__VOID,
		  G_TYPE_NONE, 0);
404

Matthias Clasen's avatar
Matthias Clasen committed
405 406
  /**
   * GtkFileChooserButton:dialog:
407
   *
Matthias Clasen's avatar
Matthias Clasen committed
408 409 410 411
   * Instance of the #GtkFileChooserDialog associated with the button.
   *
   * Since: 2.6
   */
412 413 414 415
  g_object_class_install_property (gobject_class, PROP_DIALOG,
				   g_param_spec_object ("dialog",
							P_("Dialog"),
							P_("The file chooser dialog to use."),
416
							GTK_TYPE_FILE_CHOOSER,
417
							(GTK_PARAM_WRITABLE |
418
							 G_PARAM_CONSTRUCT_ONLY)));
Matthias Clasen's avatar
Matthias Clasen committed
419

420 421
  /**
   * GtkFileChooserButton:focus-on-click:
422
   *
423 424 425 426 427 428 429 430 431 432 433 434
   * Whether the #GtkFileChooserButton button grabs focus when it is clicked
   * with the mouse.
   *
   * Since: 2.10
   */
  g_object_class_install_property (gobject_class,
                                   PROP_FOCUS_ON_CLICK,
                                   g_param_spec_boolean ("focus-on-click",
							 P_("Focus on click"),
							 P_("Whether the button grabs focus when it is clicked with the mouse"),
							 TRUE,
							 GTK_PARAM_READWRITE));
435

Matthias Clasen's avatar
Matthias Clasen committed
436 437
  /**
   * GtkFileChooserButton:title:
438
   *
Matthias Clasen's avatar
Matthias Clasen committed
439 440 441 442
   * Title to put on the #GtkFileChooserDialog associated with the button.
   *
   * Since: 2.6
   */
443 444 445 446
  g_object_class_install_property (gobject_class, PROP_TITLE,
				   g_param_spec_string ("title",
							P_("Title"),
							P_("The title of the file chooser dialog."),
447
							_(DEFAULT_TITLE),
448
							GTK_PARAM_READWRITE));
Matthias Clasen's avatar
Matthias Clasen committed
449 450

  /**
Matthias Clasen's avatar
Matthias Clasen committed
451
   * GtkFileChooserButton:width-chars:
452
   *
Matthias Clasen's avatar
Matthias Clasen committed
453 454 455 456
   * The width of the entry and label inside the button, in characters.
   *
   * Since: 2.6
   */
457 458 459 460 461
  g_object_class_install_property (gobject_class, PROP_WIDTH_CHARS,
				   g_param_spec_int ("width-chars",
						     P_("Width In Characters"),
						     P_("The desired width of the button widget, in characters."),
						     -1, G_MAXINT, -1,
462
						     GTK_PARAM_READWRITE));
463 464 465

  _gtk_file_chooser_install_properties (gobject_class);

466 467 468
  /* Bind class to template
   */
  gtk_widget_class_set_template_from_resource (widget_class,
469
					       "/org/gtk/libgtk/ui/gtkfilechooserbutton.ui");
470

471 472 473 474 475 476 477
  gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserButton, model);
  gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserButton, button);
  gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserButton, image);
  gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserButton, label);
  gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserButton, combo_box);
  gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserButton, icon_cell);
  gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserButton, name_cell);
478 479 480 481

  gtk_widget_class_bind_template_callback (widget_class, button_clicked_cb);
  gtk_widget_class_bind_template_callback (widget_class, combo_box_changed_cb);
  gtk_widget_class_bind_template_callback (widget_class, combo_box_notify_popup_shown_cb);
482 483 484 485 486 487
}

static void
gtk_file_chooser_button_init (GtkFileChooserButton *button)
{
  GtkFileChooserButtonPrivate *priv;
488
  GtkTargetList *target_list;
489

490
  priv = button->priv = gtk_file_chooser_button_get_instance_private (button);
491

492
  priv->icon_size = FALLBACK_ICON_SIZE;
493
  priv->focus_on_click = TRUE;
494

495 496
  gtk_widget_init_template (GTK_WIDGET (button));

497 498
  /* Bookmarks manager */
  priv->bookmarks_manager = _gtk_bookmarks_manager_new (bookmarks_changed_cb, button);
499 500 501 502
  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (priv->combo_box),
				      priv->name_cell, name_cell_data_func,
				      NULL, NULL);

503 504
  /* DnD */
  gtk_drag_dest_set (GTK_WIDGET (button),
505
                     (GTK_DEST_DEFAULT_ALL),
Matthias Clasen's avatar
Matthias Clasen committed
506
		     NULL, 0,
507
		     GDK_ACTION_COPY);
508 509 510 511 512
  target_list = gtk_target_list_new (NULL, 0);
  gtk_target_list_add_uri_targets (target_list, TEXT_URI_LIST);
  gtk_target_list_add_text_targets (target_list, TEXT_PLAIN);
  gtk_drag_dest_set_target_list (GTK_WIDGET (button), target_list);
  gtk_target_list_unref (target_list);
513 514 515
}


516 517 518 519 520 521 522 523
/* ******************************* *
 *  GtkFileChooserIface Functions  *
 * ******************************* */
static void
gtk_file_chooser_button_file_chooser_iface_init (GtkFileChooserIface *iface)
{
  _gtk_file_chooser_delegate_iface_init (iface);

524 525
  iface->set_current_folder = gtk_file_chooser_button_set_current_folder;
  iface->get_current_folder = gtk_file_chooser_button_get_current_folder;
526 527 528 529
  iface->select_file = gtk_file_chooser_button_select_file;
  iface->unselect_file = gtk_file_chooser_button_unselect_file;
  iface->unselect_all = gtk_file_chooser_button_unselect_all;
  iface->get_files = gtk_file_chooser_button_get_files;
530 531 532 533
  iface->add_shortcut_folder = gtk_file_chooser_button_add_shortcut_folder;
  iface->remove_shortcut_folder = gtk_file_chooser_button_remove_shortcut_folder;
}

534 535 536 537 538 539 540 541 542 543 544 545
static void
emit_selection_changed_if_changing_selection (GtkFileChooserButton *button)
{
  GtkFileChooserButtonPrivate *priv = button->priv;

  if (priv->is_changing_selection)
    {
      priv->is_changing_selection = FALSE;
      g_signal_emit_by_name (button, "selection-changed");
    }
}

546 547 548 549 550 551 552 553
static gboolean
gtk_file_chooser_button_set_current_folder (GtkFileChooser    *chooser,
					    GFile             *file,
					    GError           **error)
{
  GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
  GtkFileChooserButtonPrivate *priv = button->priv;

554 555
  if (priv->current_folder_while_inactive)
    g_object_unref (priv->current_folder_while_inactive);
556

557
  priv->current_folder_while_inactive = g_object_ref (file);
558

559
  update_combo_box (button);
560

561
  g_signal_emit_by_name (button, "current-folder-changed");
562

563 564
  if (priv->active)
    gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (priv->dialog), file, NULL);
565

566
  return TRUE;
567 568 569 570 571 572 573 574
}

static GFile *
gtk_file_chooser_button_get_current_folder (GtkFileChooser *chooser)
{
  GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
  GtkFileChooserButtonPrivate *priv = button->priv;

575 576
  if (priv->current_folder_while_inactive)
    return g_object_ref (priv->current_folder_while_inactive);
577
  else
578
    return NULL;
579 580
}

581 582 583 584 585 586 587 588
static gboolean
gtk_file_chooser_button_select_file (GtkFileChooser *chooser,
				     GFile          *file,
				     GError        **error)
{
  GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
  GtkFileChooserButtonPrivate *priv = button->priv;

589 590
  if (priv->selection_while_inactive)
    g_object_unref (priv->selection_while_inactive);
591

592 593 594
  priv->selection_while_inactive = g_object_ref (file);

  priv->is_changing_selection = TRUE;
595

596 597
  update_label_and_image (button);
  update_combo_box (button);
598

599 600
  if (priv->active)
    gtk_file_chooser_select_file (GTK_FILE_CHOOSER (priv->dialog), file, NULL);
601

602 603
  return TRUE;
}
604

605 606 607 608 609 610 611 612 613
static void
unselect_current_file (GtkFileChooserButton *button)
{
  GtkFileChooserButtonPrivate *priv = button->priv;

  if (priv->selection_while_inactive)
    {
      g_object_unref (priv->selection_while_inactive);
      priv->selection_while_inactive = NULL;
614
    }
615 616 617 618 619

  priv->is_changing_selection = TRUE;

  update_label_and_image (button);
  update_combo_box (button);
620 621 622 623 624 625 626 627 628
}

static void
gtk_file_chooser_button_unselect_file (GtkFileChooser *chooser,
				       GFile          *file)
{
  GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
  GtkFileChooserButtonPrivate *priv = button->priv;

629 630
  if (g_file_equal (priv->selection_while_inactive, file))
    unselect_current_file (button);
631 632

  if (priv->active)
633
    gtk_file_chooser_unselect_file (GTK_FILE_CHOOSER (priv->dialog), file);
634 635 636 637 638 639 640 641
}

static void
gtk_file_chooser_button_unselect_all (GtkFileChooser *chooser)
{
  GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
  GtkFileChooserButtonPrivate *priv = button->priv;

642
  unselect_current_file (button);
643 644

  if (priv->active)
645
    gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
646 647
}

648 649
static GFile *
get_selected_file (GtkFileChooserButton *button)
650 651
{
  GtkFileChooserButtonPrivate *priv = button->priv;
652
  GFile *retval;
653

654 655 656 657 658
  retval = NULL;

  if (priv->selection_while_inactive)
    retval = priv->selection_while_inactive;
  else if (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog)) == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
659
    {
660
      /* If there is no "real" selection in SELECT_FOLDER mode, then we'll just return
661
       * the current folder, since that is what GtkFileChooserWidget would do.
662 663 664
       */
      if (priv->current_folder_while_inactive)
	retval = priv->current_folder_while_inactive;
665
    }
666

667 668 669 670
  if (retval)
    return g_object_ref (retval);
  else
    return NULL;
671 672 673 674 675 676
}

static GSList *
gtk_file_chooser_button_get_files (GtkFileChooser *chooser)
{
  GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
677
  GFile *file;
678

679 680 681 682 683
  file = get_selected_file (button);
  if (file)
    return g_slist_prepend (NULL, file);
  else
    return NULL;
684 685
}

686
static gboolean
687 688 689
gtk_file_chooser_button_add_shortcut_folder (GtkFileChooser  *chooser,
					     GFile           *file,
					     GError         **error)
690 691 692 693 694 695
{
  GtkFileChooser *delegate;
  gboolean retval;

  delegate = g_object_get_qdata (G_OBJECT (chooser),
				 GTK_FILE_CHOOSER_DELEGATE_QUARK);
696
  retval = _gtk_file_chooser_add_shortcut_folder (delegate, file, error);
697 698 699

  if (retval)
    {
700 701
      GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
      GtkFileChooserButtonPrivate *priv = button->priv;
702 703 704
      GtkTreeIter iter;
      gint pos;

705
      pos = model_get_type_position (button, ROW_TYPE_SHORTCUT);
706 707 708 709
      pos += priv->n_shortcuts;

      gtk_list_store_insert (GTK_LIST_STORE (priv->model), &iter, pos);
      gtk_list_store_set (GTK_LIST_STORE (priv->model), &iter,
710 711
			  ICON_COLUMN, NULL,
			  DISPLAY_NAME_COLUMN, _(FALLBACK_DISPLAY_NAME),
712
			  TYPE_COLUMN, ROW_TYPE_SHORTCUT,
713
			  DATA_COLUMN, g_object_ref (file),
714
			  IS_FOLDER_COLUMN, FALSE,
715
			  -1);
716
      set_info_for_file_at_iter (button, file, &iter);
717 718 719 720 721 722 723 724 725
      priv->n_shortcuts++;

      gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
    }

  return retval;
}

static gboolean
726 727 728
gtk_file_chooser_button_remove_shortcut_folder (GtkFileChooser  *chooser,
						GFile           *file,
						GError         **error)
729 730 731 732 733 734 735
{
  GtkFileChooser *delegate;
  gboolean retval;

  delegate = g_object_get_qdata (G_OBJECT (chooser),
				 GTK_FILE_CHOOSER_DELEGATE_QUARK);

736
  retval = _gtk_file_chooser_remove_shortcut_folder (delegate, file, error);
737 738 739

  if (retval)
    {
740 741
      GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
      GtkFileChooserButtonPrivate *priv = button->priv;
742 743 744 745
      GtkTreeIter iter;
      gint pos;
      gchar type;

746
      pos = model_get_type_position (button, ROW_TYPE_SHORTCUT);
747
      gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, pos);
748 749 750 751 752 753 754 755 756 757 758

      do
	{
	  gpointer data;

	  gtk_tree_model_get (priv->model, &iter,
			      TYPE_COLUMN, &type,
			      DATA_COLUMN, &data,
			      -1);

	  if (type == ROW_TYPE_SHORTCUT &&
759
	      data && g_file_equal (data, file))
760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776
	    {
	      model_free_row_data (GTK_FILE_CHOOSER_BUTTON (chooser), &iter);
	      gtk_list_store_remove (GTK_LIST_STORE (priv->model), &iter);
	      priv->n_shortcuts--;
	      gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
	      update_combo_box (GTK_FILE_CHOOSER_BUTTON (chooser));
	      break;
	    }
	}
      while (type == ROW_TYPE_SHORTCUT &&
	     gtk_tree_model_iter_next (priv->model, &iter));
    }

  return retval;
}


777 778 779 780
/* ******************* *
 *  GObject Functions  *
 * ******************* */

781 782
static void
gtk_file_chooser_button_constructed (GObject *object)
783
{
784 785
  GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (object);
  GtkFileChooserButtonPrivate *priv = button->priv;
786
  GSList *list;
787

788
  G_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->constructed (object);
789 790 791

  if (!priv->dialog)
    {
792 793
      priv->dialog = gtk_file_chooser_dialog_new (NULL, NULL,
						  GTK_FILE_CHOOSER_ACTION_OPEN,
Matthias Clasen's avatar
Matthias Clasen committed
794 795
						  _("_Cancel"), GTK_RESPONSE_CANCEL,
						  _("_Open"), GTK_RESPONSE_ACCEPT,
796
						  NULL);
797 798 799

      gtk_dialog_set_default_response (GTK_DIALOG (priv->dialog),
				       GTK_RESPONSE_ACCEPT);
800
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
801 802 803 804
      gtk_dialog_set_alternative_button_order (GTK_DIALOG (priv->dialog),
					       GTK_RESPONSE_ACCEPT,
					       GTK_RESPONSE_CANCEL,
					       -1);
805
G_GNUC_END_IGNORE_DEPRECATIONS
806

807 808
      gtk_file_chooser_button_set_title (button, _(DEFAULT_TITLE));
    }
809
  else if (!gtk_window_get_title (GTK_WINDOW (priv->dialog)))
810 811 812
    {
      gtk_file_chooser_button_set_title (button, _(DEFAULT_TITLE));
    }
813

814
  g_signal_connect (priv->dialog, "delete-event",
815 816 817 818 819 820 821
		    G_CALLBACK (dialog_delete_event_cb), object);
  g_signal_connect (priv->dialog, "response",
		    G_CALLBACK (dialog_response_cb), object);

  /* This is used, instead of the standard delegate, to ensure that signals are only
   * delegated when the OK button is pressed. */
  g_object_set_qdata (object, GTK_FILE_CHOOSER_DELEGATE_QUARK, priv->dialog);
822

823 824 825 826 827
  g_signal_connect (priv->dialog, "update-preview",
		    G_CALLBACK (dialog_update_preview_cb), object);
  g_signal_connect (priv->dialog, "notify",
		    G_CALLBACK (dialog_notify_cb), object);
  g_object_add_weak_pointer (G_OBJECT (priv->dialog),
828
			     (gpointer) (&priv->dialog));
829

830 831 832
  priv->fs =
    g_object_ref (_gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog)));

833
  model_add_special (button);
834

835
  list = _gtk_file_system_list_volumes (priv->fs);
836
  model_add_volumes (button, list);
837 838
  g_slist_free (list);

839
  list = _gtk_bookmarks_manager_list_bookmarks (priv->bookmarks_manager);
840
  model_add_bookmarks (button, list);
841 842
  g_slist_foreach (list, (GFunc) g_object_unref, NULL);
  g_slist_free (list);
843

844
  model_add_other (button);
845

846 847
  model_add_empty_selection (button);

848 849 850 851 852 853 854 855 856 857
  priv->filter_model = gtk_tree_model_filter_new (priv->model, NULL);
  gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (priv->filter_model),
					  filter_model_visible_func,
					  object, NULL);

  gtk_combo_box_set_model (GTK_COMBO_BOX (priv->combo_box), priv->filter_model);
  gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (priv->combo_box),
					combo_box_row_separator_func,
					NULL, NULL);

858 859 860
  /* set up the action for a user-provided dialog, this also updates
   * the label, image and combobox
   */
861
  g_object_set (object,
862 863
		"action", gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog)),
		NULL);
864 865 866 867 868

  priv->fs_volumes_changed_id =
    g_signal_connect (priv->fs, "volumes-changed",
		      G_CALLBACK (fs_volumes_changed_cb), object);

869 870
  update_label_and_image (button);
  update_combo_box (button);
871
}
872 873 874

static void
gtk_file_chooser_button_set_property (GObject      *object,
875
				      guint         param_id,
876 877 878
				      const GValue *value,
				      GParamSpec   *pspec)
{
879 880
  GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (object);
  GtkFileChooserButtonPrivate *priv = button->priv;
881

882
  switch (param_id)
883 884
    {
    case PROP_DIALOG:
885 886
      /* Construct-only */
      priv->dialog = g_value_get_object (value);
887
      break;
888 889 890
    case PROP_FOCUS_ON_CLICK:
      gtk_file_chooser_button_set_focus_on_click (button, g_value_get_boolean (value));
      break;
891 892 893 894
    case PROP_WIDTH_CHARS:
      gtk_file_chooser_button_set_width_chars (GTK_FILE_CHOOSER_BUTTON (object),
					       g_value_get_int (value));
      break;
895
    case GTK_FILE_CHOOSER_PROP_ACTION:
896 897 898 899 900 901 902 903 904 905
      switch (g_value_get_enum (value))
	{
	case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
	case GTK_FILE_CHOOSER_ACTION_SAVE:
	  {
	    GEnumClass *eclass;
	    GEnumValue *eval;

	    eclass = g_type_class_peek (GTK_TYPE_FILE_CHOOSER_ACTION);
	    eval = g_enum_get_value (eclass, g_value_get_enum (value));
906
	    g_warning ("%s: Choosers of type `%s' do not support `%s'.",
907 908
		       G_STRFUNC, G_OBJECT_TYPE_NAME (object), eval->value_name);

909
	    g_value_set_enum ((GValue *) value, GTK_FILE_CHOOSER_ACTION_OPEN);
910 911 912
	  }
	  break;
	}
913

914 915
      g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value);
      update_label_and_image (GTK_FILE_CHOOSER_BUTTON (object));
916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931
      update_combo_box (GTK_FILE_CHOOSER_BUTTON (object));

      switch (g_value_get_enum (value))
	{
	case GTK_FILE_CHOOSER_ACTION_OPEN:
	  gtk_widget_hide (priv->combo_box);
	  gtk_widget_show (priv->button);
	  break;
	case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
	  gtk_widget_hide (priv->button);
	  gtk_widget_show (priv->combo_box);
	  break;
	default:
	  g_assert_not_reached ();
	  break;
	}
932 933 934 935 936 937 938 939 940
      break;

    case PROP_TITLE:
    case GTK_FILE_CHOOSER_PROP_FILTER:
    case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET:
    case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE:
    case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL:
    case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET:
    case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN:
941
    case GTK_FILE_CHOOSER_PROP_DO_OVERWRITE_CONFIRMATION:
942
    case GTK_FILE_CHOOSER_PROP_CREATE_FOLDERS:
943 944 945
      g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value);
      break;

946 947 948
    case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY:
      g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value);
      fs_volumes_changed_cb (priv->fs, button);
949
      bookmarks_changed_cb (button);
950 951
      break;

952 953 954 955 956
    case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE:
      g_warning ("%s: Choosers of type `%s` do not support selecting multiple files.",
		 G_STRFUNC, G_OBJECT_TYPE_NAME (object));
      break;
    default:
957
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
958 959 960 961 962 963
      break;
    }
}

static void
gtk_file_chooser_button_get_property (GObject    *object,
964
				      guint       param_id,
965 966 967
				      GValue     *value,
				      GParamSpec *pspec)
{
968 969
  GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (object);
  GtkFileChooserButtonPrivate *priv = button->priv;
970 971

  switch (param_id)
972
    {
973 974
    case PROP_WIDTH_CHARS:
      g_value_set_int (value,
975
		       gtk_label_get_width_chars (GTK_LABEL (priv->label)));
976
      break;
977 978 979 980
    case PROP_FOCUS_ON_CLICK:
      g_value_set_boolean (value,
                           gtk_file_chooser_button_get_focus_on_click (button));
      break;
981 982 983 984 985 986 987 988 989 990 991

    case PROP_TITLE:
    case GTK_FILE_CHOOSER_PROP_ACTION:
    case GTK_FILE_CHOOSER_PROP_FILTER:
    case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY:
    case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET:
    case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE:
    case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL:
    case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET:
    case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE:
    case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN:
992
    case GTK_FILE_CHOOSER_PROP_DO_OVERWRITE_CONFIRMATION:
993
    case GTK_FILE_CHOOSER_PROP_CREATE_FOLDERS:
994 995 996 997
      g_object_get_property (G_OBJECT (priv->dialog), pspec->name, value);
      break;

    default:
998
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
999 1000 1001 1002
      break;
    }
}

1003 1004 1005
static void
gtk_file_chooser_button_finalize (GObject *object)
{
1006 1007
  GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (object);
  GtkFileChooserButtonPrivate *priv = button->priv;
1008

1009 1010
  if (priv->selection_while_inactive)
    g_object_unref (priv->selection_while_inactive);
1011

1012 1013 1014
  if (priv->current_folder_while_inactive)
    g_object_unref (priv->current_folder_while_inactive);

1015
  G_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->finalize (object);
1016
}
1017 1018

/* ********************* *
1019
 *  GtkWidget Functions  *
1020 1021 1022
 * ********************* */

static void
1023
gtk_file_chooser_button_destroy (GtkWidget *widget)
1024
{
1025
  GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (widget);
1026
  GtkFileChooserButtonPrivate *priv = button->priv;
1027 1028
  GtkTreeIter iter;
  GSList *l;
1029 1030

  if (priv->dialog != NULL)
1031 1032 1033 1034 1035
    {
      gtk_widget_destroy (priv->dialog);
      priv->dialog = NULL;
    }

1036
  if (priv->model && gtk_tree_model_get_iter_first (priv->model, &iter)) do
1037 1038 1039 1040 1041
    {
      model_free_row_data (button, &iter);
    }
  while (gtk_tree_model_iter_next (priv->model, &iter));

1042
  if (priv->dnd_select_folder_cancellable)
1043
    {
1044 1045
      g_cancellable_cancel (priv->dnd_select_folder_cancellable);
      priv->dnd_select_folder_cancellable = NULL;
1046 1047
    }

1048
  if (priv->update_button_cancellable)
1049
    {
1050 1051
      g_cancellable_cancel (priv->update_button_cancellable);
      priv->update_button_cancellable = NULL;
1052 1053
    }

1054
  if (priv->change_icon_theme_cancellables)
1055
    {
1056
      for (l = priv->change_icon_theme_cancellables; l; l = l->next)
1057
        {
1058 1059
	  GCancellable *cancellable = G_CANCELLABLE (l->data);
	  g_cancellable_cancel (cancellable);
1060
        }
1061 1062
      g_slist_free (priv->change_icon_theme_cancellables);
      priv->change_icon_theme_cancellables = NULL;
1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076
    }

  if (priv->filter_model)
    {
      g_object_unref (priv->filter_model);
      priv->filter_model = NULL;
    }

  if (priv->fs)
    {
      g_signal_handler_disconnect (priv->fs, priv->fs_volumes_changed_id);
      g_object_unref (priv->fs);
      priv->fs = NULL;
    }
1077

1078 1079 1080 1081 1082 1083
  if (priv->bookmarks_manager)
    {
      _gtk_bookmarks_manager_free (priv->bookmarks_manager);
      priv->bookmarks_manager = NULL;
    }

1084
  GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->destroy (widget);
1085 1086
}

1087 1088
struct DndSelectFolderData
{
1089
  GtkFileSystem *file_system;
1090 1091
  GtkFileChooserButton *button;
  GtkFileChooserAction action;
1092
  GFile *file;
1093 1094 1095 1096 1097 1098
  gchar **uris;
  guint i;
  gboolean selected;
};

static void
1099 1100 1101 1102
dnd_select_folder_get_info_cb (GCancellable *cancellable,
			       GFileInfo    *info,
			       const GError *error,
			       gpointer      user_data)
1103
{
1104
  gboolean cancelled = g_cancellable_is_cancelled (cancellable);
1105 1106
  struct DndSelectFolderData *data = user_data;

1107
  if (cancellable != data->button->priv->dnd_select_folder_cancellable)
1108 1109
    {
      g_object_unref (data->button);
1110
      g_object_unref (data->file);
1111 1112 1113
      g_strfreev (data->uris);
      g_free (data);

1114
      g_object_unref (cancellable);
1115 1116 1117
      return;
    }

1118
  data->button->priv->dnd_select_folder_cancellable = NULL;
1119 1120 1121

  if (!cancelled && !error && info != NULL)
    {
1122 1123
      gboolean is_folder;

1124
      is_folder = _gtk_file_info_consider_as_directory (info);
1125 1126 1127 1128

      data->selected =
	(((data->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER && is_folder) ||
	  (data->action == GTK_FILE_CHOOSER_ACTION_OPEN && !is_folder)) &&
1129 1130
	 gtk_file_chooser_select_file (GTK_FILE_CHOOSER (data->button->priv->dialog),
				       data->file, NULL));
1131 1132 1133 1134 1135 1136
    }
  else
    data->selected = FALSE;

  if (data->selected || data->uris[++data->i] == NULL)
    {
1137 1138
      g_signal_emit (data->button, file_chooser_button_signals[FILE_SET], 0);

1139
      g_object_unref (