gtkfilechooserbutton.c 81.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 2 -*- */

/* GTK+: gtkfilechooserbutton.c
 * 
 * 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 Jardón's avatar
Javier Jardón 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"
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 92 93 94 95 96 97 98 99

/**
 * 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,
 * and thus 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.
 * </important>
 */


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

104
#define DEFAULT_TITLE		N_("Select a File")
105 106
#define DESKTOP_DISPLAY_NAME	N_("Desktop")
#define FALLBACK_DISPLAY_NAME	N_("(None)")
107
#define FALLBACK_ICON_NAME	"stock_unknown"
108
#define FALLBACK_ICON_SIZE	16
109

110 111 112 113 114 115 116 117 118 119 120

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

/* Property IDs */
enum
{
  PROP_0,

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

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

133 134 135 136 137 138 139
/* TreeModel Columns */
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 157 158 159 160 161
  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,

  ROW_TYPE_INVALID = -1
}
RowType;

162 163 164 165 166 167 168 169 170

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

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

  GtkTreeModel *model;
  GtkTreeModel *filter_model;
179

180
  GtkFileSystem *fs;
181
  GFile *old_file;
182 183

  gulong combo_box_changed_id;
184 185 186
  gulong dialog_file_activated_id;
  gulong dialog_folder_changed_id;
  gulong dialog_selection_changed_id;
187 188 189
  gulong fs_volumes_changed_id;
  gulong fs_bookmarks_changed_id;

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

194
  gint icon_size;
195

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

205
  /* Used for hiding/showing the dialog when the button is hidden */
206
  guint  active                       : 1;
207 208

  /* Used to track whether we need to set a default current folder on ::map() */
209
  guint  folder_has_been_set          : 1;
210

211
  guint  focus_on_click               : 1;
212 213 214 215 216 217 218 219 220
};


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

enum
{
221 222
  TEXT_PLAIN,
  TEXT_URI_LIST
223 224
};

225

226 227 228 229
/* ********************* *
 *  Function Prototypes  *
 * ********************* */

230 231 232
/* GtkFileChooserIface Functions */
static void     gtk_file_chooser_button_file_chooser_iface_init (GtkFileChooserIface *iface);
static gboolean gtk_file_chooser_button_add_shortcut_folder     (GtkFileChooser      *chooser,
233
								 GFile               *file,
234 235
								 GError             **error);
static gboolean gtk_file_chooser_button_remove_shortcut_folder  (GtkFileChooser      *chooser,
236
								 GFile               *file,
237 238
								 GError             **error);

239
/* GObject Functions */
240 241 242
static GObject *gtk_file_chooser_button_constructor        (GType             type,
							    guint             n_params,
							    GObjectConstructParam *params);
243
static void     gtk_file_chooser_button_set_property       (GObject          *object,
244
							    guint             param_id,
245 246 247
							    const GValue     *value,
							    GParamSpec       *pspec);
static void     gtk_file_chooser_button_get_property       (GObject          *object,
248
							    guint             param_id,
249 250
							    GValue           *value,
							    GParamSpec       *pspec);
251
static void     gtk_file_chooser_button_finalize           (GObject          *object);
252 253

/* GtkWidget Functions */
254
static void     gtk_file_chooser_button_destroy            (GtkWidget        *widget);
255 256 257 258 259
static void     gtk_file_chooser_button_drag_data_received (GtkWidget        *widget,
							    GdkDragContext   *context,
							    gint              x,
							    gint              y,
							    GtkSelectionData *data,
260
							    guint             type,
261 262
							    guint             drag_time);
static void     gtk_file_chooser_button_show_all           (GtkWidget        *widget);
263 264
static void     gtk_file_chooser_button_show               (GtkWidget        *widget);
static void     gtk_file_chooser_button_hide               (GtkWidget        *widget);
265
static void     gtk_file_chooser_button_map                (GtkWidget        *widget);
Matthias Clasen's avatar
Matthias Clasen committed
266 267
static gboolean gtk_file_chooser_button_mnemonic_activate  (GtkWidget        *widget,
							    gboolean          group_cycling);
268
static void     gtk_file_chooser_button_style_updated      (GtkWidget        *widget);
269 270
static void     gtk_file_chooser_button_screen_changed     (GtkWidget        *widget,
							    GdkScreen        *old_screen);
271 272

/* Utility Functions */
273
static GtkIconTheme *get_icon_theme               (GtkWidget            *widget);
274 275
static void          set_info_for_file_at_iter         (GtkFileChooserButton *fs,
							GFile                *file,
276
							GtkTreeIter          *iter);
277 278 279 280 281 282 283 284 285 286 287 288

static gint          model_get_type_position      (GtkFileChooserButton *button,
						   RowType               row_type);
static void          model_free_row_data          (GtkFileChooserButton *button,
						   GtkTreeIter          *iter);
