gtkfilechooserbutton.c 87 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 "gtkiconfactory.h"
40 41
#include "gtkimage.h"
#include "gtklabel.h"
42
#include "gtkliststore.h"
43
#include "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 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91

/**
 * 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.
 *
 * <example>
 * <title>Create a button to let the user select a file in /etc</title>
 * <programlisting>
 * {
 *   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");
 * }
 * </programlisting>
 * </example>
 *
 * The #GtkFileChooserButton supports the #GtkFileChooserAction<!-- -->s
 * %GTK_FILE_CHOOSER_ACTION_OPEN and %GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER.
 *
 * <important>
 * The #GtkFileChooserButton will ellipsize the label,
92
 * and will thus request little horizontal space.  To give the button
93 94 95 96 97 98 99
 * 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.
 * </important>
 */


100 101 102 103
/* **************** *
 *  Private Macros  *
 * **************** */

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

109 110 111 112 113 114 115 116 117 118 119

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

/* Property IDs */
enum
{
  PROP_0,

  PROP_DIALOG,
120
  PROP_FOCUS_ON_CLICK,
121
  PROP_TITLE,
122
  PROP_WIDTH_CHARS
123 124
};

125 126 127 128 129 130 131
/* Signals */
enum
{
  FILE_SET,
  LAST_SIGNAL
};

132 133 134 135
/* TreeModel Columns
 *
 * keep in line with the store defined in gtkfilechooserbutton.ui
 */
136 137 138 139 140 141
enum
{
  ICON_COLUMN,
  DISPLAY_NAME_COLUMN,
  TYPE_COLUMN,
  DATA_COLUMN,
142
  IS_FOLDER_COLUMN,
143
  CANCELLABLE_COLUMN,
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
  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,
159
  ROW_TYPE_EMPTY_SELECTION,
160 161 162 163 164

  ROW_TYPE_INVALID = -1
}
RowType;

165 166 167 168 169 170 171 172 173

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

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

  GtkTreeModel *model;
  GtkTreeModel *filter_model;
182

183
  GtkFileSystem *fs;
184
  GFile *selection_while_inactive;
185
  GFile *current_folder_while_inactive;
186 187 188

  gulong fs_volumes_changed_id;

189 190 191
  GCancellable *dnd_select_folder_cancellable;
  GCancellable *update_button_cancellable;
  GSList *change_icon_theme_cancellables;
192

193 194
  GtkBookmarksManager *bookmarks_manager;

195
  gint icon_size;
196

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

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

209
  guint  focus_on_click               : 1;
210 211 212

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


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

enum
{
222 223
  TEXT_PLAIN,
  TEXT_URI_LIST
224 225
};

226

227 228 229 230
/* ********************* *
 *  Function Prototypes  *
 * ********************* */

231 232
/* GtkFileChooserIface Functions */
static void     gtk_file_chooser_button_file_chooser_iface_init (GtkFileChooserIface *iface);
233 234 235 236
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);
237 238 239 240 241 242 243
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);
244
static gboolean gtk_file_chooser_button_add_shortcut_folder     (GtkFileChooser      *chooser,
245
								 GFile               *file,
246 247
								 GError             **error);
static gboolean gtk_file_chooser_button_remove_shortcut_folder  (GtkFileChooser      *chooser,
248
								 GFile               *file,
249 250
								 GError             **error);

251
/* GObject Functions */
252 253 254
static GObject *gtk_file_chooser_button_constructor        (GType             type,
							    guint             n_params,
							    GObjectConstructParam *params);
255
static void     gtk_file_chooser_button_set_property       (GObject          *object,
256
							    guint             param_id,
257 258 259
							    const GValue     *value,
							    GParamSpec       *pspec);
static void     gtk_file_chooser_button_get_property       (GObject          *object,
260
							    guint             param_id,
261 262
							    GValue           *value,
							    GParamSpec       *pspec);