static inline void   model_add_special            (GtkFileChooserButton *button);
static inline void   model_add_other              (GtkFileChooserButton *button);
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,
289
						   GFile                *file);
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
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);
static void     fs_bookmarks_changed_cb          (GtkFileSystem  *fs,
						  gpointer        user_data);

static void     combo_box_changed_cb             (GtkComboBox    *combo_box,
						  gpointer        user_data);

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_selection_changed_cb      (GtkFileChooser *dialog,
						  gpointer        user_data);
static void     dialog_file_activated_cb         (GtkFileChooser *dialog,
						  gpointer        user_data);
static void     dialog_current_folder_changed_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);
339

340
static guint file_chooser_button_signals[LAST_SIGNAL] = { 0 };
341 342 343 344 345

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

346
G_DEFINE_TYPE_WITH_CODE (GtkFileChooserButton, gtk_file_chooser_button, GTK_TYPE_BOX, { \
347
    G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_CHOOSER, gtk_file_chooser_button_file_chooser_iface_init) \
Matthias Clasen's avatar
Matthias Clasen committed
348
})
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363


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

364
  gobject_class->constructor = gtk_file_chooser_button_constructor;
365 366
  gobject_class->set_property = gtk_file_chooser_button_set_property;
  gobject_class->get_property = gtk_file_chooser_button_get_property;
367
  gobject_class->finalize = gtk_file_chooser_button_finalize;
368

369
  widget_class->destroy = gtk_file_chooser_button_destroy;
370 371
  widget_class->drag_data_received = gtk_file_chooser_button_drag_data_received;
  widget_class->show_all = gtk_file_chooser_button_show_all;
372 373
  widget_class->show = gtk_file_chooser_button_show;
  widget_class->hide = gtk_file_chooser_button_hide;
374
  widget_class->map = gtk_file_chooser_button_map;
375
  widget_class->style_updated = gtk_file_chooser_button_style_updated;
376
  widget_class->screen_changed = gtk_file_chooser_button_screen_changed;
Matthias Clasen's avatar
Matthias Clasen committed
377
  widget_class->mnemonic_activate = gtk_file_chooser_button_mnemonic_activate;
378

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

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

414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
  /**
   * GtkFileChooserButton:focus-on-click:
   * 
   * 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));
  
Matthias Clasen's avatar
Matthias Clasen committed
430 431 432 433 434 435 436
  /**
   * GtkFileChooserButton:title:
   * 
   * Title to put on the #GtkFileChooserDialog associated with the button.
   *
   * Since: 2.6
   */
437 438 439 440
  g_object_class_install_property (gobject_class, PROP_TITLE,
				   g_param_spec_string ("title",
							P_("Title"),
							P_("The title of the file chooser dialog."),
441
							_(DEFAULT_TITLE),
442
							GTK_PARAM_READWRITE));
Matthias Clasen's avatar
Matthias Clasen committed
443 444

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

  _gtk_file_chooser_install_properties (gobject_class);

  g_type_class_add_private (class, sizeof (GtkFileChooserButtonPrivate));
}

static void
gtk_file_chooser_button_init (GtkFileChooserButton *button)
{
  GtkFileChooserButtonPrivate *priv;
467
  GtkWidget *box, *image, *sep;
468
  GtkTargetList *target_list;
469

470 471 472
  priv = button->priv = G_TYPE_INSTANCE_GET_PRIVATE (button,
                                                     GTK_TYPE_FILE_CHOOSER_BUTTON,
                                                     GtkFileChooserButtonPrivate);
473

474
  priv->icon_size = FALLBACK_ICON_SIZE;
475
  priv->focus_on_click = TRUE;
476

477 478
  gtk_widget_push_composite_child ();

479
  /* Button */
480
  priv->button = gtk_button_new ();
481 482 483 484
  g_signal_connect (priv->button, "clicked",
                    G_CALLBACK (button_clicked_cb), button);
  gtk_box_pack_start (GTK_BOX (button), priv->button, TRUE, TRUE, 0);
  gtk_widget_set_halign (priv->button, GTK_ALIGN_FILL);
485 486
  gtk_widget_show (priv->button);

487
  box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
488 489
  gtk_container_add (GTK_CONTAINER (priv->button), box);
  gtk_widget_show (box);
490

491 492 493
  priv->image = gtk_image_new ();
  gtk_box_pack_start (GTK_BOX (box), priv->image, FALSE, FALSE, 0);
  gtk_widget_show (priv->image);
494

495 496
  priv->label = gtk_label_new (_(FALLBACK_DISPLAY_NAME));
  gtk_label_set_ellipsize (GTK_LABEL (priv->label), PANGO_ELLIPSIZE_END);
497 498
  gtk_widget_set_halign (priv->label, GTK_ALIGN_START);
  gtk_widget_set_valign (priv->label, GTK_ALIGN_CENTER);
499 500
  gtk_box_pack_start (GTK_BOX (box), priv->label, TRUE, TRUE, 0);
  //gtk_container_add (GTK_CONTAINER (box), priv->label);
501 502
  gtk_widget_show (priv->label);

503
  sep = gtk_separator_new (GTK_ORIENTATION_VERTICAL);
504
  gtk_box_pack_start (GTK_BOX (box), sep, FALSE, FALSE, 0);
505 506
  gtk_widget_show (sep);

507
  image = gtk_image_new_from_stock (GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU);
508
  gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 0);