263
static void     gtk_file_chooser_button_finalize           (GObject          *object);
264 265

/* GtkWidget Functions */
266
static void     gtk_file_chooser_button_destroy            (GtkWidget        *widget);
267 268 269 270 271
static void     gtk_file_chooser_button_drag_data_received (GtkWidget        *widget,
							    GdkDragContext   *context,
							    gint              x,
							    gint              y,
							    GtkSelectionData *data,
272
							    guint             type,
273 274
							    guint             drag_time);
static void     gtk_file_chooser_button_show_all           (GtkWidget        *widget);
275 276
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
277
static void     gtk_file_chooser_button_map                (GtkWidget        *widget);
Matthias Clasen's avatar
2.5.3  
Matthias Clasen committed
278 279
static gboolean gtk_file_chooser_button_mnemonic_activate  (GtkWidget        *widget,
							    gboolean          group_cycling);
280
static void     gtk_file_chooser_button_style_updated      (GtkWidget        *widget);
281 282
static void     gtk_file_chooser_button_screen_changed     (GtkWidget        *widget,
							    GdkScreen        *old_screen);
283 284

/* Utility Functions */
285
static GtkIconTheme *get_icon_theme               (GtkWidget            *widget);
286 287
static void          set_info_for_file_at_iter         (GtkFileChooserButton *fs,
							GFile                *file,
288
							GtkTreeIter          *iter);
289 290 291 292 293

static gint          model_get_type_position      (GtkFileChooserButton *button,
						   RowType               row_type);
static void          model_free_row_data          (GtkFileChooserButton *button,
						   GtkTreeIter          *iter);
294 295 296
static void          model_add_special            (GtkFileChooserButton *button);
static void          model_add_other              (GtkFileChooserButton *button);
static void          model_add_empty_selection    (GtkFileChooserButton *button);
297 298 299 300 301
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,
302
						   GFile                *file);
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
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);
326
static void     bookmarks_changed_cb             (gpointer        user_data);
327 328 329

static void     combo_box_changed_cb             (GtkComboBox    *combo_box,
						  gpointer        user_data);
330 331 332
static void     combo_box_notify_popup_shown_cb  (GObject        *object,
						  GParamSpec     *pspec,
						  gpointer        user_data);
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347

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);
348

349
static guint file_chooser_button_signals[LAST_SIGNAL] = { 0 };
350 351 352 353 354

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

355
G_DEFINE_TYPE_WITH_CODE (GtkFileChooserButton, gtk_file_chooser_button, GTK_TYPE_BOX, { \
356
    G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_CHOOSER, gtk_file_chooser_button_file_chooser_iface_init) \
Matthias Clasen's avatar
Matthias Clasen committed
357
})
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372


/* ***************** *
 *  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);

373
  gobject_class->constructor = gtk_file_chooser_button_constructor;
374 375
  gobject_class->set_property = gtk_file_chooser_button_set_property;
  gobject_class->get_property = gtk_file_chooser_button_get_property;
376
  gobject_class->finalize = gtk_file_chooser_button_finalize;
377

378
  widget_class->destroy = gtk_file_chooser_button_destroy;
379 380
  widget_class->drag_data_received = gtk_file_chooser_button_drag_data_received;
  widget_class->show_all = gtk_file_chooser_button_show_all;
381 382
  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
383
  widget_class->map = gtk_file_chooser_button_map;
384
  widget_class->style_updated = gtk_file_chooser_button_style_updated;
385
  widget_class->screen_changed = gtk_file_chooser_button_screen_changed;
Matthias Clasen's avatar
2.5.3  
Matthias Clasen committed
386
  widget_class->mnemonic_activate = gtk_file_chooser_button_mnemonic_activate;
387

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

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

423 424
  /**
   * GtkFileChooserButton:focus-on-click:
425
   *
426 427 428 429 430 431 432 433 434 435 436 437
   * 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));
438

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

  /**
Matthias Clasen's avatar
Matthias Clasen committed
454
   * GtkFileChooserButton:width-chars:
455
   *
Matthias Clasen's avatar
Matthias Clasen committed
456 457 458 459
   * The width of the entry and label inside the button, in characters.
   *
   * Since: 2.6
   */