509 510
  gtk_widget_show (image);

511 512 513 514 515 516 517
  /* Combo Box */
  /* Keep in sync with columns enum, line 88 */
  priv->model =
    GTK_TREE_MODEL (gtk_list_store_new (NUM_COLUMNS,
					GDK_TYPE_PIXBUF, /* Icon */
					G_TYPE_STRING,	 /* Display Name */
					G_TYPE_CHAR,	 /* Row Type */
518 519
					G_TYPE_POINTER	 /* Volume || Path */,
					G_TYPE_BOOLEAN   /* Is Folder? */,
520
					G_TYPE_POINTER	 /* cancellable */));
521 522 523 524 525

  priv->combo_box = gtk_combo_box_new ();
  priv->combo_box_changed_id =
    g_signal_connect (priv->combo_box, "changed",
		      G_CALLBACK (combo_box_changed_cb), button);
526 527
  gtk_box_pack_start (GTK_BOX (button), priv->combo_box, TRUE, TRUE, 0);
  gtk_widget_set_halign (priv->combo_box, GTK_ALIGN_FILL);
528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543

  priv->icon_cell = gtk_cell_renderer_pixbuf_new ();
  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->combo_box),
			      priv->icon_cell, FALSE);
  gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (priv->combo_box),
				 priv->icon_cell, "pixbuf", ICON_COLUMN);

  priv->name_cell = gtk_cell_renderer_text_new ();
  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->combo_box),
			      priv->name_cell, TRUE);
  gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (priv->combo_box),
				 priv->name_cell, "text", DISPLAY_NAME_COLUMN);
  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (priv->combo_box),
				      priv->name_cell, name_cell_data_func,
				      NULL, NULL);

544 545 546 547
  gtk_widget_pop_composite_child ();

  /* DnD */
  gtk_drag_dest_set (GTK_WIDGET (button),
548
                     (GTK_DEST_DEFAULT_ALL),
549
		     NULL, 0,
550
		     GDK_ACTION_COPY);
551 552 553 554 555
  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);
556 557 558
}


559 560 561 562 563 564 565 566 567 568 569 570 571
/* ******************************* *
 *  GtkFileChooserIface Functions  *
 * ******************************* */
static void
gtk_file_chooser_button_file_chooser_iface_init (GtkFileChooserIface *iface)
{
  _gtk_file_chooser_delegate_iface_init (iface);

  iface->add_shortcut_folder = gtk_file_chooser_button_add_shortcut_folder;
  iface->remove_shortcut_folder = gtk_file_chooser_button_remove_shortcut_folder;
}

static gboolean
572 573 574
gtk_file_chooser_button_add_shortcut_folder (GtkFileChooser  *chooser,
					     GFile           *file,
					     GError         **error)
575 576 577 578 579 580
{
  GtkFileChooser *delegate;
  gboolean retval;

  delegate = g_object_get_qdata (G_OBJECT (chooser),
				 GTK_FILE_CHOOSER_DELEGATE_QUARK);
581
  retval = _gtk_file_chooser_add_shortcut_folder (delegate, file, error);
582 583 584

  if (retval)
    {
585 586
      GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
      GtkFileChooserButtonPrivate *priv = button->priv;
587 588 589
      GtkTreeIter iter;
      gint pos;

590
      pos = model_get_type_position (button, ROW_TYPE_SHORTCUT);
591 592 593 594
      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,
595 596
			  ICON_COLUMN, NULL,
			  DISPLAY_NAME_COLUMN, _(FALLBACK_DISPLAY_NAME),
597
			  TYPE_COLUMN, ROW_TYPE_SHORTCUT,
598
			  DATA_COLUMN, g_object_ref (file),
599
			  IS_FOLDER_COLUMN, FALSE,
600
			  -1);
601
      set_info_for_file_at_iter (button, file, &iter);
602 603 604 605 606 607 608 609 610
      priv->n_shortcuts++;

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

  return retval;
}

static gboolean
611 612 613
gtk_file_chooser_button_remove_shortcut_folder (GtkFileChooser  *chooser,
						GFile           *file,
						GError         **error)
614 615 616 617 618 619 620
{
  GtkFileChooser *delegate;
  gboolean retval;

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

621
  retval = _gtk_file_chooser_remove_shortcut_folder (delegate, file, error);
622 623 624

  if (retval)
    {
625 626
      GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
      GtkFileChooserButtonPrivate *priv = button->priv;
627 628 629 630
      GtkTreeIter iter;
      gint pos;
      gchar type;

631
      pos = model_get_type_position (button, ROW_TYPE_SHORTCUT);
632
      gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, pos);
633 634 635 636 637 638 639 640 641 642 643

      do
	{
	  gpointer data;

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

	  if (type == ROW_TYPE_SHORTCUT &&
644
	      data && g_file_equal (data, file))
645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661
	    {
	      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;
}


662 663 664 665
/* ******************* *
 *  GObject Functions  *
 * ******************* */

666 667 668 669 670 671
static GObject *
gtk_file_chooser_button_constructor (GType                  type,
				     guint                  n_params,
				     GObjectConstructParam *params)
{
  GObject *object;
672
  GtkFileChooserButton *button;
673
  GtkFileChooserButtonPrivate *priv;
674
  GSList *list;
675
  char *current_folder;
676

677 678 679
  object = G_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->constructor (type,
									       n_params,
									       params);
680 681
  button = GTK_FILE_CHOOSER_BUTTON (object);
  priv = button->priv;
682 683 684

  if (!priv->dialog)
    {
685 686 687 688 689 690 691
      priv->dialog = gtk_file_chooser_dialog_new (NULL, NULL,
						  GTK_FILE_CHOOSER_ACTION_OPEN,
						  GTK_STOCK_CANCEL,
						  GTK_RESPONSE_CANCEL,
						  GTK_STOCK_OPEN,
						  GTK_RESPONSE_ACCEPT,
						  NULL);
692 693 694 695 696 697 698 699

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

700 701
      gtk_file_chooser_button_set_title (button, _(DEFAULT_TITLE));
    }
702
  else if (!gtk_window_get_title (GTK_WINDOW (priv->dialog)))
703 704 705
    {
      gtk_file_chooser_button_set_title (button, _(DEFAULT_TITLE));
    }
706

707 708 709 710 711 712 713
  current_folder = gtk_file_chooser_get_current_folder_uri (GTK_FILE_CHOOSER (priv->dialog));
  if (current_folder != NULL)
    {
      priv->folder_has_been_set = TRUE;
      g_free (current_folder);
    }

714
  g_signal_connect (priv->dialog, "delete-event",
715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735
		    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);
  priv->dialog_folder_changed_id =
    g_signal_connect (priv->dialog, "current-folder-changed",
		      G_CALLBACK (dialog_current_folder_changed_cb), object);
  priv->dialog_file_activated_id =
    g_signal_connect (priv->dialog, "file-activated",
		      G_CALLBACK (dialog_file_activated_cb), object);
  priv->dialog_selection_changed_id =
    g_signal_connect (priv->dialog, "selection-changed",
		      G_CALLBACK (dialog_selection_changed_cb), object);
  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),
736
			     (gpointer) (&priv->dialog));
737

738 739 740
  priv->fs =
    g_object_ref (_gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog)));

741
  model_add_special (button);
742

743
  list = _gtk_file_system_list_volumes (priv->fs);
744
  model_add_volumes (button, list);
745 746
  g_slist_free (list);

747
  list = _gtk_file_system_list_bookmarks (priv->fs);
748
  model_add_bookmarks (button, list);
749 750
  g_slist_foreach (list, (GFunc) g_object_unref, NULL);
  g_slist_free (list);
751

752
  model_add_other (button);
753 754 755 756 757 758 759 760 761 762 763

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

764 765 766 767 768 769
  /* set up the action for a user-provided dialog, this also updates
   * the label, image and combobox
   */
  g_object_set (object, 
		"action", gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog)),
		NULL);
770 771 772 773 774 775 776 777

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

778 779
  return object;
}
780 781 782

static void
gtk_file_chooser_button_set_property (GObject      *object,
783
				      guint         param_id,
784 785 786
				      const GValue *value,
				      GParamSpec   *pspec)
{
787 788
  GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (object);
  GtkFileChooserButtonPrivate *priv = button->priv;
789

790
  switch (param_id)
791 792
    {
    case PROP_DIALOG:
793 794
      /* Construct-only */
      priv->dialog = g_value_get_object (value);
795
      break;
796 797 798
    case PROP_FOCUS_ON_CLICK:
      gtk_file_chooser_button_set_focus_on_click (button, g_value_get_boolean (value));
      break;
799 800 801 802
    case PROP_WIDTH_CHARS:
      gtk_file_chooser_button_set_width_chars (GTK_FILE_CHOOSER_BUTTON (object),
					       g_value_get_int (value));
      break;
803
    case GTK_FILE_CHOOSER_PROP_ACTION:
804 805 806 807 808 809 810 811 812 813
      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));