460 461 462 463 464
  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,
465
						     GTK_PARAM_READWRITE));
466 467 468

  _gtk_file_chooser_install_properties (gobject_class);

469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485
  /* Bind class to template
   */
  gtk_widget_class_set_template_from_resource (widget_class,
					       "/org/gtk/libgtk/gtkfilechooserbutton.ui");

  gtk_widget_class_bind_child (widget_class, GtkFileChooserButtonPrivate, model);
  gtk_widget_class_bind_child (widget_class, GtkFileChooserButtonPrivate, button);
  gtk_widget_class_bind_child (widget_class, GtkFileChooserButtonPrivate, image);
  gtk_widget_class_bind_child (widget_class, GtkFileChooserButtonPrivate, label);
  gtk_widget_class_bind_child (widget_class, GtkFileChooserButtonPrivate, combo_box);
  gtk_widget_class_bind_child (widget_class, GtkFileChooserButtonPrivate, icon_cell);
  gtk_widget_class_bind_child (widget_class, GtkFileChooserButtonPrivate, name_cell);

  gtk_widget_class_bind_callback (widget_class, button_clicked_cb);
  gtk_widget_class_bind_callback (widget_class, combo_box_changed_cb);
  gtk_widget_class_bind_callback (widget_class, combo_box_notify_popup_shown_cb);

486 487 488 489 490 491 492
  g_type_class_add_private (class, sizeof (GtkFileChooserButtonPrivate));
}

static void
gtk_file_chooser_button_init (GtkFileChooserButton *button)
{
  GtkFileChooserButtonPrivate *priv;
493
  GtkTargetList *target_list;
494

495 496 497
  priv = button->priv = G_TYPE_INSTANCE_GET_PRIVATE (button,
                                                     GTK_TYPE_FILE_CHOOSER_BUTTON,
                                                     GtkFileChooserButtonPrivate);
498

499
  priv->icon_size = FALLBACK_ICON_SIZE;
500
  priv->focus_on_click = TRUE;
501

502 503
  gtk_widget_init_template (GTK_WIDGET (button));

504 505
  /* Bookmarks manager */
  priv->bookmarks_manager = _gtk_bookmarks_manager_new (bookmarks_changed_cb, button);
506 507 508 509
  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (priv->combo_box),
				      priv->name_cell, name_cell_data_func,
				      NULL, NULL);

510 511
  /* DnD */
  gtk_drag_dest_set (GTK_WIDGET (button),
512
                     (GTK_DEST_DEFAULT_ALL),
Matthias Clasen's avatar
Matthias Clasen committed
513
		     NULL, 0,
514
		     GDK_ACTION_COPY);
515 516 517 518 519
  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);
520 521 522
}


523 524 525 526 527 528 529 530
/* ******************************* *
 *  GtkFileChooserIface Functions  *
 * ******************************* */
static void
gtk_file_chooser_button_file_chooser_iface_init (GtkFileChooserIface *iface)
{
  _gtk_file_chooser_delegate_iface_init (iface);

531 532
  iface->set_current_folder = gtk_file_chooser_button_set_current_folder;
  iface->get_current_folder = gtk_file_chooser_button_get_current_folder;
533 534 535 536
  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;
537 538 539 540
  iface->add_shortcut_folder = gtk_file_chooser_button_add_shortcut_folder;
  iface->remove_shortcut_folder = gtk_file_chooser_button_remove_shortcut_folder;
}

541 542 543 544 545 546 547 548 549 550 551 552
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");
    }
}

553 554 555 556 557 558 559 560
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;