814
	    g_warning ("%s: Choosers of type `%s' do not support `%s'.",
815 816
		       G_STRFUNC, G_OBJECT_TYPE_NAME (object), eval->value_name);

817
	    g_value_set_enum ((GValue *) value, GTK_FILE_CHOOSER_ACTION_OPEN);
818 819 820
	  }
	  break;
	}
821

822 823
      g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value);
      update_label_and_image (GTK_FILE_CHOOSER_BUTTON (object));
824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839
      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;
	}
840 841 842 843 844 845 846 847 848
      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:
849
    case GTK_FILE_CHOOSER_PROP_DO_OVERWRITE_CONFIRMATION:
850
    case GTK_FILE_CHOOSER_PROP_CREATE_FOLDERS:
851 852 853
      g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value);
      break;

854 855 856 857 858 859
    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);
      fs_bookmarks_changed_cb (priv->fs, button);
      break;

860 861 862 863 864
    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:
865
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
866 867 868 869 870 871
      break;
    }
}

static void
gtk_file_chooser_button_get_property (GObject    *object,
872
				      guint       param_id,
873 874 875
				      GValue     *value,
				      GParamSpec *pspec)
{
876 877
  GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (object);
  GtkFileChooserButtonPrivate *priv = button->priv;
878 879

  switch (param_id)
880
    {
881 882
    case PROP_WIDTH_CHARS:
      g_value_set_int (value,
883
		       gtk_label_get_width_chars (GTK_LABEL (priv->label)));
884
      break;
885 886 887 888
    case PROP_FOCUS_ON_CLICK:
      g_value_set_boolean (value,
                           gtk_file_chooser_button_get_focus_on_click (button));
      break;
889 890 891 892 893 894 895 896 897 898 899

    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:
900
    case GTK_FILE_CHOOSER_PROP_DO_OVERWRITE_CONFIRMATION:
901
    case GTK_FILE_CHOOSER_PROP_CREATE_FOLDERS:
902 903 904 905
      g_object_get_property (G_OBJECT (priv->dialog), pspec->name, value);
      break;

    default:
906
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
907 908 909 910
      break;
    }
}

911 912 913
static void
gtk_file_chooser_button_finalize (GObject *object)
{
914 915
  GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (object);
  GtkFileChooserButtonPrivate *priv = button->priv;
916

917 918
  if (priv->old_file)
    g_object_unref (priv->old_file);
919

920
  G_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->finalize (object);
921
}
922 923

/* ********************* *
924
 *  GtkWidget Functions  *
925 926 927
 * ********************* */

static void
928
gtk_file_chooser_button_destroy (GtkWidget *widget)
929
{
930
  GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (widget);
931
  GtkFileChooserButtonPrivate *priv = button->priv;
932 933
  GtkTreeIter iter;
  GSList *l;
934 935

  if (priv->dialog != NULL)
936 937 938 939 940
    {
      gtk_widget_destroy (priv->dialog);
      priv->dialog = NULL;
    }

941
  if (priv->model && gtk_tree_model_get_iter_first (priv->model, &iter)) do
942 943 944 945 946
    {
      model_free_row_data (button, &iter);
    }
  while (gtk_tree_model_iter_next (priv->model, &iter));

947
  if (priv->dnd_select_folder_cancellable)
948
    {
949 950
      g_cancellable_cancel (priv->dnd_select_folder_cancellable);
      priv->dnd_select_folder_cancellable = NULL;
951 952
    }

953
  if (priv->update_button_cancellable)
954
    {
955 956
      g_cancellable_cancel (priv->update_button_cancellable);
      priv->update_button_cancellable = NULL;
957 958
    }

959
  if (priv->change_icon_theme_cancellables)
960
    {
961
      for (l = priv->change_icon_theme_cancellables; l; l = l->next)
962
        {
963 964
	  GCancellable *cancellable = G_CANCELLABLE (l->data);
	  g_cancellable_cancel (cancellable);
965
        }
966 967
      g_slist_free (priv->change_icon_theme_cancellables);
      priv->change_icon_theme_cancellables = NULL;
968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988
    }

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

  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_signal_handler_disconnect (priv->fs, priv->fs_bookmarks_changed_id);
      g_object_unref (priv->fs);
      priv->fs = NULL;
    }
989

990
  GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->destroy (widget);
991 992
}

993 994
struct DndSelectFolderData
{
995
  GtkFileSystem *file_system;
996 997
  GtkFileChooserButton *button;
  GtkFileChooserAction action;
998
  GFile *file;
999 1000 1001 1002 1003 1004
  gchar **uris;
  guint i;
  gboolean selected;
};

static void
1005 1006 1007 1008
dnd_select_folder_get_info_cb (GCancellable *cancellable,
			       GFileInfo    *info,
			       const GError *error,
			       gpointer      user_data)
1009
{
1010
  gboolean cancelled = g_cancellable_is_cancelled (cancellable);
1011 1012
  struct DndSelectFolderData *data = user_data;

1013
  if (cancellable != data->button->priv->dnd_select_folder_cancellable)
1014 1015
    {
      g_object_unref (data->button);
1016
      g_object_unref (data->file);
1017 1018 1019
      g_strfreev (data->uris);
      g_free (data);

1020
      g_object_unref (cancellable);
1021 1022 1023
      return;
    }

1024
  data->button->priv->dnd_select_folder_cancellable = NULL;
1025 1026 1027

  if (!cancelled && !error && info != NULL)
    {
1028 1029
      gboolean is_folder;

1030
      is_folder = _gtk_file_info_consider_as_directory (info);
1031 1032 1033 1034

      data->selected =
	(((data->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER && is_folder) ||
	  (data->action == GTK_FILE_CHOOSER_ACTION_OPEN && !is_folder)) &&
1035 1036
	 gtk_file_chooser_select_file (GTK_FILE_CHOOSER (data->button->priv->dialog),
				       data->file, NULL));
1037 1038 1039 1040 1041 1042
    }
  else
    data->selected = FALSE;

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

1045
      g_object_unref (data->button);
1046
      g_object_unref (data->file);
1047 1048 1049
      g_strfreev (data->uris);
      g_free (data);

1050
      g_object_unref (cancellable);
1051 1052 1053
      return;
    }

1054 1055
  if (data->file)
    g_object_unref (data->file);
1056

1057
  data->file = g_file_new_for_uri (data->uris[data->i]);
1058

1059
  data->button->priv->dnd_select_folder_cancellable =
1060 1061 1062
    _gtk_file_system_get_info (data->file_system, data->file,
			       "standard::type",
			       dnd_select_folder_get_info_cb, user_data);
1063

1064
  g_object_unref (cancellable);
1065 1066
}

1067 1068 1069 1070 1071 1072
static void
gtk_file_chooser_button_drag_data_received (GtkWidget	     *widget,
					    GdkDragContext   *context,
					    gint	      x,
					    gint	      y,
					    GtkSelectionData *data,
1073
					    guint	      type,
1074 1075
					    guint	      drag_time)
{
1076 1077
  GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (widget);
  GtkFileChooserButtonPrivate *priv = button->priv;
1078
  GFile *file;
1079
  gchar *text;
1080 1081

  if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->drag_data_received != NULL)
1082 1083 1084 1085 1086
    GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->drag_data_received (widget,
										 context,
										 x, y,
										 data, type,
										 drag_time);
1087

1088
  if (widget == NULL || context == NULL || data == NULL || gtk_selection_data_get_length (data) < 0)
1089 1090
    return;

1091
  switch (type)
1092 1093 1094 1095
    {
    case TEXT_URI_LIST:
      {
	gchar **uris;
1096
	struct DndSelectFolderData *info;
1097

1098
	uris = gtk_selection_data_get_uris (data);
1099
	
1100 1101 1102
	if (uris == NULL)
	  break;

1103 1104 1105 1106 1107
	info = g_new0 (struct DndSelectFolderData, 1);
	info->button = g_object_ref (button);
	info->i = 0;
	info->uris = uris;
	info->selected = FALSE;
1108
	info->file_system = priv->fs;
1109
	g_object_get (priv->dialog, "action", &info->action, NULL);
1110

1111
	info->file = g_file_new_for_uri (info->uris[info->i]);
1112

1113 1114
	if (priv->dnd_select_folder_cancellable)
	  g_cancellable_cancel (priv->dnd_select_folder_cancellable);
1115

1116
	priv->dnd_select_folder_cancellable =
1117 1118 1119
	  _gtk_file_system_get_info (priv->fs, info->file,
				     "standard::type",
				     dnd_select_folder_get_info_cb, info);
1120 1121 1122 1123
      }
      break;

    case TEXT_PLAIN:
1124
      text = (char*) gtk_selection_data_get_text (data);
1125
      file = g_file_new_for_uri (text);
1126 1127
      gtk_file_chooser_select_file (GTK_FILE_CHOOSER (priv->dialog), file,
				    NULL);
1128 1129
      g_object_unref (file);
      g_free (text);
1130
      g_signal_emit (button, file_chooser_button_signals[FILE_SET], 0);
1131 1132 1133
      break;

    default:
1134 1135 1136
      break;
    }

1137
  gtk_drag_finish (context, TRUE, FALSE, drag_time);
1138 1139 1140 1141 1142 1143 1144 1145
}

static void
gtk_file_chooser_button_show_all (GtkWidget *widget)
{
  gtk_widget_show (widget);
}

1146 1147 1148
static void
gtk_file_chooser_button_show (GtkWidget *widget)
{
1149 1150
  GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (widget);
  GtkFileChooserButtonPrivate *priv = button->priv;
1151 1152

  if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->show)