561 562
  if (priv->current_folder_while_inactive)
    g_object_unref (priv->current_folder_while_inactive);
563

564
  priv->current_folder_while_inactive = g_object_ref (file);
565

566
  update_combo_box (button);
567

568
  g_signal_emit_by_name (button, "current-folder-changed");
569

570 571
  if (priv->active)
    gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (priv->dialog), file, NULL);
572

573
  return TRUE;
574 575 576 577 578 579 580 581
}

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

582 583
  if (priv->current_folder_while_inactive)
    return g_object_ref (priv->current_folder_while_inactive);
584
  else
585
    return NULL;
586 587
}

588 589 590 591 592 593 594 595
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;

596 597
  if (priv->selection_while_inactive)
    g_object_unref (priv->selection_while_inactive);
598

599 600 601
  priv->selection_while_inactive = g_object_ref (file);

  priv->is_changing_selection = TRUE;
602

603 604
  update_label_and_image (button);
  update_combo_box (button);
605

606 607
  if (priv->active)
    gtk_file_chooser_select_file (GTK_FILE_CHOOSER (priv->dialog), file, NULL);
608

609 610
  return TRUE;
}
611

612 613 614 615 616 617 618 619 620
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;
621
    }
622 623 624 625 626

  priv->is_changing_selection = TRUE;

  update_label_and_image (button);
  update_combo_box (button);
627 628 629 630 631 632 633 634 635
}

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

636 637
  if (g_file_equal (priv->selection_while_inactive, file))
    unselect_current_file (button);
638 639

  if (priv->active)
640
    gtk_file_chooser_unselect_file (GTK_FILE_CHOOSER (priv->dialog), file);
641 642 643 644 645 646 647 648
}

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

649
  unselect_current_file (button);
650 651

  if (priv->active)
652
    gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
653 654
}

655 656
static GFile *
get_selected_file (GtkFileChooserButton *button)
657 658
{
  GtkFileChooserButtonPrivate *priv = button->priv;
659
  GFile *retval;
660

661 662 663 664 665
  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)
666
    {
667 668 669 670 671
      /* If there is no "real" selection in SELECT_FOLDER mode, then we'll just return
       * the current folder, since that is what GtkFileChooserDefault would do.
       */
      if (priv->current_folder_while_inactive)
	retval = priv->current_folder_while_inactive;
672
    }
673

674 675 676 677
  if (retval)
    return g_object_ref (retval);
  else
    return NULL;
678 679 680 681 682 683
}

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

686 687 688 689 690
  file = get_selected_file (button);
  if (file)
    return g_slist_prepend (NULL, file);
  else
    return NULL;
691 692
}

693
static gboolean
694 695 696
gtk_file_chooser_button_add_shortcut_folder (GtkFileChooser  *chooser,
					     GFile           *file,
					     GError         **error)
697 698 699 700 701 702
{
  GtkFileChooser *delegate;
  gboolean retval;

  delegate = g_object_get_qdata (G_OBJECT (chooser),
				 GTK_FILE_CHOOSER_DELEGATE_QUARK);
703
  retval = _gtk_file_chooser_add_shortcut_folder (delegate, file, error);
704 705 706

  if (retval)
    {
707 708
      GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
      GtkFileChooserButtonPrivate *priv = button->priv;
709 710 711
      GtkTreeIter iter;
      gint pos;

712
      pos = model_get_type_position (button, ROW_TYPE_SHORTCUT);
713 714 715 716
      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,
717 718
			  ICON_COLUMN, NULL,
			  DISPLAY_NAME_COLUMN, _(FALLBACK_DISPLAY_NAME),
719
			  TYPE_COLUMN, ROW_TYPE_SHORTCUT,
720
			  DATA_COLUMN, g_object_ref (file),
721
			  IS_FOLDER_COLUMN, FALSE,
722
			  -1);
723
      set_info_for_file_at_iter (button, file, &iter);
724 725 726 727 728 729 730 731 732
      priv->n_shortcuts++;

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

  return retval;
}