1153
    GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->show (widget);
1154

1155
  if (priv->active)
1156
    open_dialog (GTK_FILE_CHOOSER_BUTTON (widget));
1157 1158 1159 1160 1161
}

static void
gtk_file_chooser_button_hide (GtkWidget *widget)
{
1162 1163
  GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (widget);
  GtkFileChooserButtonPrivate *priv = button->priv;
1164 1165 1166 1167

  gtk_widget_hide (priv->dialog);

  if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->hide)
1168
    GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->hide (widget);
1169
}
1170

1171 1172 1173
static void
gtk_file_chooser_button_map (GtkWidget *widget)
{
1174 1175
  GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (widget);
  GtkFileChooserButtonPrivate *priv = button->priv;
1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187

  if (!priv->folder_has_been_set)
    {
      char *current_working_dir;

      current_working_dir = g_get_current_dir ();
      gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (widget), current_working_dir);
      g_free (current_working_dir);

      priv->folder_has_been_set = TRUE;
    }

1188
  GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->map (widget);
1189 1190
}

Matthias Clasen's avatar
Matthias Clasen committed
1191 1192 1193 1194
static gboolean
gtk_file_chooser_button_mnemonic_activate (GtkWidget *widget,
					   gboolean   group_cycling)
{
1195 1196
  GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (widget);
  GtkFileChooserButtonPrivate *priv = button->priv;
Matthias Clasen's avatar
Matthias Clasen committed
1197

1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209
  switch (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog)))
    {
    case GTK_FILE_CHOOSER_ACTION_OPEN:
      gtk_widget_grab_focus (priv->button);
      break;
    case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
      return gtk_widget_mnemonic_activate (priv->combo_box, group_cycling);
      break;
    default:
      g_assert_not_reached ();
      break;
    }
Matthias Clasen's avatar
Matthias Clasen committed
1210 1211 1212 1213

  return TRUE;
}

1214
/* Changes the icons wherever it is needed */
1215 1216 1217 1218 1219 1220 1221
struct ChangeIconThemeData
{
  GtkFileChooserButton *button;
  GtkTreeRowReference *row_ref;
};

static void
1222 1223 1224 1225
change_icon_theme_get_info_cb (GCancellable *cancellable,
			       GFileInfo    *info,
			       const GError *error,
			       gpointer      user_data)
1226
{
1227
  gboolean cancelled = g_cancellable_is_cancelled (cancellable);
1228 1229 1230
  GdkPixbuf *pixbuf;
  struct ChangeIconThemeData *data = user_data;

1231
  if (!g_slist_find (data->button->priv->change_icon_theme_cancellables, cancellable))
1232 1233
    goto out;

1234 1235
  data->button->priv->change_icon_theme_cancellables =
    g_slist_remove (data->button->priv->change_icon_theme_cancellables, cancellable);
1236 1237 1238 1239

  if (cancelled || error)
    goto out;

1240
  pixbuf = _gtk_file_info_render_icon (info, GTK_WIDGET (data->button), data->button->priv->icon_size);
1241 1242 1243 1244 1245 1246 1247 1248 1249 1250

  if (pixbuf)
    {
      gint width = 0;
      GtkTreeIter iter;
      GtkTreePath *path;

      width = MAX (width, gdk_pixbuf_get_width (pixbuf));

      path = gtk_tree_row_reference_get_path (data->row_ref);
1251 1252 1253 1254
      if (path) 
        {
          gtk_tree_model_get_iter (data->button->priv->model, &iter, path);
          gtk_tree_path_free (path);
1255

1256 1257 1258
          gtk_list_store_set (GTK_LIST_STORE (data->button->priv->model), &iter,
	  		      ICON_COLUMN, pixbuf,
			      -1);
1259

1260 1261 1262 1263 1264
          g_object_set (data->button->priv->icon_cell,
		        "width", width,
		        NULL);
        }
      g_object_unref (pixbuf);
1265 1266 1267 1268 1269 1270 1271
    }

out:
  g_object_unref (data->button);
  gtk_tree_row_reference_free (data->row_ref);
  g_free (data);

1272
  g_object_unref (cancellable);
1273 1274
}

1275 1276 1277
static void
change_icon_theme (GtkFileChooserButton *button)
{
1278
  GtkFileChooserButtonPrivate *priv = button->priv;
1279
  GtkSettings *settings;
1280 1281
  GtkIconTheme *theme;
  GtkTreeIter iter;
1282 1283 1284
  GSList *l;
  gint width = 0, height = 0;

1285
  for (l = button->priv->change_icon_theme_cancellables; l; l = l->next)
1286
    {
1287 1288
      GCancellable *cancellable = G_CANCELLABLE (l->data);
      g_cancellable_cancel (cancellable);
1289
    }
1290 1291
  g_slist_free (button->priv->change_icon_theme_cancellables);
  button->priv->change_icon_theme_cancellables = NULL;
1292 1293 1294

  settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (button)));

1295
  if (gtk_icon_size_lookup_for_settings (settings, GTK_ICON_SIZE_MENU,
1296
					 &width, &height))
1297
    priv->icon_size = MAX (width, height);
1298
  else
1299
    priv->icon_size = FALLBACK_ICON_SIZE;
1300

1301
  update_label_and_image (button);
1302

1303
  gtk_tree_model_get_iter_first (priv->model, &iter);
1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325

  theme = get_icon_theme (GTK_WIDGET (button));

  do
    {
      GdkPixbuf *pixbuf;
      gchar type;
      gpointer data;

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

      switch (type)
	{
	case ROW_TYPE_SPECIAL:
	case ROW_TYPE_SHORTCUT:
	case ROW_TYPE_BOOKMARK:
	case ROW_TYPE_CURRENT_FOLDER:
	  if (data)
1326
	    {
1327
	      if (g_file_is_native (G_FILE (data)))
1328 1329
		{
		  GtkTreePath *path;
1330
		  GCancellable *cancellable;
1331 1332 1333 1334 1335 1336 1337
		  struct ChangeIconThemeData *info;		  
		  
		  info = g_new0 (struct ChangeIconThemeData, 1);
		  info->button = g_object_ref (button);
		  path = gtk_tree_model_get_path (priv->model, &iter);
		  info->row_ref = gtk_tree_row_reference_new (priv->model, path);
		  gtk_tree_path_free (path);
1338 1339

		  cancellable =
1340 1341 1342 1343
		    _gtk_file_system_get_info (priv->fs, data,
					       "standard::icon",
					       change_icon_theme_get_info_cb,
					       info);
1344 1345
		  button->priv->change_icon_theme_cancellables =
		    g_slist_append (button->priv->change_icon_theme_cancellables, cancellable);
1346 1347 1348 1349 1350 1351 1352 1353
		  pixbuf = NULL;
		}
	      else
		/* Don't call get_info for remote paths to avoid latency and
		 * auth dialogs.
		 * If we switch to a better bookmarks file format (XBEL), we
		 * should use mime info to get a better icon.
		 */
1354
		pixbuf = gtk_icon_theme_load_icon (theme, "folder-remote",
1355
						   priv->icon_size, 0, NULL);
1356
	    }
1357 1358 1359 1360 1361 1362
	  else
	    pixbuf = gtk_icon_theme_load_icon (theme, FALLBACK_ICON_NAME,
					       priv->icon_size, 0, NULL);
	  break;
	case ROW_TYPE_VOLUME:
	  if (data)
1363 1364 1365 1366
	    pixbuf = _gtk_file_system_volume_render_icon (data,
							  GTK_WIDGET (button),
							  priv->icon_size,
							  NULL);
1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381
	  else
	    pixbuf = gtk_icon_theme_load_icon (theme, FALLBACK_ICON_NAME,
					       priv->icon_size, 0, NULL);
	  break;
	default:
	  continue;
	  break;
	}

      if (pixbuf)
	width = MAX (width, gdk_pixbuf_get_width (pixbuf));

      gtk_list_store_set (GTK_LIST_STORE (priv->model), &iter,
			  ICON_COLUMN, pixbuf,
			  -1);
1382 1383 1384

      if (pixbuf)
	g_object_unref (pixbuf);
1385 1386 1387 1388 1389 1390
    }
  while (gtk_tree_model_iter_next (priv->model, &iter));

  g_object_set (button->priv->icon_cell,
		"width", width,
		NULL);
1391 1392 1393
}

static void
1394
gtk_file_chooser_button_style_updated (GtkWidget *widget)
1395
{
1396
  GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->style_updated (widget);
1397 1398 1399 1400 1401 1402 1403 1404 1405 1406

  if (gtk_widget_has_screen (widget))
    change_icon_theme (GTK_FILE_CHOOSER_BUTTON (widget));
}

static void
gtk_file_chooser_button_screen_changed (GtkWidget *widget,
					GdkScreen *old_screen)
{
  if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->screen_changed)
1407 1408
    GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->screen_changed (widget,
									     old_screen);
1409

1410
  change_icon_theme (GTK_FILE_CHOOSER_BUTTON (widget));
1411 1412
}

Matthias Clasen's avatar
Matthias Clasen committed
1413

1414 1415 1416
/* ******************* *
 *  Utility Functions  *
 * ******************* */
1417

1418 1419 1420
/* General */
static GtkIconTheme *
get_icon_theme (GtkWidget *widget)
1421
{
1422 1423
  if (gtk_widget_has_screen (widget))
    return gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget));
Matthias Clasen's avatar
<