static gboolean
733 734 735
gtk_file_chooser_button_remove_shortcut_folder (GtkFileChooser  *chooser,
						GFile           *file,
						GError         **error)
736 737 738 739 740 741 742
{
  GtkFileChooser *delegate;
  gboolean retval;

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

743
  retval = _gtk_file_chooser_remove_shortcut_folder (delegate, file, error);
744 745 746

  if (retval)
    {
747 748
      GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
      GtkFileChooserButtonPrivate *priv = button->priv;
749 750 751 752
      GtkTreeIter iter;
      gint pos;
      gchar type;

753
      pos = model_get_type_position (button, ROW_TYPE_SHORTCUT);
754
      gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, pos);
755 756 757 758 759 760 761 762 763 764 765

      do
	{
	  gpointer data;

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

	  if (type == ROW_TYPE_SHORTCUT &&
766
	      data && g_file_equal (data, file))
767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783
	    {
	      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;
}


784 785 786 787
/* ******************* *
 *  GObject Functions  *
 * ******************* */

788 789 790 791 792 793
static GObject *
gtk_file_chooser_button_constructor (GType                  type,
				     guint                  n_params,
				     GObjectConstructParam *params)
{
  GObject *object;
794
  GtkFileChooserButton *button;
795
  GtkFileChooserButtonPrivate *priv;
796
  GSList *list;
797

798 799 800
  object = G_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->constructor (type,
									       n_params,
									       params);
801 802
  button = GTK_FILE_CHOOSER_BUTTON (object);
  priv = button->priv;
803 804 805

  if (!priv->dialog)
    {
806 807
      priv->dialog = gtk_file_chooser_dialog_new (NULL, NULL,
						  GTK_FILE_CHOOSER_ACTION_OPEN,
808
						  _("_Cancel"),
809
						  GTK_RESPONSE_CANCEL,
810
						  _("_Open"),
811 812
						  GTK_RESPONSE_ACCEPT,
						  NULL);
813 814 815 816 817 818 819 820

      gtk_dialog_set_default_response (GTK_DIALOG (priv->dialog),
				       GTK_RESPONSE_ACCEPT);
      gtk_dialog_set_alternative_button_order (GTK_DIALOG (priv->dialog),
					       GTK_RESPONSE_ACCEPT,
					       GTK_RESPONSE_CANCEL,
					       -1);

821 822
      gtk_file_chooser_button_set_title (button, _(DEFAULT_TITLE));
    }
823
  else if (!gtk_window_get_title (GTK_WINDOW (priv->dialog)))
824 825 826
    {
      gtk_file_chooser_button_set_title (button, _(DEFAULT_TITLE));
    }
827

828
  g_signal_connect (priv->dialog, "delete-event",
829 830 831 832 833 834 835
		    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);
836

837 838 839 840 841
  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),
842
			     (gpointer) (&priv->dialog));
843

844 845 846
  priv->fs =
    g_object_ref (_gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog)));

847
  model_add_special (button);
848

849
  list = _gtk_file_system_list_volumes (priv->fs);
850
  model_add_volumes (button, list);
851 852
  g_slist_free (list);

853
  list = _gtk_bookmarks_manager_list_bookmarks (priv->bookmarks_manager);
854
  model_add_bookmarks (button, list);
855 856
  g_slist_foreach (list, (GFunc) g_object_unref, NULL);
  g_slist_free (list);
857

858
  model_add_other (button);
859

860 861
  model_add_empty_selection (button);

862 863 864 865 866 867 868 869 870 871
  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);

872 873 874
  /* set up the action for a user-provided dialog, this also updates
   * the label, image and combobox
   */
875
  g_object_set (object,
876 877
		"action", gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog)),
		NULL);
878 879 880 881 882

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

883 884 885
  update_label_and_image (button);
  update_combo_box (button);

886 887
  return object;
}
888 889 890

static void
gtk_file_chooser_button_set_property (GObject      *object,
891
				      guint         param_id,
892 893 894
				      const GValue *value,
				      GParamSpec   *pspec)
{
895 896
  GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (object);
  GtkFileChooserButtonPrivate *priv = button->priv;
897

898
  switch (param_id)
899 900
    {
    case PROP_DIALOG:
901 902
      /* Construct-only */
      priv->dialog = g_value_get_object (value);
903
      break;
904 905 906
    case PROP_FOCUS_ON_CLICK:
      gtk_file_chooser_button_set_focus_on_click (button, g_value_get_boolean (value));
      break;
907 908 909 910
    case PROP_WIDTH_CHARS:
      gtk_file_chooser_button_set_width_chars (GTK_FILE_CHOOSER_BUTTON (object),
					       g_value_get_int (value));
      break;
911
    case GTK_FILE_CHOOSER_PROP_ACTION:
912 913 914 915 916 917 918 919 920 921
      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));
922
	    g_warning ("%s: Choosers of type `%s' do not support `%s'.",
923 924
		       G_STRFUNC, G_OBJECT_TYPE_NAME (object), eval->value_name);

925
	    g_value_set_enum ((GValue *) value, GTK_FILE_CHOOSER_ACTION_OPEN);
926 927 928
	  }
	  break;
	}
929

930 931
      g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value);
      update_label_and_image (GTK_FILE_CHOOSER_BUTTON (object));
932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947
      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;
	}
948 949 950 951 952 953 954 955 956
      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:
957
    case GTK_FILE_CHOOSER_PROP_DO_OVERWRITE_CONFIRMATION:
958
    case GTK_FILE_CHOOSER_PROP_CREATE_FOLDERS:
959 960 961
      g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value);
      break;

962 963 964
    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);
965
      bookmarks_changed_cb (button);
966 967
      break;

968 969 970 971 972
    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:
973
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
974 975 976 977 978 979
      break;
    }
}

static void
gtk_file_chooser_button_get_property (GObject    *object,
980
				      guint       param_id,
981 982 983
				      GValue     *value,
				      GParamSpec *pspec)
{
984 985
  GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (object);
  GtkFileChooserButtonPrivate *priv = button->priv;
986 987

  switch (param_id)
988
    {
989 990
    case PROP_WIDTH_CHARS:
      g_value_set_int (value,
991
		       gtk_label_get_width_chars (GTK_LABEL (priv->label)));
992
      break;
993 994 995 996
    case PROP_FOCUS_ON_CLICK:
      g_value_set_boolean (value,
                           gtk_file_chooser_button_get_focus_on_click (button));
      break;
997 998 999 1000 1001 1002 1003 1004 1005 1006 1007

    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:
1008
    case GTK_FILE_CHOOSER_PROP_DO_OVERWRITE_CONFIRMATION:
1009
    case GTK_FILE_CHOOSER_PROP_CREATE_FOLDERS:
1010 1011 1012 1013
      g_object_get_property (G_OBJECT (priv->dialog), pspec->name, value);
      break;

    default:
1014
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1015 1016 1017 1018
      break;
    }
}

1019 1020 1021
static void
gtk_file_chooser_button_finalize (GObject *object)
{
1022 1023
  GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (object);
  GtkFileChooserButtonPrivate *priv = button->priv;
1024

1025 1026
  if (priv->selection_while_inactive)
    g_object_unref (priv->selection_while_inactive);
1027

1028 1029 1030
  if (priv->current_folder_while_inactive)
    g_object_unref (priv->current_folder_while_inactive);

1031
  G_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->finalize (object);
1032
}
1033 1034

/* ********************* *
1035
 *  GtkWidget Functions  *
1036 1037 1038
 * ********************* */

static void
1039
gtk_file_chooser_button_destroy (GtkWidget *widget)
1040
{
1041
  GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (widget);
1042
  GtkFileChooserButtonPrivate *priv = button->priv;
1043 1044
  GtkTreeIter iter;
  GSList *l;
1045 1046

  if (priv->dialog != NULL)
1047 1048 1049 1050 1051
    {
      gtk_widget_destroy (priv->dialog);
      priv->dialog = NULL;
    }

1052
  if (priv->model && gtk_tree_model_get_iter_first (priv->model, &iter)) do
1053 1054 1055 1056 1057
    {
      model_free_row_data (button, &iter);
    }
  while (gtk_tree_model_iter_next (priv->model, &iter));

1058
  if (priv->dnd_select_folder_cancellable)
1059
    {
1060 1061
      g_cancellable_cancel (priv->dnd_select_folder_cancellable);
      priv->dnd_select_folder_cancellable = NULL;
1062 1063
    }

1064
  if (priv->update_button_cancellable)
1065
    {
1066 1067
      g_cancellable_cancel (priv->update_button_cancellable);
      priv->update_button_cancellable = NULL;
1068 1069
    }

1070
  if (priv->change_icon_theme_cancellables)
1071
    {
1072
      for (l = priv->change_icon_theme_cancellables; l; l = l->next)
1073
        {
1074 1075
	  GCancellable *cancellable = G_CANCELLABLE (l->data);
	  g_cancellable_cancel (cancellable);
1076
        }
1077 1078
      g_slist_free (priv->change_icon_theme_cancellables);
      priv->change_icon_theme_cancellables = NULL;
1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092
    }

  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;
    }
1093

1094 1095 1096 1097 1098 1099
  if (priv->bookmarks_manager)
    {
      _gtk_bookmarks_manager_free (priv->bookmarks_manager);
      priv->bookmarks_manager = NULL;
    }

1100
  GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->destroy (widget);
1101 1102
}

1103 1104
struct DndSelectFolderData
{
1105
  GtkFileSystem *file_system;
1106 1107
  GtkFileChooserButton *button;
  GtkFileChooserAction action;
1108
  GFile *file;
1109 1110 1111 1112 1113 1114
  gchar **uris;
  guint i;
  gboolean selected;
};

static void
1115 1116 1117 1118
dnd_select_folder_get_info_cb (GCancellable *cancellable,
			       GFileInfo    *info,
			       const GError *error,
			       gpointer      user_data)
1119
{
1120
  gboolean cancelled = g_cancellable_is_cancelled (cancellable);
1121 1122
  struct DndSelectFolderData *data = user_data;

1123
  if (cancellable != data->button->priv->dnd_select_folder_cancellable)
1124 1125
    {
      g_object_unref (data->button);
1126
      g_object_unref (data->file);
1127 1128 1129
      g_strfreev (data->uris);
      g_free (data);

1130
      g_object_unref (cancellable);
1131 1132 1133
      return;
    }

1134
  data->button->priv->dnd_select_folder_cancellable = NULL;
1135 1136 1137

  if (!cancelled && !error && info != NULL)
    {
1138 1139
      gboolean is_folder;

1140
      is_folder = _gtk_file_info_consider_as_directory (info);
1141 1142 1143 1144

      data->selected =
	(((data->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER && is_folder) ||
	  (data->action == GTK_FILE_CHOOSER_ACTION_OPEN && !is_folder)) &&
1145 1146
	 gtk_file_chooser_select_file (GTK_FILE_CHOOSER (data->button->priv->dialog),
				       data->file, NULL));
1147 1148 1149 1150 1151 1152
    }
  else
    data->selected = FALSE;

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

1155
      g_object_unref (data->button);
1156
      g_object_unref (data->file);
1157 1158 1159
      g_strfreev (data->uris);
      g_free (data);

1160
      g_object_unref (cancellable);
1161 1162 1163
      return;
    }

1164 1165
  if (data->file)
    g_object_unref (data->file);