1 /* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
2 
3 /* GTK+: gtkfilechooserbutton.c
4  *
5  * Copyright (c) 2004 James M. Cape <jcape@ignore-your.tv>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "config.h"
22 
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #ifdef HAVE_UNISTD_H
26 #include <unistd.h>
27 #endif
28 
29 #include <string.h>
30 
31 #include "gtkintl.h"
32 #include "gtkbutton.h"
33 #include "gtkcelllayout.h"
34 #include "gtkcellrenderertext.h"
35 #include "gtkcellrendererpixbuf.h"
36 #include "gtkcombobox.h"
37 #include "gtkcssiconthemevalueprivate.h"
38 #include "gtkdnd.h"
39 #include "gtkdragdest.h"
40 #include "gtkicontheme.h"
41 #include "deprecated/gtkiconfactory.h"
42 #include "gtkimage.h"
43 #include "gtklabel.h"
44 #include "gtkliststore.h"
45 #include "deprecated/gtkstock.h"
46 #include "gtktreemodelfilter.h"
47 #include "gtkseparator.h"
48 #include "gtkfilechooserdialog.h"
49 #include "gtkfilechoosernative.h"
50 #include "gtkfilechooserprivate.h"
51 #include "gtkfilechooserutils.h"
52 #include "gtkmarshalers.h"
53 
54 #include "gtkfilechooserbutton.h"
55 
56 #include "gtkorientable.h"
57 
58 #include "gtktypebuiltins.h"
59 #include "gtkprivate.h"
60 #include "gtksettings.h"
61 #include "gtkstylecontextprivate.h"
62 #include "gtkbitmaskprivate.h"
63 
64 /**
65  * SECTION:gtkfilechooserbutton
66  * @Short_description: A button to launch a file selection dialog
67  * @Title: GtkFileChooserButton
68  * @See_also:#GtkFileChooserDialog
69  *
70  * The #GtkFileChooserButton is a widget that lets the user select a
71  * file.  It implements the #GtkFileChooser interface.  Visually, it is a
72  * file name with a button to bring up a #GtkFileChooserDialog.
73  * The user can then use that dialog to change the file associated with
74  * that button.  This widget does not support setting the
75  * #GtkFileChooser:select-multiple property to %TRUE.
76  *
77  * ## Create a button to let the user select a file in /etc
78  *
79  * |[<!-- language="C" -->
80  * {
81  *   GtkWidget *button;
82  *
83  *   button = gtk_file_chooser_button_new (_("Select a file"),
84  *                                         GTK_FILE_CHOOSER_ACTION_OPEN);
85  *   gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (button),
86  *                                        "/etc");
87  * }
88  * ]|
89  *
90  * The #GtkFileChooserButton supports the #GtkFileChooserActions
91  * %GTK_FILE_CHOOSER_ACTION_OPEN and %GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER.
92  *
93  * > The #GtkFileChooserButton will ellipsize the label, and will thus
94  * > request little horizontal space.  To give the button more space,
95  * > you should call gtk_widget_get_preferred_size(),
96  * > gtk_file_chooser_button_set_width_chars(), or pack the button in
97  * > such a way that other interface elements give space to the
98  * > widget.
99  *
100  * # CSS nodes
101  *
102  * GtkFileChooserButton has a CSS node with name “filechooserbutton”, containing
103  * a subnode for the internal button with name “button” and style class “.file”.
104  */
105 
106 
107 /* **************** *
108  *  Private Macros  *
109  * **************** */
110 
111 #define FALLBACK_ICON_SIZE	16
112 #define DEFAULT_TITLE		N_("Select a File")
113 #define DESKTOP_DISPLAY_NAME	N_("Desktop")
114 #define FALLBACK_DISPLAY_NAME	N_("(None)") /* this string is used in gtk+/gtk/tests/filechooser.c - change it there if you change it here */
115 
116 
117 /* ********************** *
118  *  Private Enumerations  *
119  * ********************** */
120 
121 /* Property IDs */
122 enum
123 {
124   PROP_0,
125 
126   PROP_DIALOG,
127   PROP_TITLE,
128   PROP_WIDTH_CHARS
129 };
130 
131 /* Signals */
132 enum
133 {
134   FILE_SET,
135   LAST_SIGNAL
136 };
137 
138 /* TreeModel Columns
139  *
140  * keep in line with the store defined in gtkfilechooserbutton.ui
141  */
142 enum
143 {
144   ICON_COLUMN,
145   DISPLAY_NAME_COLUMN,
146   TYPE_COLUMN,
147   DATA_COLUMN,
148   IS_FOLDER_COLUMN,
149   CANCELLABLE_COLUMN,
150   NUM_COLUMNS
151 };
152 
153 /* TreeModel Row Types */
154 typedef enum
155 {
156   ROW_TYPE_SPECIAL,
157   ROW_TYPE_VOLUME,
158   ROW_TYPE_SHORTCUT,
159   ROW_TYPE_BOOKMARK_SEPARATOR,
160   ROW_TYPE_BOOKMARK,
161   ROW_TYPE_CURRENT_FOLDER_SEPARATOR,
162   ROW_TYPE_CURRENT_FOLDER,
163   ROW_TYPE_OTHER_SEPARATOR,
164   ROW_TYPE_OTHER,
165   ROW_TYPE_EMPTY_SELECTION,
166 
167   ROW_TYPE_INVALID = -1
168 }
169 RowType;
170 
171 
172 /* ******************** *
173  *  Private Structures  *
174  * ******************** */
175 
176 struct _GtkFileChooserButtonPrivate
177 {
178   GtkFileChooser *chooser;      /* Points to either dialog or native, depending on which is set */
179   GtkWidget *dialog;            /* Set if you explicitly enable */
180   GtkFileChooserNative *native; /* Otherwise this is set */
181   GtkWidget *button;
182   GtkWidget *image;
183   GtkWidget *label;
184   GtkWidget *combo_box;
185   GtkCellRenderer *icon_cell;
186   GtkCellRenderer *name_cell;
187 
188   GtkTreeModel *model;
189   GtkTreeModel *filter_model;
190 
191   GtkFileSystem *fs;
192   GFile *selection_while_inactive;
193   GFile *current_folder_while_inactive;
194 
195   gulong fs_volumes_changed_id;
196 
197   GCancellable *dnd_select_folder_cancellable;
198   GCancellable *update_button_cancellable;
199   GSList *change_icon_theme_cancellables;
200 
201   GtkBookmarksManager *bookmarks_manager;
202 
203   gint icon_size;
204 
205   guint8 n_special;
206   guint8 n_volumes;
207   guint8 n_shortcuts;
208   guint8 n_bookmarks;
209   guint  has_bookmark_separator       : 1;
210   guint  has_current_folder_separator : 1;
211   guint  has_current_folder           : 1;
212   guint  has_other_separator          : 1;
213 
214   /* Used for hiding/showing the dialog when the button is hidden */
215   guint  active                       : 1;
216 
217   /* Whether the next async callback from GIO should emit the "selection-changed" signal */
218   guint  is_changing_selection        : 1;
219 };
220 
221 
222 /* ************* *
223  *  DnD Support  *
224  * ************* */
225 
226 enum
227 {
228   TEXT_PLAIN,
229   TEXT_URI_LIST
230 };
231 
232 
233 /* ********************* *
234  *  Function Prototypes  *
235  * ********************* */
236 
237 /* GtkFileChooserIface Functions */
238 static void     gtk_file_chooser_button_file_chooser_iface_init (GtkFileChooserIface *iface);
239 static gboolean gtk_file_chooser_button_set_current_folder (GtkFileChooser    *chooser,
240 							    GFile             *file,
241 							    GError           **error);
242 static GFile *gtk_file_chooser_button_get_current_folder (GtkFileChooser    *chooser);
243 static gboolean gtk_file_chooser_button_select_file (GtkFileChooser *chooser,
244 						     GFile          *file,
245 						     GError        **error);
246 static void gtk_file_chooser_button_unselect_file (GtkFileChooser *chooser,
247 						   GFile          *file);
248 static void gtk_file_chooser_button_unselect_all (GtkFileChooser *chooser);
249 static GSList *gtk_file_chooser_button_get_files (GtkFileChooser *chooser);
250 static gboolean gtk_file_chooser_button_add_shortcut_folder     (GtkFileChooser      *chooser,
251 								 GFile               *file,
252 								 GError             **error);
253 static gboolean gtk_file_chooser_button_remove_shortcut_folder  (GtkFileChooser      *chooser,
254 								 GFile               *file,
255 								 GError             **error);
256 
257 /* GObject Functions */
258 static void     gtk_file_chooser_button_constructed        (GObject          *object);
259 static void     gtk_file_chooser_button_set_property       (GObject          *object,
260 							    guint             param_id,
261 							    const GValue     *value,
262 							    GParamSpec       *pspec);
263 static void     gtk_file_chooser_button_get_property       (GObject          *object,
264 							    guint             param_id,
265 							    GValue           *value,
266 							    GParamSpec       *pspec);
267 static void     gtk_file_chooser_button_finalize           (GObject          *object);
268 
269 /* GtkWidget Functions */
270 static void     gtk_file_chooser_button_destroy            (GtkWidget        *widget);
271 static void     gtk_file_chooser_button_drag_data_received (GtkWidget        *widget,
272 							    GdkDragContext   *context,
273 							    gint              x,
274 							    gint              y,
275 							    GtkSelectionData *data,
276 							    guint             type,
277 							    guint             drag_time);
278 static void     gtk_file_chooser_button_show_all           (GtkWidget        *widget);
279 static void     gtk_file_chooser_button_show               (GtkWidget        *widget);
280 static void     gtk_file_chooser_button_hide               (GtkWidget        *widget);
281 static void     gtk_file_chooser_button_map                (GtkWidget        *widget);
282 static gboolean gtk_file_chooser_button_mnemonic_activate  (GtkWidget        *widget,
283 							    gboolean          group_cycling);
284 static void     gtk_file_chooser_button_style_updated      (GtkWidget        *widget);
285 static void     gtk_file_chooser_button_screen_changed     (GtkWidget        *widget,
286 							    GdkScreen        *old_screen);
287 static void     gtk_file_chooser_button_state_flags_changed (GtkWidget       *widget,
288                                                              GtkStateFlags    previous_state);
289 
290 /* Utility Functions */
291 static GtkIconTheme *get_icon_theme               (GtkWidget            *widget);
292 static void          set_info_for_file_at_iter         (GtkFileChooserButton *fs,
293 							GFile                *file,
294 							GtkTreeIter          *iter);
295 
296 static gint          model_get_type_position      (GtkFileChooserButton *button,
297 						   RowType               row_type);
298 static void          model_free_row_data          (GtkFileChooserButton *button,
299 						   GtkTreeIter          *iter);
300 static void          model_add_special            (GtkFileChooserButton *button);
301 static void          model_add_other              (GtkFileChooserButton *button);
302 static void          model_add_empty_selection    (GtkFileChooserButton *button);
303 static void          model_add_volumes            (GtkFileChooserButton *button,
304 						   GSList               *volumes);
305 static void          model_add_bookmarks          (GtkFileChooserButton *button,
306 						   GSList               *bookmarks);
307 static void          model_update_current_folder  (GtkFileChooserButton *button,
308 						   GFile                *file);
309 static void          model_remove_rows            (GtkFileChooserButton *button,
310 						   gint                  pos,
311 						   gint                  n_rows);
312 
313 static gboolean      filter_model_visible_func    (GtkTreeModel         *model,
314 						   GtkTreeIter          *iter,
315 						   gpointer              user_data);
316 
317 static gboolean      combo_box_row_separator_func (GtkTreeModel         *model,
318 						   GtkTreeIter          *iter,
319 						   gpointer              user_data);
320 static void          name_cell_data_func          (GtkCellLayout        *layout,
321 						   GtkCellRenderer      *cell,
322 						   GtkTreeModel         *model,
323 						   GtkTreeIter          *iter,
324 						   gpointer              user_data);
325 static void          open_dialog                  (GtkFileChooserButton *button);
326 static void          update_combo_box             (GtkFileChooserButton *button);
327 static void          update_label_and_image       (GtkFileChooserButton *button);
328 
329 /* Child Object Callbacks */
330 static void     fs_volumes_changed_cb            (GtkFileSystem  *fs,
331 						  gpointer        user_data);
332 static void     bookmarks_changed_cb             (gpointer        user_data);
333 
334 static void     combo_box_changed_cb             (GtkComboBox    *combo_box,
335 						  gpointer        user_data);
336 static void     combo_box_notify_popup_shown_cb  (GObject        *object,
337 						  GParamSpec     *pspec,
338 						  gpointer        user_data);
339 
340 static void     button_clicked_cb                (GtkButton      *real_button,
341 						  gpointer        user_data);
342 
343 static void     chooser_update_preview_cb        (GtkFileChooser *dialog,
344 						  gpointer        user_data);
345 static void     chooser_notify_cb                (GObject        *dialog,
346 						  GParamSpec     *pspec,
347 						  gpointer        user_data);
348 static gboolean dialog_delete_event_cb           (GtkWidget      *dialog,
349 						  GdkEvent       *event,
350 						  gpointer        user_data);
351 static void     dialog_response_cb               (GtkDialog      *dialog,
352 						  gint            response,
353 						  gpointer        user_data);
354 static void     native_response_cb               (GtkFileChooserNative *native,
355                                                   gint            response,
356                                                   gpointer        user_data);
357 
358 static guint file_chooser_button_signals[LAST_SIGNAL] = { 0 };
359 
360 /* ******************* *
361  *  GType Declaration  *
362  * ******************* */
363 
G_DEFINE_TYPE_WITH_CODE(GtkFileChooserButton,gtk_file_chooser_button,GTK_TYPE_BOX,G_ADD_PRIVATE (GtkFileChooserButton)G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_CHOOSER,gtk_file_chooser_button_file_chooser_iface_init))364 G_DEFINE_TYPE_WITH_CODE (GtkFileChooserButton, gtk_file_chooser_button, GTK_TYPE_BOX,
365                          G_ADD_PRIVATE (GtkFileChooserButton)
366                          G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_CHOOSER,
367                                                 gtk_file_chooser_button_file_chooser_iface_init))
368 
369 
370 /* ***************** *
371  *  GType Functions  *
372  * ***************** */
373 
374 static void
375 gtk_file_chooser_button_class_init (GtkFileChooserButtonClass * class)
376 {
377   GObjectClass *gobject_class;
378   GtkWidgetClass *widget_class;
379 
380   gobject_class = G_OBJECT_CLASS (class);
381   widget_class = GTK_WIDGET_CLASS (class);
382 
383   gobject_class->constructed = gtk_file_chooser_button_constructed;
384   gobject_class->set_property = gtk_file_chooser_button_set_property;
385   gobject_class->get_property = gtk_file_chooser_button_get_property;
386   gobject_class->finalize = gtk_file_chooser_button_finalize;
387 
388   widget_class->destroy = gtk_file_chooser_button_destroy;
389   widget_class->drag_data_received = gtk_file_chooser_button_drag_data_received;
390   widget_class->show_all = gtk_file_chooser_button_show_all;
391   widget_class->show = gtk_file_chooser_button_show;
392   widget_class->hide = gtk_file_chooser_button_hide;
393   widget_class->map = gtk_file_chooser_button_map;
394   widget_class->style_updated = gtk_file_chooser_button_style_updated;
395   widget_class->screen_changed = gtk_file_chooser_button_screen_changed;
396   widget_class->mnemonic_activate = gtk_file_chooser_button_mnemonic_activate;
397   widget_class->state_flags_changed = gtk_file_chooser_button_state_flags_changed;
398 
399   /**
400    * GtkFileChooserButton::file-set:
401    * @widget: the object which received the signal.
402    *
403    * The ::file-set signal is emitted when the user selects a file.
404    *
405    * Note that this signal is only emitted when the user
406    * changes the file.
407    *
408    * Since: 2.12
409    */
410   file_chooser_button_signals[FILE_SET] =
411     g_signal_new (I_("file-set"),
412 		  G_TYPE_FROM_CLASS (gobject_class),
413 		  G_SIGNAL_RUN_FIRST,
414 		  G_STRUCT_OFFSET (GtkFileChooserButtonClass, file_set),
415 		  NULL, NULL,
416 		  NULL,
417 		  G_TYPE_NONE, 0);
418 
419   /**
420    * GtkFileChooserButton:dialog:
421    *
422    * Instance of the #GtkFileChooserDialog associated with the button.
423    *
424    * Since: 2.6
425    */
426   g_object_class_install_property (gobject_class, PROP_DIALOG,
427 				   g_param_spec_object ("dialog",
428 							P_("Dialog"),
429 							P_("The file chooser dialog to use."),
430 							GTK_TYPE_FILE_CHOOSER,
431 							(GTK_PARAM_WRITABLE |
432 							 G_PARAM_CONSTRUCT_ONLY)));
433 
434   /**
435    * GtkFileChooserButton:title:
436    *
437    * Title to put on the #GtkFileChooserDialog associated with the button.
438    *
439    * Since: 2.6
440    */
441   g_object_class_install_property (gobject_class, PROP_TITLE,
442 				   g_param_spec_string ("title",
443 							P_("Title"),
444 							P_("The title of the file chooser dialog."),
445 							_(DEFAULT_TITLE),
446 							GTK_PARAM_READWRITE));
447 
448   /**
449    * GtkFileChooserButton:width-chars:
450    *
451    * The width of the entry and label inside the button, in characters.
452    *
453    * Since: 2.6
454    */
455   g_object_class_install_property (gobject_class, PROP_WIDTH_CHARS,
456 				   g_param_spec_int ("width-chars",
457 						     P_("Width In Characters"),
458 						     P_("The desired width of the button widget, in characters."),
459 						     -1, G_MAXINT, -1,
460 						     GTK_PARAM_READWRITE));
461 
462   _gtk_file_chooser_install_properties (gobject_class);
463 
464   /* Bind class to template
465    */
466   gtk_widget_class_set_template_from_resource (widget_class,
467 					       "/org/gtk/libgtk/ui/gtkfilechooserbutton.ui");
468 
469   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserButton, model);
470   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserButton, button);
471   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserButton, image);
472   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserButton, label);
473   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserButton, combo_box);
474   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserButton, icon_cell);
475   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserButton, name_cell);
476 
477   gtk_widget_class_bind_template_callback (widget_class, button_clicked_cb);
478   gtk_widget_class_bind_template_callback (widget_class, combo_box_changed_cb);
479   gtk_widget_class_bind_template_callback (widget_class, combo_box_notify_popup_shown_cb);
480 
481   gtk_widget_class_set_css_name (widget_class, "filechooserbutton");
482 }
483 
484 static void
gtk_file_chooser_button_init(GtkFileChooserButton * button)485 gtk_file_chooser_button_init (GtkFileChooserButton *button)
486 {
487   GtkFileChooserButtonPrivate *priv;
488   GtkTargetList *target_list;
489 
490   priv = button->priv = gtk_file_chooser_button_get_instance_private (button);
491 
492   priv->icon_size = FALLBACK_ICON_SIZE;
493 
494   gtk_widget_init_template (GTK_WIDGET (button));
495 
496   /* Bookmarks manager */
497   priv->bookmarks_manager = _gtk_bookmarks_manager_new (bookmarks_changed_cb, button);
498   gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (priv->combo_box),
499 				      priv->name_cell, name_cell_data_func,
500 				      NULL, NULL);
501 
502   /* DnD */
503   gtk_drag_dest_set (GTK_WIDGET (button),
504                      (GTK_DEST_DEFAULT_ALL),
505 		     NULL, 0,
506 		     GDK_ACTION_COPY);
507   target_list = gtk_target_list_new (NULL, 0);
508   gtk_target_list_add_uri_targets (target_list, TEXT_URI_LIST);
509   gtk_target_list_add_text_targets (target_list, TEXT_PLAIN);
510   gtk_drag_dest_set_target_list (GTK_WIDGET (button), target_list);
511   gtk_target_list_unref (target_list);
512 }
513 
514 
515 /* ******************************* *
516  *  GtkFileChooserIface Functions  *
517  * ******************************* */
518 static void
gtk_file_chooser_button_file_chooser_iface_init(GtkFileChooserIface * iface)519 gtk_file_chooser_button_file_chooser_iface_init (GtkFileChooserIface *iface)
520 {
521   _gtk_file_chooser_delegate_iface_init (iface);
522 
523   iface->set_current_folder = gtk_file_chooser_button_set_current_folder;
524   iface->get_current_folder = gtk_file_chooser_button_get_current_folder;
525   iface->select_file = gtk_file_chooser_button_select_file;
526   iface->unselect_file = gtk_file_chooser_button_unselect_file;
527   iface->unselect_all = gtk_file_chooser_button_unselect_all;
528   iface->get_files = gtk_file_chooser_button_get_files;
529   iface->add_shortcut_folder = gtk_file_chooser_button_add_shortcut_folder;
530   iface->remove_shortcut_folder = gtk_file_chooser_button_remove_shortcut_folder;
531 }
532 
533 static void
emit_selection_changed_if_changing_selection(GtkFileChooserButton * button)534 emit_selection_changed_if_changing_selection (GtkFileChooserButton *button)
535 {
536   GtkFileChooserButtonPrivate *priv = button->priv;
537 
538   if (priv->is_changing_selection)
539     {
540       priv->is_changing_selection = FALSE;
541       g_signal_emit_by_name (button, "selection-changed");
542     }
543 }
544 
545 static gboolean
gtk_file_chooser_button_set_current_folder(GtkFileChooser * chooser,GFile * file,GError ** error)546 gtk_file_chooser_button_set_current_folder (GtkFileChooser    *chooser,
547 					    GFile             *file,
548 					    GError           **error)
549 {
550   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
551   GtkFileChooserButtonPrivate *priv = button->priv;
552 
553   if (priv->current_folder_while_inactive)
554     g_object_unref (priv->current_folder_while_inactive);
555 
556   priv->current_folder_while_inactive = g_object_ref (file);
557 
558   update_combo_box (button);
559 
560   g_signal_emit_by_name (button, "current-folder-changed");
561 
562   if (priv->active)
563     gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (priv->chooser), file, NULL);
564 
565   return TRUE;
566 }
567 
568 static GFile *
gtk_file_chooser_button_get_current_folder(GtkFileChooser * chooser)569 gtk_file_chooser_button_get_current_folder (GtkFileChooser *chooser)
570 {
571   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
572   GtkFileChooserButtonPrivate *priv = button->priv;
573 
574   if (priv->current_folder_while_inactive)
575     return g_object_ref (priv->current_folder_while_inactive);
576   else
577     return NULL;
578 }
579 
580 static gboolean
gtk_file_chooser_button_select_file(GtkFileChooser * chooser,GFile * file,GError ** error)581 gtk_file_chooser_button_select_file (GtkFileChooser *chooser,
582 				     GFile          *file,
583 				     GError        **error)
584 {
585   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
586   GtkFileChooserButtonPrivate *priv = button->priv;
587 
588   if (priv->selection_while_inactive)
589     g_object_unref (priv->selection_while_inactive);
590 
591   priv->selection_while_inactive = g_object_ref (file);
592 
593   priv->is_changing_selection = TRUE;
594 
595   update_label_and_image (button);
596   update_combo_box (button);
597 
598   if (priv->active)
599     gtk_file_chooser_select_file (GTK_FILE_CHOOSER (priv->chooser), file, NULL);
600 
601   return TRUE;
602 }
603 
604 static void
unselect_current_file(GtkFileChooserButton * button)605 unselect_current_file (GtkFileChooserButton *button)
606 {
607   GtkFileChooserButtonPrivate *priv = button->priv;
608 
609   if (priv->selection_while_inactive)
610     {
611       g_object_unref (priv->selection_while_inactive);
612       priv->selection_while_inactive = NULL;
613       priv->is_changing_selection = TRUE;
614     }
615 
616   update_label_and_image (button);
617   update_combo_box (button);
618 }
619 
620 static void
gtk_file_chooser_button_unselect_file(GtkFileChooser * chooser,GFile * file)621 gtk_file_chooser_button_unselect_file (GtkFileChooser *chooser,
622 				       GFile          *file)
623 {
624   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
625   GtkFileChooserButtonPrivate *priv = button->priv;
626 
627   if (g_file_equal (priv->selection_while_inactive, file))
628     unselect_current_file (button);
629 
630   if (priv->active)
631     gtk_file_chooser_unselect_file (GTK_FILE_CHOOSER (priv->chooser), file);
632 }
633 
634 static void
gtk_file_chooser_button_unselect_all(GtkFileChooser * chooser)635 gtk_file_chooser_button_unselect_all (GtkFileChooser *chooser)
636 {
637   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
638   GtkFileChooserButtonPrivate *priv = button->priv;
639 
640   unselect_current_file (button);
641 
642   if (priv->active)
643     gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->chooser));
644 }
645 
646 static GFile *
get_selected_file(GtkFileChooserButton * button)647 get_selected_file (GtkFileChooserButton *button)
648 {
649   GtkFileChooserButtonPrivate *priv = button->priv;
650   GFile *retval;
651 
652   retval = NULL;
653 
654   if (priv->selection_while_inactive)
655     retval = priv->selection_while_inactive;
656   else if (priv->chooser && gtk_file_chooser_get_action (priv->chooser) == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
657     {
658       /* If there is no "real" selection in SELECT_FOLDER mode, then we'll just return
659        * the current folder, since that is what GtkFileChooserWidget would do.
660        */
661       if (priv->current_folder_while_inactive)
662 	retval = priv->current_folder_while_inactive;
663     }
664 
665   if (retval)
666     return g_object_ref (retval);
667   else
668     return NULL;
669 }
670 
671 static GSList *
gtk_file_chooser_button_get_files(GtkFileChooser * chooser)672 gtk_file_chooser_button_get_files (GtkFileChooser *chooser)
673 {
674   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
675   GFile *file;
676 
677   file = get_selected_file (button);
678   if (file)
679     return g_slist_prepend (NULL, file);
680   else
681     return NULL;
682 }
683 
684 static gboolean
gtk_file_chooser_button_add_shortcut_folder(GtkFileChooser * chooser,GFile * file,GError ** error)685 gtk_file_chooser_button_add_shortcut_folder (GtkFileChooser  *chooser,
686 					     GFile           *file,
687 					     GError         **error)
688 {
689   GtkFileChooser *delegate;
690   gboolean retval;
691 
692   delegate = g_object_get_qdata (G_OBJECT (chooser),
693 				 GTK_FILE_CHOOSER_DELEGATE_QUARK);
694   retval = _gtk_file_chooser_add_shortcut_folder (delegate, file, error);
695 
696   if (retval)
697     {
698       GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
699       GtkFileChooserButtonPrivate *priv = button->priv;
700       GtkTreeIter iter;
701       gint pos;
702 
703       pos = model_get_type_position (button, ROW_TYPE_SHORTCUT);
704       pos += priv->n_shortcuts;
705 
706       gtk_list_store_insert (GTK_LIST_STORE (priv->model), &iter, pos);
707       gtk_list_store_set (GTK_LIST_STORE (priv->model), &iter,
708 			  ICON_COLUMN, NULL,
709 			  DISPLAY_NAME_COLUMN, _(FALLBACK_DISPLAY_NAME),
710 			  TYPE_COLUMN, ROW_TYPE_SHORTCUT,
711 			  DATA_COLUMN, g_object_ref (file),
712 			  IS_FOLDER_COLUMN, FALSE,
713 			  -1);
714       set_info_for_file_at_iter (button, file, &iter);
715       priv->n_shortcuts++;
716 
717       gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
718     }
719 
720   return retval;
721 }
722 
723 static gboolean
gtk_file_chooser_button_remove_shortcut_folder(GtkFileChooser * chooser,GFile * file,GError ** error)724 gtk_file_chooser_button_remove_shortcut_folder (GtkFileChooser  *chooser,
725 						GFile           *file,
726 						GError         **error)
727 {
728   GtkFileChooser *delegate;
729   gboolean retval;
730 
731   delegate = g_object_get_qdata (G_OBJECT (chooser),
732 				 GTK_FILE_CHOOSER_DELEGATE_QUARK);
733 
734   retval = _gtk_file_chooser_remove_shortcut_folder (delegate, file, error);
735 
736   if (retval)
737     {
738       GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser);
739       GtkFileChooserButtonPrivate *priv = button->priv;
740       GtkTreeIter iter;
741       gint pos;
742       gchar type;
743 
744       pos = model_get_type_position (button, ROW_TYPE_SHORTCUT);
745       gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, pos);
746 
747       do
748 	{
749 	  gpointer data;
750 
751 	  gtk_tree_model_get (priv->model, &iter,
752 			      TYPE_COLUMN, &type,
753 			      DATA_COLUMN, &data,
754 			      -1);
755 
756 	  if (type == ROW_TYPE_SHORTCUT &&
757 	      data && g_file_equal (data, file))
758 	    {
759 	      model_free_row_data (GTK_FILE_CHOOSER_BUTTON (chooser), &iter);
760 	      gtk_list_store_remove (GTK_LIST_STORE (priv->model), &iter);
761 	      priv->n_shortcuts--;
762 	      gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
763 	      update_combo_box (GTK_FILE_CHOOSER_BUTTON (chooser));
764 	      break;
765 	    }
766 	}
767       while (type == ROW_TYPE_SHORTCUT &&
768 	     gtk_tree_model_iter_next (priv->model, &iter));
769     }
770 
771   return retval;
772 }
773 
774 
775 /* ******************* *
776  *  GObject Functions  *
777  * ******************* */
778 
779 static void
gtk_file_chooser_button_constructed(GObject * object)780 gtk_file_chooser_button_constructed (GObject *object)
781 {
782   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (object);
783   GtkFileChooserButtonPrivate *priv = button->priv;
784   GSList *list;
785 
786   G_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->constructed (object);
787 
788   if (!priv->dialog)
789     {
790       priv->native = gtk_file_chooser_native_new (NULL,
791                                                   NULL,
792 						  GTK_FILE_CHOOSER_ACTION_OPEN,
793 						  NULL,
794 						  NULL);
795       priv->chooser = GTK_FILE_CHOOSER (priv->native);
796       gtk_file_chooser_button_set_title (button, _(DEFAULT_TITLE));
797 
798       g_signal_connect (priv->native, "response",
799                         G_CALLBACK (native_response_cb), object);
800     }
801   else /* dialog set */
802     {
803       priv->chooser = GTK_FILE_CHOOSER (priv->dialog);
804 
805       if (!gtk_window_get_title (GTK_WINDOW (priv->dialog)))
806         gtk_file_chooser_button_set_title (button, _(DEFAULT_TITLE));
807 
808       g_signal_connect (priv->dialog, "delete-event",
809                         G_CALLBACK (dialog_delete_event_cb), object);
810       g_signal_connect (priv->dialog, "response",
811                         G_CALLBACK (dialog_response_cb), object);
812 
813       g_object_add_weak_pointer (G_OBJECT (priv->dialog),
814                                  (gpointer) (&priv->dialog));
815     }
816 
817   g_signal_connect (priv->chooser, "notify",
818                     G_CALLBACK (chooser_notify_cb), object);
819 
820   /* This is used, instead of the standard delegate, to ensure that signals are only
821    * delegated when the OK button is pressed. */
822   g_object_set_qdata (object, GTK_FILE_CHOOSER_DELEGATE_QUARK, priv->chooser);
823 
824   priv->fs =
825     g_object_ref (_gtk_file_chooser_get_file_system (priv->chooser));
826 
827   model_add_special (button);
828 
829   list = _gtk_file_system_list_volumes (priv->fs);
830   model_add_volumes (button, list);
831   g_slist_free (list);
832 
833   list = _gtk_bookmarks_manager_list_bookmarks (priv->bookmarks_manager);
834   model_add_bookmarks (button, list);
835   g_slist_free_full (list, g_object_unref);
836 
837   model_add_other (button);
838 
839   model_add_empty_selection (button);
840 
841   priv->filter_model = gtk_tree_model_filter_new (priv->model, NULL);
842   gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (priv->filter_model),
843 					  filter_model_visible_func,
844 					  object, NULL);
845 
846   gtk_combo_box_set_model (GTK_COMBO_BOX (priv->combo_box), priv->filter_model);
847   gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (priv->combo_box),
848 					combo_box_row_separator_func,
849 					NULL, NULL);
850 
851   /* set up the action for a user-provided dialog, this also updates
852    * the label, image and combobox
853    */
854   g_object_set (object,
855 		"action", gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->chooser)),
856 		NULL);
857 
858   priv->fs_volumes_changed_id =
859     g_signal_connect (priv->fs, "volumes-changed",
860 		      G_CALLBACK (fs_volumes_changed_cb), object);
861 
862   update_label_and_image (button);
863   update_combo_box (button);
864 }
865 
866 static void
gtk_file_chooser_button_set_property(GObject * object,guint param_id,const GValue * value,GParamSpec * pspec)867 gtk_file_chooser_button_set_property (GObject      *object,
868 				      guint         param_id,
869 				      const GValue *value,
870 				      GParamSpec   *pspec)
871 {
872   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (object);
873   GtkFileChooserButtonPrivate *priv = button->priv;
874 
875   switch (param_id)
876     {
877     case PROP_DIALOG:
878       /* Construct-only */
879       priv->dialog = g_value_get_object (value);
880       break;
881     case PROP_WIDTH_CHARS:
882       gtk_file_chooser_button_set_width_chars (GTK_FILE_CHOOSER_BUTTON (object),
883 					       g_value_get_int (value));
884       break;
885     case GTK_FILE_CHOOSER_PROP_ACTION:
886       switch (g_value_get_enum (value))
887 	{
888 	case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
889 	case GTK_FILE_CHOOSER_ACTION_SAVE:
890 	  {
891 	    GEnumClass *eclass;
892 	    GEnumValue *eval;
893 
894 	    eclass = g_type_class_peek (GTK_TYPE_FILE_CHOOSER_ACTION);
895 	    eval = g_enum_get_value (eclass, g_value_get_enum (value));
896 	    g_warning ("%s: Choosers of type '%s' do not support '%s'.",
897 		       G_STRFUNC, G_OBJECT_TYPE_NAME (object), eval->value_name);
898 
899 	    g_value_set_enum ((GValue *) value, GTK_FILE_CHOOSER_ACTION_OPEN);
900 	  }
901 	  break;
902 	}
903 
904       g_object_set_property (G_OBJECT (priv->chooser), pspec->name, value);
905       update_label_and_image (GTK_FILE_CHOOSER_BUTTON (object));
906       update_combo_box (GTK_FILE_CHOOSER_BUTTON (object));
907 
908       switch (g_value_get_enum (value))
909 	{
910 	case GTK_FILE_CHOOSER_ACTION_OPEN:
911 	  gtk_widget_hide (priv->combo_box);
912 	  gtk_widget_show (priv->button);
913 	  break;
914 	case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
915 	  gtk_widget_hide (priv->button);
916 	  gtk_widget_show (priv->combo_box);
917 	  break;
918 	default:
919 	  g_assert_not_reached ();
920 	  break;
921 	}
922       break;
923 
924     case PROP_TITLE:
925     case GTK_FILE_CHOOSER_PROP_FILTER:
926     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET:
927     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE:
928     case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL:
929     case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET:
930     case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN:
931     case GTK_FILE_CHOOSER_PROP_DO_OVERWRITE_CONFIRMATION:
932     case GTK_FILE_CHOOSER_PROP_CREATE_FOLDERS:
933       g_object_set_property (G_OBJECT (priv->chooser), pspec->name, value);
934       break;
935 
936     case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY:
937       g_object_set_property (G_OBJECT (priv->chooser), pspec->name, value);
938       fs_volumes_changed_cb (priv->fs, button);
939       bookmarks_changed_cb (button);
940       break;
941 
942     case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE:
943       g_warning ("%s: Choosers of type '%s' do not support selecting multiple files.",
944 		 G_STRFUNC, G_OBJECT_TYPE_NAME (object));
945       break;
946     default:
947       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
948       break;
949     }
950 }
951 
952 static void
gtk_file_chooser_button_get_property(GObject * object,guint param_id,GValue * value,GParamSpec * pspec)953 gtk_file_chooser_button_get_property (GObject    *object,
954 				      guint       param_id,
955 				      GValue     *value,
956 				      GParamSpec *pspec)
957 {
958   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (object);
959   GtkFileChooserButtonPrivate *priv = button->priv;
960 
961   switch (param_id)
962     {
963     case PROP_WIDTH_CHARS:
964       g_value_set_int (value,
965 		       gtk_label_get_width_chars (GTK_LABEL (priv->label)));
966       break;
967 
968     case PROP_TITLE:
969     case GTK_FILE_CHOOSER_PROP_ACTION:
970     case GTK_FILE_CHOOSER_PROP_FILTER:
971     case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY:
972     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET:
973     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE:
974     case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL:
975     case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET:
976     case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE:
977     case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN:
978     case GTK_FILE_CHOOSER_PROP_DO_OVERWRITE_CONFIRMATION:
979     case GTK_FILE_CHOOSER_PROP_CREATE_FOLDERS:
980       g_object_get_property (G_OBJECT (priv->chooser), pspec->name, value);
981       break;
982 
983     default:
984       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
985       break;
986     }
987 }
988 
989 static void
gtk_file_chooser_button_finalize(GObject * object)990 gtk_file_chooser_button_finalize (GObject *object)
991 {
992   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (object);
993   GtkFileChooserButtonPrivate *priv = button->priv;
994 
995   if (priv->selection_while_inactive)
996     g_object_unref (priv->selection_while_inactive);
997 
998   if (priv->current_folder_while_inactive)
999     g_object_unref (priv->current_folder_while_inactive);
1000 
1001   if (priv->model)
1002     {
1003       model_remove_rows (button, 0, gtk_tree_model_iter_n_children (priv->model, NULL));
1004       g_object_unref (priv->model);
1005     }
1006 
1007   G_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->finalize (object);
1008 }
1009 
1010 /* ********************* *
1011  *  GtkWidget Functions  *
1012  * ********************* */
1013 
1014 static void
gtk_file_chooser_button_state_flags_changed(GtkWidget * widget,GtkStateFlags previous_state)1015 gtk_file_chooser_button_state_flags_changed (GtkWidget     *widget,
1016                                              GtkStateFlags  previous_state)
1017 {
1018   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (widget);
1019   GtkFileChooserButtonPrivate *priv = button->priv;
1020   GtkWidget *child;
1021 
1022   if (gtk_widget_get_visible (priv->button))
1023     child = priv->button;
1024   else
1025     child = priv->combo_box;
1026 
1027   if (gtk_widget_get_state_flags (widget) & GTK_STATE_FLAG_DROP_ACTIVE)
1028     gtk_widget_set_state_flags (child, GTK_STATE_FLAG_DROP_ACTIVE, FALSE);
1029   else
1030     gtk_widget_unset_state_flags (child, GTK_STATE_FLAG_DROP_ACTIVE);
1031 
1032   GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->state_flags_changed (widget, previous_state);
1033 }
1034 
1035 static void
gtk_file_chooser_button_destroy(GtkWidget * widget)1036 gtk_file_chooser_button_destroy (GtkWidget *widget)
1037 {
1038   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (widget);
1039   GtkFileChooserButtonPrivate *priv = button->priv;
1040   GtkTreeIter iter;
1041   GSList *l;
1042 
1043   if (priv->dialog != NULL)
1044     {
1045       gtk_widget_destroy (priv->dialog);
1046       priv->dialog = NULL;
1047     }
1048 
1049   if (priv->native)
1050     {
1051       gtk_native_dialog_destroy (GTK_NATIVE_DIALOG (priv->native));
1052       g_clear_object (&priv->native);
1053     }
1054 
1055   priv->chooser = NULL;
1056 
1057   if (priv->model && gtk_tree_model_get_iter_first (priv->model, &iter))
1058     {
1059       do
1060         model_free_row_data (button, &iter);
1061       while (gtk_tree_model_iter_next (priv->model, &iter));
1062     }
1063 
1064   if (priv->dnd_select_folder_cancellable)
1065     {
1066       g_cancellable_cancel (priv->dnd_select_folder_cancellable);
1067       priv->dnd_select_folder_cancellable = NULL;
1068     }
1069 
1070   if (priv->update_button_cancellable)
1071     {
1072       g_cancellable_cancel (priv->update_button_cancellable);
1073       priv->update_button_cancellable = NULL;
1074     }
1075 
1076   if (priv->change_icon_theme_cancellables)
1077     {
1078       for (l = priv->change_icon_theme_cancellables; l; l = l->next)
1079         {
1080 	  GCancellable *cancellable = G_CANCELLABLE (l->data);
1081 	  g_cancellable_cancel (cancellable);
1082         }
1083       g_slist_free (priv->change_icon_theme_cancellables);
1084       priv->change_icon_theme_cancellables = NULL;
1085     }
1086 
1087   if (priv->filter_model)
1088     {
1089       g_object_unref (priv->filter_model);
1090       priv->filter_model = NULL;
1091     }
1092 
1093   if (priv->fs)
1094     {
1095       g_signal_handler_disconnect (priv->fs, priv->fs_volumes_changed_id);
1096       g_object_unref (priv->fs);
1097       priv->fs = NULL;
1098     }
1099 
1100   if (priv->bookmarks_manager)
1101     {
1102       _gtk_bookmarks_manager_free (priv->bookmarks_manager);
1103       priv->bookmarks_manager = NULL;
1104     }
1105 
1106   GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->destroy (widget);
1107 }
1108 
1109 struct DndSelectFolderData
1110 {
1111   GtkFileSystem *file_system;
1112   GtkFileChooserButton *button;
1113   GtkFileChooserAction action;
1114   GFile *file;
1115   gchar **uris;
1116   guint i;
1117   gboolean selected;
1118 };
1119 
1120 static void
dnd_select_folder_get_info_cb(GCancellable * cancellable,GFileInfo * info,const GError * error,gpointer user_data)1121 dnd_select_folder_get_info_cb (GCancellable *cancellable,
1122 			       GFileInfo    *info,
1123 			       const GError *error,
1124 			       gpointer      user_data)
1125 {
1126   gboolean cancelled = g_cancellable_is_cancelled (cancellable);
1127   struct DndSelectFolderData *data = user_data;
1128 
1129   if (cancellable != data->button->priv->dnd_select_folder_cancellable)
1130     {
1131       g_object_unref (data->button);
1132       g_object_unref (data->file);
1133       g_strfreev (data->uris);
1134       g_free (data);
1135 
1136       g_object_unref (cancellable);
1137       return;
1138     }
1139 
1140   data->button->priv->dnd_select_folder_cancellable = NULL;
1141 
1142   if (!cancelled && !error && info != NULL)
1143     {
1144       gboolean is_folder;
1145 
1146       is_folder = _gtk_file_info_consider_as_directory (info);
1147 
1148       data->selected =
1149 	(((data->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER && is_folder) ||
1150 	  (data->action == GTK_FILE_CHOOSER_ACTION_OPEN && !is_folder)) &&
1151 	 gtk_file_chooser_select_file (GTK_FILE_CHOOSER (data->button), data->file, NULL));
1152     }
1153   else
1154     data->selected = FALSE;
1155 
1156   if (data->selected || data->uris[++data->i] == NULL)
1157     {
1158       g_signal_emit (data->button, file_chooser_button_signals[FILE_SET], 0);
1159 
1160       g_object_unref (data->button);
1161       g_object_unref (data->file);
1162       g_strfreev (data->uris);
1163       g_free (data);
1164 
1165       g_object_unref (cancellable);
1166       return;
1167     }
1168 
1169   if (data->file)
1170     g_object_unref (data->file);
1171 
1172   data->file = g_file_new_for_uri (data->uris[data->i]);
1173 
1174   data->button->priv->dnd_select_folder_cancellable =
1175     _gtk_file_system_get_info (data->file_system, data->file,
1176 			       "standard::type",
1177 			       dnd_select_folder_get_info_cb, user_data);
1178 
1179   g_object_unref (cancellable);
1180 }
1181 
1182 static void
gtk_file_chooser_button_drag_data_received(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * data,guint type,guint drag_time)1183 gtk_file_chooser_button_drag_data_received (GtkWidget	     *widget,
1184 					    GdkDragContext   *context,
1185 					    gint	      x,
1186 					    gint	      y,
1187 					    GtkSelectionData *data,
1188 					    guint	      type,
1189 					    guint	      drag_time)
1190 {
1191   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (widget);
1192   GtkFileChooserButtonPrivate *priv = button->priv;
1193   GFile *file;
1194   gchar *text;
1195 
1196   if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->drag_data_received != NULL)
1197     GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->drag_data_received (widget,
1198 										 context,
1199 										 x, y,
1200 										 data, type,
1201 										 drag_time);
1202 
1203   if (widget == NULL || context == NULL || data == NULL || gtk_selection_data_get_length (data) < 0)
1204     return;
1205 
1206   switch (type)
1207     {
1208     case TEXT_URI_LIST:
1209       {
1210 	gchar **uris;
1211 	struct DndSelectFolderData *info;
1212 
1213 	uris = gtk_selection_data_get_uris (data);
1214 
1215 	if (uris == NULL)
1216 	  break;
1217 
1218 	info = g_new0 (struct DndSelectFolderData, 1);
1219 	info->button = g_object_ref (button);
1220 	info->i = 0;
1221 	info->uris = uris;
1222 	info->selected = FALSE;
1223 	info->file_system = priv->fs;
1224 	g_object_get (priv->chooser, "action", &info->action, NULL);
1225 
1226 	info->file = g_file_new_for_uri (info->uris[info->i]);
1227 
1228 	if (priv->dnd_select_folder_cancellable)
1229 	  g_cancellable_cancel (priv->dnd_select_folder_cancellable);
1230 
1231 	priv->dnd_select_folder_cancellable =
1232 	  _gtk_file_system_get_info (priv->fs, info->file,
1233 				     "standard::type",
1234 				     dnd_select_folder_get_info_cb, info);
1235       }
1236       break;
1237 
1238     case TEXT_PLAIN:
1239       text = (char*) gtk_selection_data_get_text (data);
1240       file = g_file_new_for_uri (text);
1241       gtk_file_chooser_select_file (GTK_FILE_CHOOSER (priv->chooser), file, NULL);
1242       g_object_unref (file);
1243       g_free (text);
1244       g_signal_emit (button, file_chooser_button_signals[FILE_SET], 0);
1245       break;
1246 
1247     default:
1248       break;
1249     }
1250 
1251   gtk_drag_finish (context, TRUE, FALSE, drag_time);
1252 }
1253 
1254 static void
gtk_file_chooser_button_show_all(GtkWidget * widget)1255 gtk_file_chooser_button_show_all (GtkWidget *widget)
1256 {
1257   gtk_widget_show (widget);
1258 }
1259 
1260 static void
gtk_file_chooser_button_show(GtkWidget * widget)1261 gtk_file_chooser_button_show (GtkWidget *widget)
1262 {
1263   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (widget);
1264   GtkFileChooserButtonPrivate *priv = button->priv;
1265 
1266   if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->show)
1267     GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->show (widget);
1268 
1269   if (priv->active)
1270     open_dialog (GTK_FILE_CHOOSER_BUTTON (widget));
1271 }
1272 
1273 static void
gtk_file_chooser_button_hide(GtkWidget * widget)1274 gtk_file_chooser_button_hide (GtkWidget *widget)
1275 {
1276   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (widget);
1277   GtkFileChooserButtonPrivate *priv = button->priv;
1278 
1279   if (priv->dialog)
1280     gtk_widget_hide (priv->dialog);
1281   else
1282     gtk_native_dialog_hide (GTK_NATIVE_DIALOG (priv->native));
1283 
1284   if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->hide)
1285     GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->hide (widget);
1286 }
1287 
1288 static void
gtk_file_chooser_button_map(GtkWidget * widget)1289 gtk_file_chooser_button_map (GtkWidget *widget)
1290 {
1291   GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->map (widget);
1292 }
1293 
1294 static gboolean
gtk_file_chooser_button_mnemonic_activate(GtkWidget * widget,gboolean group_cycling)1295 gtk_file_chooser_button_mnemonic_activate (GtkWidget *widget,
1296 					   gboolean   group_cycling)
1297 {
1298   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (widget);
1299   GtkFileChooserButtonPrivate *priv = button->priv;
1300 
1301   switch (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->chooser)))
1302     {
1303     case GTK_FILE_CHOOSER_ACTION_OPEN:
1304       gtk_widget_grab_focus (priv->button);
1305       break;
1306     case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
1307       return gtk_widget_mnemonic_activate (priv->combo_box, group_cycling);
1308       break;
1309     default:
1310       g_assert_not_reached ();
1311       break;
1312     }
1313 
1314   return TRUE;
1315 }
1316 
1317 /* Changes the icons wherever it is needed */
1318 struct ChangeIconThemeData
1319 {
1320   GtkFileChooserButton *button;
1321   GtkTreeRowReference *row_ref;
1322 };
1323 
1324 static void
change_icon_theme_get_info_cb(GCancellable * cancellable,GFileInfo * info,const GError * error,gpointer user_data)1325 change_icon_theme_get_info_cb (GCancellable *cancellable,
1326 			       GFileInfo    *info,
1327 			       const GError *error,
1328 			       gpointer      user_data)
1329 {
1330   gboolean cancelled = g_cancellable_is_cancelled (cancellable);
1331   cairo_surface_t *surface;
1332   struct ChangeIconThemeData *data = user_data;
1333 
1334   if (!g_slist_find (data->button->priv->change_icon_theme_cancellables, cancellable))
1335     goto out;
1336 
1337   data->button->priv->change_icon_theme_cancellables =
1338     g_slist_remove (data->button->priv->change_icon_theme_cancellables, cancellable);
1339 
1340   if (cancelled || error)
1341     goto out;
1342 
1343   surface = _gtk_file_info_render_icon (info, GTK_WIDGET (data->button), data->button->priv->icon_size);
1344 
1345   if (surface)
1346     {
1347       gint width = 0;
1348       GtkTreeIter iter;
1349       GtkTreePath *path;
1350 
1351       width = MAX (width, data->button->priv->icon_size);
1352 
1353       path = gtk_tree_row_reference_get_path (data->row_ref);
1354       if (path)
1355         {
1356           gtk_tree_model_get_iter (data->button->priv->model, &iter, path);
1357           gtk_tree_path_free (path);
1358 
1359           gtk_list_store_set (GTK_LIST_STORE (data->button->priv->model), &iter,
1360 	  		      ICON_COLUMN, surface,
1361 			      -1);
1362 
1363           g_object_set (data->button->priv->icon_cell,
1364 		        "width", width,
1365 		        NULL);
1366         }
1367       cairo_surface_destroy (surface);
1368     }
1369 
1370 out:
1371   g_object_unref (data->button);
1372   gtk_tree_row_reference_free (data->row_ref);
1373   g_free (data);
1374 
1375   g_object_unref (cancellable);
1376 }
1377 
1378 static void
change_icon_theme(GtkFileChooserButton * button)1379 change_icon_theme (GtkFileChooserButton *button)
1380 {
1381   GtkFileChooserButtonPrivate *priv = button->priv;
1382   GtkIconTheme *theme;
1383   GtkTreeIter iter;
1384   GSList *l;
1385   gint width = 0, height = 0;
1386 
1387   for (l = button->priv->change_icon_theme_cancellables; l; l = l->next)
1388     {
1389       GCancellable *cancellable = G_CANCELLABLE (l->data);
1390       g_cancellable_cancel (cancellable);
1391     }
1392   g_slist_free (button->priv->change_icon_theme_cancellables);
1393   button->priv->change_icon_theme_cancellables = NULL;
1394 
1395   if (gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, &height))
1396     priv->icon_size = MAX (width, height);
1397   else
1398     priv->icon_size = FALLBACK_ICON_SIZE;
1399 
1400   update_label_and_image (button);
1401 
1402   gtk_tree_model_get_iter_first (priv->model, &iter);
1403 
1404   theme = get_icon_theme (GTK_WIDGET (button));
1405 
1406   do
1407     {
1408       cairo_surface_t *surface = NULL;
1409       gchar type;
1410       gpointer data;
1411 
1412       type = ROW_TYPE_INVALID;
1413       gtk_tree_model_get (priv->model, &iter,
1414 			  TYPE_COLUMN, &type,
1415 			  DATA_COLUMN, &data,
1416 			  -1);
1417 
1418       switch (type)
1419 	{
1420 	case ROW_TYPE_SPECIAL:
1421 	case ROW_TYPE_SHORTCUT:
1422 	case ROW_TYPE_BOOKMARK:
1423 	case ROW_TYPE_CURRENT_FOLDER:
1424 	  if (data)
1425 	    {
1426 	      if (g_file_is_native (G_FILE (data)))
1427 		{
1428 		  GtkTreePath *path;
1429 		  GCancellable *cancellable;
1430 		  struct ChangeIconThemeData *info;
1431 
1432 		  info = g_new0 (struct ChangeIconThemeData, 1);
1433 		  info->button = g_object_ref (button);
1434 		  path = gtk_tree_model_get_path (priv->model, &iter);
1435 		  info->row_ref = gtk_tree_row_reference_new (priv->model, path);
1436 		  gtk_tree_path_free (path);
1437 
1438 		  cancellable =
1439 		    _gtk_file_system_get_info (priv->fs, data,
1440 					       "standard::icon",
1441 					       change_icon_theme_get_info_cb,
1442 					       info);
1443 		  button->priv->change_icon_theme_cancellables =
1444 		    g_slist_append (button->priv->change_icon_theme_cancellables, cancellable);
1445 		  surface = NULL;
1446 		}
1447 	      else
1448                 {
1449                   /* Don't call get_info for remote paths to avoid latency and
1450                    * auth dialogs.
1451                    * If we switch to a better bookmarks file format (XBEL), we
1452                    * should use mime info to get a better icon.
1453                    */
1454                   surface = gtk_icon_theme_load_surface (theme, "folder-remote",
1455                                                          priv->icon_size,
1456                                                          gtk_widget_get_scale_factor (GTK_WIDGET (button)),
1457                                                          gtk_widget_get_window (GTK_WIDGET (button)),
1458                                                          0, NULL);
1459                 }
1460 	    }
1461 	  break;
1462 	case ROW_TYPE_VOLUME:
1463 	  if (data)
1464             {
1465               surface = _gtk_file_system_volume_render_icon (data,
1466                                                              GTK_WIDGET (button),
1467                                                              priv->icon_size,
1468                                                              NULL);
1469             }
1470 
1471 	  break;
1472 	default:
1473 	  continue;
1474 	  break;
1475 	}
1476 
1477       if (surface)
1478 	width = MAX (width, priv->icon_size);
1479 
1480       gtk_list_store_set (GTK_LIST_STORE (priv->model), &iter,
1481 			  ICON_COLUMN, surface,
1482 			  -1);
1483 
1484       if (surface)
1485 	cairo_surface_destroy (surface);
1486     }
1487   while (gtk_tree_model_iter_next (priv->model, &iter));
1488 
1489   g_object_set (button->priv->icon_cell,
1490 		"width", width,
1491 		NULL);
1492 }
1493 
1494 static void
gtk_file_chooser_button_style_updated(GtkWidget * widget)1495 gtk_file_chooser_button_style_updated (GtkWidget *widget)
1496 {
1497   GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->style_updated (widget);
1498 
1499   if (gtk_widget_has_screen (widget))
1500     {
1501       /* We need to update the icon surface, but only in case
1502        * the icon theme really changed. */
1503       GtkStyleContext *context = gtk_widget_get_style_context (widget);
1504       GtkCssStyleChange *change = gtk_style_context_get_change (context);
1505       if (!change || gtk_css_style_change_changes_property (change, GTK_CSS_PROPERTY_ICON_THEME))
1506         change_icon_theme (GTK_FILE_CHOOSER_BUTTON (widget));
1507     }
1508 }
1509 
1510 static void
gtk_file_chooser_button_screen_changed(GtkWidget * widget,GdkScreen * old_screen)1511 gtk_file_chooser_button_screen_changed (GtkWidget *widget,
1512 					GdkScreen *old_screen)
1513 {
1514   if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->screen_changed)
1515     GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->screen_changed (widget,
1516 									     old_screen);
1517 
1518   change_icon_theme (GTK_FILE_CHOOSER_BUTTON (widget));
1519 }
1520 
1521 
1522 /* ******************* *
1523  *  Utility Functions  *
1524  * ******************* */
1525 
1526 /* General */
1527 static GtkIconTheme *
get_icon_theme(GtkWidget * widget)1528 get_icon_theme (GtkWidget *widget)
1529 {
1530   return gtk_css_icon_theme_value_get_icon_theme
1531     (_gtk_style_context_peek_property (gtk_widget_get_style_context (widget), GTK_CSS_PROPERTY_ICON_THEME));
1532 }
1533 
1534 
1535 struct SetDisplayNameData
1536 {
1537   GtkFileChooserButton *button;
1538   char *label;
1539   GtkTreeRowReference *row_ref;
1540 };
1541 
1542 static void
set_info_get_info_cb(GCancellable * cancellable,GFileInfo * info,const GError * error,gpointer callback_data)1543 set_info_get_info_cb (GCancellable *cancellable,
1544 		      GFileInfo    *info,
1545 		      const GError *error,
1546 		      gpointer      callback_data)
1547 {
1548   gboolean cancelled = g_cancellable_is_cancelled (cancellable);
1549   cairo_surface_t *surface;
1550   GtkTreePath *path;
1551   GtkTreeIter iter;
1552   GCancellable *model_cancellable = NULL;
1553   struct SetDisplayNameData *data = callback_data;
1554   gboolean is_folder;
1555 
1556   if (!data->button->priv->model)
1557     /* button got destroyed */
1558     goto out;
1559 
1560   path = gtk_tree_row_reference_get_path (data->row_ref);
1561   if (!path)
1562     /* Cancellable doesn't exist anymore in the model */
1563     goto out;
1564 
1565   gtk_tree_model_get_iter (data->button->priv->model, &iter, path);
1566   gtk_tree_path_free (path);
1567 
1568   /* Validate the cancellable */
1569   gtk_tree_model_get (data->button->priv->model, &iter,
1570 		      CANCELLABLE_COLUMN, &model_cancellable,
1571 		      -1);
1572   if (cancellable != model_cancellable)
1573     goto out;
1574 
1575   gtk_list_store_set (GTK_LIST_STORE (data->button->priv->model), &iter,
1576 		      CANCELLABLE_COLUMN, NULL,
1577 		      -1);
1578 
1579   if (cancelled || error)
1580     /* There was an error, leave the fallback name in there */
1581     goto out;
1582 
1583   surface = _gtk_file_info_render_icon (info, GTK_WIDGET (data->button), data->button->priv->icon_size);
1584 
1585   if (!data->label)
1586     data->label = g_strdup (g_file_info_get_display_name (info));
1587 
1588   is_folder = _gtk_file_info_consider_as_directory (info);
1589 
1590   gtk_list_store_set (GTK_LIST_STORE (data->button->priv->model), &iter,
1591 		      ICON_COLUMN, surface,
1592 		      DISPLAY_NAME_COLUMN, data->label,
1593 		      IS_FOLDER_COLUMN, is_folder,
1594 		      -1);
1595 
1596   if (surface)
1597     cairo_surface_destroy (surface);
1598 
1599 out:
1600   g_object_unref (data->button);
1601   g_free (data->label);
1602   gtk_tree_row_reference_free (data->row_ref);
1603   g_free (data);
1604 
1605   g_object_unref (cancellable);
1606 }
1607 
1608 static void
set_info_for_file_at_iter(GtkFileChooserButton * button,GFile * file,GtkTreeIter * iter)1609 set_info_for_file_at_iter (GtkFileChooserButton *button,
1610 			   GFile                *file,
1611 			   GtkTreeIter          *iter)
1612 {
1613   struct SetDisplayNameData *data;
1614   GtkTreePath *tree_path;
1615   GCancellable *cancellable;
1616 
1617   data = g_new0 (struct SetDisplayNameData, 1);
1618   data->button = g_object_ref (button);
1619   data->label = _gtk_bookmarks_manager_get_bookmark_label (button->priv->bookmarks_manager, file);
1620 
1621   tree_path = gtk_tree_model_get_path (button->priv->model, iter);
1622   data->row_ref = gtk_tree_row_reference_new (button->priv->model, tree_path);
1623   gtk_tree_path_free (tree_path);
1624 
1625   cancellable = _gtk_file_system_get_info (button->priv->fs, file,
1626 					   "standard::type,standard::icon,standard::display-name",
1627 					   set_info_get_info_cb, data);
1628 
1629   gtk_list_store_set (GTK_LIST_STORE (button->priv->model), iter,
1630 		      CANCELLABLE_COLUMN, cancellable,
1631 		      -1);
1632 }
1633 
1634 /* Shortcuts Model */
1635 static gint
model_get_type_position(GtkFileChooserButton * button,RowType row_type)1636 model_get_type_position (GtkFileChooserButton *button,
1637 			 RowType               row_type)
1638 {
1639   gint retval = 0;
1640 
1641   if (row_type == ROW_TYPE_SPECIAL)
1642     return retval;
1643 
1644   retval += button->priv->n_special;
1645 
1646   if (row_type == ROW_TYPE_VOLUME)
1647     return retval;
1648 
1649   retval += button->priv->n_volumes;
1650 
1651   if (row_type == ROW_TYPE_SHORTCUT)
1652     return retval;
1653 
1654   retval += button->priv->n_shortcuts;
1655 
1656   if (row_type == ROW_TYPE_BOOKMARK_SEPARATOR)
1657     return retval;
1658 
1659   retval += button->priv->has_bookmark_separator;
1660 
1661   if (row_type == ROW_TYPE_BOOKMARK)
1662     return retval;
1663 
1664   retval += button->priv->n_bookmarks;
1665 
1666   if (row_type == ROW_TYPE_CURRENT_FOLDER_SEPARATOR)
1667     return retval;
1668 
1669   retval += button->priv->has_current_folder_separator;
1670 
1671   if (row_type == ROW_TYPE_CURRENT_FOLDER)
1672     return retval;
1673 
1674   retval += button->priv->has_current_folder;
1675 
1676   if (row_type == ROW_TYPE_OTHER_SEPARATOR)
1677     return retval;
1678 
1679   retval += button->priv->has_other_separator;
1680 
1681   if (row_type == ROW_TYPE_OTHER)
1682     return retval;
1683 
1684   retval++;
1685 
1686   if (row_type == ROW_TYPE_EMPTY_SELECTION)
1687     return retval;
1688 
1689   g_assert_not_reached ();
1690   return -1;
1691 }
1692 
1693 static void
model_free_row_data(GtkFileChooserButton * button,GtkTreeIter * iter)1694 model_free_row_data (GtkFileChooserButton *button,
1695 		     GtkTreeIter          *iter)
1696 {
1697   gchar type;
1698   gpointer data;
1699   GCancellable *cancellable;
1700 
1701   gtk_tree_model_get (button->priv->model, iter,
1702 		      TYPE_COLUMN, &type,
1703 		      DATA_COLUMN, &data,
1704 		      CANCELLABLE_COLUMN, &cancellable,
1705 		      -1);
1706 
1707   if (cancellable)
1708     g_cancellable_cancel (cancellable);
1709 
1710   switch (type)
1711     {
1712     case ROW_TYPE_SPECIAL:
1713     case ROW_TYPE_SHORTCUT:
1714     case ROW_TYPE_BOOKMARK:
1715     case ROW_TYPE_CURRENT_FOLDER:
1716       g_object_unref (data);
1717       break;
1718     case ROW_TYPE_VOLUME:
1719       _gtk_file_system_volume_unref (data);
1720       break;
1721     default:
1722       break;
1723     }
1724 }
1725 
1726 static void
model_add_special_get_info_cb(GCancellable * cancellable,GFileInfo * info,const GError * error,gpointer user_data)1727 model_add_special_get_info_cb (GCancellable *cancellable,
1728 			       GFileInfo    *info,
1729 			       const GError *error,
1730 			       gpointer      user_data)
1731 {
1732   gboolean cancelled = g_cancellable_is_cancelled (cancellable);
1733   GtkTreeIter iter;
1734   GtkTreePath *path;
1735   cairo_surface_t *surface;
1736   GCancellable *model_cancellable = NULL;
1737   struct ChangeIconThemeData *data = user_data;
1738   gchar *name;
1739 
1740   if (!data->button->priv->model)
1741     /* button got destroyed */
1742     goto out;
1743 
1744   path = gtk_tree_row_reference_get_path (data->row_ref);
1745   if (!path)
1746     /* Cancellable doesn't exist anymore in the model */
1747     goto out;
1748 
1749   gtk_tree_model_get_iter (data->button->priv->model, &iter, path);
1750   gtk_tree_path_free (path);
1751 
1752   gtk_tree_model_get (data->button->priv->model, &iter,
1753 		      CANCELLABLE_COLUMN, &model_cancellable,
1754 		      -1);
1755   if (cancellable != model_cancellable)
1756     goto out;
1757 
1758   gtk_list_store_set (GTK_LIST_STORE (data->button->priv->model), &iter,
1759 		      CANCELLABLE_COLUMN, NULL,
1760 		      -1);
1761 
1762   if (cancelled || error)
1763     goto out;
1764 
1765   surface = _gtk_file_info_render_icon (info, GTK_WIDGET (data->button), data->button->priv->icon_size);
1766   if (surface)
1767     {
1768       gtk_list_store_set (GTK_LIST_STORE (data->button->priv->model), &iter,
1769 			  ICON_COLUMN, surface,
1770 			  -1);
1771       cairo_surface_destroy (surface);
1772     }
1773 
1774   gtk_tree_model_get (data->button->priv->model, &iter,
1775                       DISPLAY_NAME_COLUMN, &name,
1776                       -1);
1777   if (!name)
1778     gtk_list_store_set (GTK_LIST_STORE (data->button->priv->model), &iter,
1779   		        DISPLAY_NAME_COLUMN, g_file_info_get_display_name (info),
1780 		        -1);
1781   g_free (name);
1782 
1783 out:
1784   g_object_unref (data->button);
1785   gtk_tree_row_reference_free (data->row_ref);
1786   g_free (data);
1787 
1788   g_object_unref (cancellable);
1789 }
1790 
1791 static void
model_add_special(GtkFileChooserButton * button)1792 model_add_special (GtkFileChooserButton *button)
1793 {
1794   const gchar *homedir;
1795   const gchar *desktopdir;
1796   GtkListStore *store;
1797   GtkTreeIter iter;
1798   GFile *file;
1799   gint pos;
1800 
1801   store = GTK_LIST_STORE (button->priv->model);
1802   pos = model_get_type_position (button, ROW_TYPE_SPECIAL);
1803 
1804   homedir = g_get_home_dir ();
1805 
1806   if (homedir)
1807     {
1808       GtkTreePath *tree_path;
1809       GCancellable *cancellable;
1810       struct ChangeIconThemeData *info;
1811 
1812       file = g_file_new_for_path (homedir);
1813       gtk_list_store_insert (store, &iter, pos);
1814       pos++;
1815 
1816       info = g_new0 (struct ChangeIconThemeData, 1);
1817       info->button = g_object_ref (button);
1818       tree_path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
1819       info->row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (store),
1820 						  tree_path);
1821       gtk_tree_path_free (tree_path);
1822 
1823       cancellable = _gtk_file_system_get_info (button->priv->fs, file,
1824 					       "standard::icon,standard::display-name",
1825 					       model_add_special_get_info_cb, info);
1826 
1827       gtk_list_store_set (store, &iter,
1828 			  ICON_COLUMN, NULL,
1829 			  DISPLAY_NAME_COLUMN, NULL,
1830 			  TYPE_COLUMN, ROW_TYPE_SPECIAL,
1831 			  DATA_COLUMN, file,
1832 			  IS_FOLDER_COLUMN, TRUE,
1833 			  CANCELLABLE_COLUMN, cancellable,
1834 			  -1);
1835 
1836       button->priv->n_special++;
1837     }
1838 
1839   desktopdir = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP);
1840 
1841   /* "To disable a directory, point it to the homedir."
1842    * See http://freedesktop.org/wiki/Software/xdg-user-dirs
1843    */
1844   if (g_strcmp0 (desktopdir, g_get_home_dir ()) != 0)
1845     {
1846       GtkTreePath *tree_path;
1847       GCancellable *cancellable;
1848       struct ChangeIconThemeData *info;
1849 
1850       file = g_file_new_for_path (desktopdir);
1851       gtk_list_store_insert (store, &iter, pos);
1852       pos++;
1853 
1854       info = g_new0 (struct ChangeIconThemeData, 1);
1855       info->button = g_object_ref (button);
1856       tree_path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
1857       info->row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (store),
1858 						  tree_path);
1859       gtk_tree_path_free (tree_path);
1860 
1861       cancellable = _gtk_file_system_get_info (button->priv->fs, file,
1862 					       "standard::icon,standard::display-name",
1863 					       model_add_special_get_info_cb, info);
1864 
1865       gtk_list_store_set (store, &iter,
1866 			  TYPE_COLUMN, ROW_TYPE_SPECIAL,
1867 			  ICON_COLUMN, NULL,
1868 			  DISPLAY_NAME_COLUMN, _(DESKTOP_DISPLAY_NAME),
1869 			  DATA_COLUMN, file,
1870 			  IS_FOLDER_COLUMN, TRUE,
1871 			  CANCELLABLE_COLUMN, cancellable,
1872 			  -1);
1873 
1874       button->priv->n_special++;
1875     }
1876 }
1877 
1878 static void
model_add_volumes(GtkFileChooserButton * button,GSList * volumes)1879 model_add_volumes (GtkFileChooserButton *button,
1880                    GSList               *volumes)
1881 {
1882   GtkListStore *store;
1883   gint pos;
1884   gboolean local_only;
1885   GSList *l;
1886 
1887   if (!volumes)
1888     return;
1889 
1890   store = GTK_LIST_STORE (button->priv->model);
1891   pos = model_get_type_position (button, ROW_TYPE_VOLUME);
1892   local_only = gtk_file_chooser_get_local_only (GTK_FILE_CHOOSER (button->priv->chooser));
1893 
1894   for (l = volumes; l; l = l->next)
1895     {
1896       GtkFileSystemVolume *volume;
1897       GtkTreeIter iter;
1898       cairo_surface_t *surface;
1899       gchar *display_name;
1900 
1901       volume = l->data;
1902 
1903       if (local_only)
1904         {
1905           if (_gtk_file_system_volume_is_mounted (volume))
1906             {
1907               GFile *base_file;
1908 
1909               base_file = _gtk_file_system_volume_get_root (volume);
1910               if (base_file != NULL)
1911                 {
1912                   if (!_gtk_file_has_native_path (base_file))
1913                     {
1914                       g_object_unref (base_file);
1915                       continue;
1916                     }
1917                   else
1918                     g_object_unref (base_file);
1919                 }
1920             }
1921         }
1922 
1923       surface = _gtk_file_system_volume_render_icon (volume,
1924                                                      GTK_WIDGET (button),
1925                                                      button->priv->icon_size,
1926                                                      NULL);
1927       display_name = _gtk_file_system_volume_get_display_name (volume);
1928 
1929       gtk_list_store_insert (store, &iter, pos);
1930       gtk_list_store_set (store, &iter,
1931                           ICON_COLUMN, surface,
1932                           DISPLAY_NAME_COLUMN, display_name,
1933                           TYPE_COLUMN, ROW_TYPE_VOLUME,
1934                           DATA_COLUMN, _gtk_file_system_volume_ref (volume),
1935                           IS_FOLDER_COLUMN, TRUE,
1936                           -1);
1937 
1938       if (surface)
1939         cairo_surface_destroy (surface);
1940       g_free (display_name);
1941 
1942       button->priv->n_volumes++;
1943       pos++;
1944     }
1945 }
1946 
1947 extern gchar * _gtk_file_chooser_label_for_file (GFile *file);
1948 
1949 static void
model_add_bookmarks(GtkFileChooserButton * button,GSList * bookmarks)1950 model_add_bookmarks (GtkFileChooserButton *button,
1951 		     GSList               *bookmarks)
1952 {
1953   GtkListStore *store;
1954   GtkTreeIter iter;
1955   gint pos;
1956   gboolean local_only;
1957   GSList *l;
1958 
1959   if (!bookmarks)
1960     return;
1961 
1962   store = GTK_LIST_STORE (button->priv->model);
1963   pos = model_get_type_position (button, ROW_TYPE_BOOKMARK);
1964   local_only = gtk_file_chooser_get_local_only (GTK_FILE_CHOOSER (button->priv->chooser));
1965 
1966   for (l = bookmarks; l; l = l->next)
1967     {
1968       GFile *file;
1969 
1970       file = l->data;
1971 
1972       if (_gtk_file_has_native_path (file))
1973 	{
1974 	  gtk_list_store_insert (store, &iter, pos);
1975 	  gtk_list_store_set (store, &iter,
1976 			      ICON_COLUMN, NULL,
1977 			      DISPLAY_NAME_COLUMN, _(FALLBACK_DISPLAY_NAME),
1978 			      TYPE_COLUMN, ROW_TYPE_BOOKMARK,
1979 			      DATA_COLUMN, g_object_ref (file),
1980 			      IS_FOLDER_COLUMN, FALSE,
1981 			      -1);
1982 	  set_info_for_file_at_iter (button, file, &iter);
1983 	}
1984       else
1985 	{
1986 	  gchar *label;
1987 	  GtkIconTheme *icon_theme;
1988 	  cairo_surface_t *surface = NULL;
1989 
1990 	  if (local_only)
1991 	    continue;
1992 
1993 	  /* Don't call get_info for remote paths to avoid latency and
1994 	   * auth dialogs.
1995 	   * If we switch to a better bookmarks file format (XBEL), we
1996 	   * should use mime info to get a better icon.
1997 	   */
1998 	  label = _gtk_bookmarks_manager_get_bookmark_label (button->priv->bookmarks_manager, file);
1999 	  if (!label)
2000 	    label = _gtk_file_chooser_label_for_file (file);
2001 
2002           icon_theme = get_icon_theme (GTK_WIDGET (button));
2003 	  surface = gtk_icon_theme_load_surface (icon_theme, "folder-remote",
2004 						 button->priv->icon_size,
2005 						 gtk_widget_get_scale_factor (GTK_WIDGET (button)),
2006 						 gtk_widget_get_window (GTK_WIDGET (button)),
2007 						 0, NULL);
2008 
2009 	  gtk_list_store_insert (store, &iter, pos);
2010 	  gtk_list_store_set (store, &iter,
2011 			      ICON_COLUMN, surface,
2012 			      DISPLAY_NAME_COLUMN, label,
2013 			      TYPE_COLUMN, ROW_TYPE_BOOKMARK,
2014 			      DATA_COLUMN, g_object_ref (file),
2015 			      IS_FOLDER_COLUMN, TRUE,
2016 			      -1);
2017 
2018 	  g_free (label);
2019 	  if (surface)
2020 	    cairo_surface_destroy (surface);
2021 	}
2022 
2023       button->priv->n_bookmarks++;
2024       pos++;
2025     }
2026 
2027   if (button->priv->n_bookmarks > 0 &&
2028       !button->priv->has_bookmark_separator)
2029     {
2030       pos = model_get_type_position (button, ROW_TYPE_BOOKMARK_SEPARATOR);
2031 
2032       gtk_list_store_insert (store, &iter, pos);
2033       gtk_list_store_set (store, &iter,
2034 			  ICON_COLUMN, NULL,
2035 			  DISPLAY_NAME_COLUMN, NULL,
2036 			  TYPE_COLUMN, ROW_TYPE_BOOKMARK_SEPARATOR,
2037 			  DATA_COLUMN, NULL,
2038 			  IS_FOLDER_COLUMN, FALSE,
2039 			  -1);
2040       button->priv->has_bookmark_separator = TRUE;
2041     }
2042 }
2043 
2044 static void
model_update_current_folder(GtkFileChooserButton * button,GFile * file)2045 model_update_current_folder (GtkFileChooserButton *button,
2046 			     GFile                *file)
2047 {
2048   GtkListStore *store;
2049   GtkTreeIter iter;
2050   gint pos;
2051 
2052   if (!file)
2053     return;
2054 
2055   store = GTK_LIST_STORE (button->priv->model);
2056 
2057   if (!button->priv->has_current_folder_separator)
2058     {
2059       pos = model_get_type_position (button, ROW_TYPE_CURRENT_FOLDER_SEPARATOR);
2060       gtk_list_store_insert (store, &iter, pos);
2061       gtk_list_store_set (store, &iter,
2062 			  ICON_COLUMN, NULL,
2063 			  DISPLAY_NAME_COLUMN, NULL,
2064 			  TYPE_COLUMN, ROW_TYPE_CURRENT_FOLDER_SEPARATOR,
2065 			  DATA_COLUMN, NULL,
2066 			  IS_FOLDER_COLUMN, FALSE,
2067 			  -1);
2068       button->priv->has_current_folder_separator = TRUE;
2069     }
2070 
2071   pos = model_get_type_position (button, ROW_TYPE_CURRENT_FOLDER);
2072   if (!button->priv->has_current_folder)
2073     {
2074       gtk_list_store_insert (store, &iter, pos);
2075       button->priv->has_current_folder = TRUE;
2076     }
2077   else
2078     {
2079       gtk_tree_model_iter_nth_child (button->priv->model, &iter, NULL, pos);
2080       model_free_row_data (button, &iter);
2081     }
2082 
2083   if (g_file_is_native (file))
2084     {
2085       gtk_list_store_set (store, &iter,
2086 			  ICON_COLUMN, NULL,
2087 			  DISPLAY_NAME_COLUMN, _(FALLBACK_DISPLAY_NAME),
2088 			  TYPE_COLUMN, ROW_TYPE_CURRENT_FOLDER,
2089 			  DATA_COLUMN, g_object_ref (file),
2090 			  IS_FOLDER_COLUMN, FALSE,
2091 			  -1);
2092       set_info_for_file_at_iter (button, file, &iter);
2093     }
2094   else
2095     {
2096       gchar *label;
2097       GtkIconTheme *icon_theme;
2098       cairo_surface_t *surface;
2099 
2100       /* Don't call get_info for remote paths to avoid latency and
2101        * auth dialogs.
2102        * If we switch to a better bookmarks file format (XBEL), we
2103        * should use mime info to get a better icon.
2104        */
2105       label = _gtk_bookmarks_manager_get_bookmark_label (button->priv->bookmarks_manager, file);
2106       if (!label)
2107 	label = _gtk_file_chooser_label_for_file (file);
2108 
2109       icon_theme = get_icon_theme (GTK_WIDGET (button));
2110 
2111       if (g_file_is_native (file))
2112 	  surface = gtk_icon_theme_load_surface (icon_theme, "folder",
2113 						 button->priv->icon_size,
2114 						 gtk_widget_get_scale_factor (GTK_WIDGET (button)),
2115 						 gtk_widget_get_window (GTK_WIDGET (button)),
2116 						 0, NULL);
2117       else
2118 	  surface = gtk_icon_theme_load_surface (icon_theme, "folder-remote",
2119 						 button->priv->icon_size,
2120 						 gtk_widget_get_scale_factor (GTK_WIDGET (button)),
2121 						 gtk_widget_get_window (GTK_WIDGET (button)),
2122 						 0, NULL);
2123 
2124       gtk_list_store_set (store, &iter,
2125 			  ICON_COLUMN, surface,
2126 			  DISPLAY_NAME_COLUMN, label,
2127 			  TYPE_COLUMN, ROW_TYPE_CURRENT_FOLDER,
2128 			  DATA_COLUMN, g_object_ref (file),
2129 			  IS_FOLDER_COLUMN, TRUE,
2130 			  -1);
2131 
2132       g_free (label);
2133       if (surface)
2134 	cairo_surface_destroy (surface);
2135     }
2136 }
2137 
2138 static void
model_add_other(GtkFileChooserButton * button)2139 model_add_other (GtkFileChooserButton *button)
2140 {
2141   GtkListStore *store;
2142   GtkTreeIter iter;
2143   gint pos;
2144 
2145   store = GTK_LIST_STORE (button->priv->model);
2146   pos = model_get_type_position (button, ROW_TYPE_OTHER_SEPARATOR);
2147 
2148   gtk_list_store_insert (store, &iter, pos);
2149   gtk_list_store_set (store, &iter,
2150 		      ICON_COLUMN, NULL,
2151 		      DISPLAY_NAME_COLUMN, NULL,
2152 		      TYPE_COLUMN, ROW_TYPE_OTHER_SEPARATOR,
2153 		      DATA_COLUMN, NULL,
2154 		      IS_FOLDER_COLUMN, FALSE,
2155 		      -1);
2156   button->priv->has_other_separator = TRUE;
2157   pos++;
2158 
2159   gtk_list_store_insert (store, &iter, pos);
2160   gtk_list_store_set (store, &iter,
2161 		      ICON_COLUMN, NULL,
2162 		      DISPLAY_NAME_COLUMN, _("Other…"),
2163 		      TYPE_COLUMN, ROW_TYPE_OTHER,
2164 		      DATA_COLUMN, NULL,
2165 		      IS_FOLDER_COLUMN, FALSE,
2166 		      -1);
2167 }
2168 
2169 static void
model_add_empty_selection(GtkFileChooserButton * button)2170 model_add_empty_selection (GtkFileChooserButton *button)
2171 {
2172   GtkListStore *store;
2173   GtkTreeIter iter;
2174   gint pos;
2175 
2176   store = GTK_LIST_STORE (button->priv->model);
2177   pos = model_get_type_position (button, ROW_TYPE_EMPTY_SELECTION);
2178 
2179   gtk_list_store_insert (store, &iter, pos);
2180   gtk_list_store_set (store, &iter,
2181 		      ICON_COLUMN, NULL,
2182 		      DISPLAY_NAME_COLUMN, _(FALLBACK_DISPLAY_NAME),
2183 		      TYPE_COLUMN, ROW_TYPE_EMPTY_SELECTION,
2184 		      DATA_COLUMN, NULL,
2185 		      IS_FOLDER_COLUMN, FALSE,
2186 		      -1);
2187 }
2188 
2189 static void
model_remove_rows(GtkFileChooserButton * button,gint pos,gint n_rows)2190 model_remove_rows (GtkFileChooserButton *button,
2191 		   gint                  pos,
2192 		   gint                  n_rows)
2193 {
2194   GtkListStore *store;
2195 
2196   if (!n_rows)
2197     return;
2198 
2199   store = GTK_LIST_STORE (button->priv->model);
2200 
2201   do
2202     {
2203       GtkTreeIter iter;
2204 
2205       if (!gtk_tree_model_iter_nth_child (button->priv->model, &iter, NULL, pos))
2206 	g_assert_not_reached ();
2207 
2208       model_free_row_data (button, &iter);
2209       gtk_list_store_remove (store, &iter);
2210       n_rows--;
2211     }
2212   while (n_rows);
2213 }
2214 
2215 /* Filter Model */
2216 static gboolean
test_if_file_is_visible(GtkFileSystem * fs,GFile * file,gboolean local_only,gboolean is_folder)2217 test_if_file_is_visible (GtkFileSystem *fs,
2218 			 GFile         *file,
2219 			 gboolean       local_only,
2220 			 gboolean       is_folder)
2221 {
2222   if (!file)
2223     return FALSE;
2224 
2225   if (local_only && !_gtk_file_has_native_path (file))
2226     return FALSE;
2227 
2228   if (!is_folder)
2229     return FALSE;
2230 
2231   return TRUE;
2232 }
2233 
2234 static gboolean
filter_model_visible_func(GtkTreeModel * model,GtkTreeIter * iter,gpointer user_data)2235 filter_model_visible_func (GtkTreeModel *model,
2236 			   GtkTreeIter  *iter,
2237 			   gpointer      user_data)
2238 {
2239   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data);
2240   GtkFileChooserButtonPrivate *priv = button->priv;
2241   gchar type;
2242   gpointer data;
2243   gboolean local_only, retval, is_folder;
2244 
2245   type = ROW_TYPE_INVALID;
2246   data = NULL;
2247   local_only = gtk_file_chooser_get_local_only (GTK_FILE_CHOOSER (priv->chooser));
2248 
2249   gtk_tree_model_get (model, iter,
2250 		      TYPE_COLUMN, &type,
2251 		      DATA_COLUMN, &data,
2252 		      IS_FOLDER_COLUMN, &is_folder,
2253 		      -1);
2254 
2255   switch (type)
2256     {
2257     case ROW_TYPE_CURRENT_FOLDER:
2258       retval = TRUE;
2259       break;
2260     case ROW_TYPE_SPECIAL:
2261     case ROW_TYPE_SHORTCUT:
2262     case ROW_TYPE_BOOKMARK:
2263       retval = test_if_file_is_visible (priv->fs, data, local_only, is_folder);
2264       break;
2265     case ROW_TYPE_VOLUME:
2266       {
2267 	retval = TRUE;
2268 	if (local_only)
2269 	  {
2270 	    if (_gtk_file_system_volume_is_mounted (data))
2271 	      {
2272 		GFile *base_file;
2273 
2274 		base_file = _gtk_file_system_volume_get_root (data);
2275 
2276 		if (base_file)
2277 		  {
2278 		    if (!_gtk_file_has_native_path (base_file))
2279 		      retval = FALSE;
2280                     g_object_unref (base_file);
2281 		  }
2282 		else
2283 		  retval = FALSE;
2284 	      }
2285 	  }
2286       }
2287       break;
2288     case ROW_TYPE_EMPTY_SELECTION:
2289       {
2290 	gboolean popup_shown;
2291 
2292 	g_object_get (priv->combo_box,
2293 		      "popup-shown", &popup_shown,
2294 		      NULL);
2295 
2296 	if (popup_shown)
2297 	  retval = FALSE;
2298 	else
2299 	  {
2300 	    GFile *selected;
2301 
2302 	    /* When the combo box is not popped up... */
2303 
2304 	    selected = get_selected_file (button);
2305 	    if (selected)
2306 	      retval = FALSE; /* ... nonempty selection means the ROW_TYPE_EMPTY_SELECTION is *not* visible... */
2307 	    else
2308 	      retval = TRUE;  /* ... and empty selection means the ROW_TYPE_EMPTY_SELECTION *is* visible */
2309 
2310 	    if (selected)
2311 	      g_object_unref (selected);
2312 	  }
2313 
2314 	break;
2315       }
2316     default:
2317       retval = TRUE;
2318       break;
2319     }
2320 
2321   return retval;
2322 }
2323 
2324 /* Combo Box */
2325 static void
name_cell_data_func(GtkCellLayout * layout,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,gpointer user_data)2326 name_cell_data_func (GtkCellLayout   *layout,
2327 		     GtkCellRenderer *cell,
2328 		     GtkTreeModel    *model,
2329 		     GtkTreeIter     *iter,
2330 		     gpointer         user_data)
2331 {
2332   gchar type;
2333 
2334   type = 0;
2335   gtk_tree_model_get (model, iter,
2336 		      TYPE_COLUMN, &type,
2337 		      -1);
2338 
2339   if (type == ROW_TYPE_CURRENT_FOLDER)
2340     g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
2341   else if (type == ROW_TYPE_BOOKMARK || type == ROW_TYPE_SHORTCUT)
2342     g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_MIDDLE, NULL);
2343   else
2344     g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_NONE, NULL);
2345 }
2346 
2347 static gboolean
combo_box_row_separator_func(GtkTreeModel * model,GtkTreeIter * iter,gpointer user_data)2348 combo_box_row_separator_func (GtkTreeModel *model,
2349 			      GtkTreeIter  *iter,
2350 			      gpointer      user_data)
2351 {
2352   gchar type = ROW_TYPE_INVALID;
2353 
2354   gtk_tree_model_get (model, iter, TYPE_COLUMN, &type, -1);
2355 
2356   return (type == ROW_TYPE_BOOKMARK_SEPARATOR ||
2357 	  type == ROW_TYPE_CURRENT_FOLDER_SEPARATOR ||
2358 	  type == ROW_TYPE_OTHER_SEPARATOR);
2359 }
2360 
2361 static void
select_combo_box_row_no_notify(GtkFileChooserButton * button,int pos)2362 select_combo_box_row_no_notify (GtkFileChooserButton *button, int pos)
2363 {
2364   GtkFileChooserButtonPrivate *priv = button->priv;
2365   GtkTreeIter iter, filter_iter;
2366 
2367   gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, pos);
2368   gtk_tree_model_filter_convert_child_iter_to_iter (GTK_TREE_MODEL_FILTER (priv->filter_model),
2369 						    &filter_iter, &iter);
2370 
2371   g_signal_handlers_block_by_func (priv->combo_box, combo_box_changed_cb, button);
2372   gtk_combo_box_set_active_iter (GTK_COMBO_BOX (priv->combo_box), &filter_iter);
2373   g_signal_handlers_unblock_by_func (priv->combo_box, combo_box_changed_cb, button);
2374 }
2375 
2376 static void
update_combo_box(GtkFileChooserButton * button)2377 update_combo_box (GtkFileChooserButton *button)
2378 {
2379   GtkFileChooserButtonPrivate *priv = button->priv;
2380   GFile *file;
2381   GtkTreeIter iter;
2382   gboolean row_found;
2383 
2384   file = get_selected_file (button);
2385 
2386   row_found = FALSE;
2387 
2388   gtk_tree_model_get_iter_first (priv->filter_model, &iter);
2389 
2390   do
2391     {
2392       gchar type;
2393       gpointer data;
2394 
2395       type = ROW_TYPE_INVALID;
2396       data = NULL;
2397 
2398       gtk_tree_model_get (priv->filter_model, &iter,
2399 			  TYPE_COLUMN, &type,
2400 			  DATA_COLUMN, &data,
2401 			  -1);
2402 
2403       switch (type)
2404 	{
2405 	case ROW_TYPE_SPECIAL:
2406 	case ROW_TYPE_SHORTCUT:
2407 	case ROW_TYPE_BOOKMARK:
2408 	case ROW_TYPE_CURRENT_FOLDER:
2409 	  row_found = (file && g_file_equal (data, file));
2410 	  break;
2411 	case ROW_TYPE_VOLUME:
2412 	  {
2413 	    GFile *base_file;
2414 
2415 	    base_file = _gtk_file_system_volume_get_root (data);
2416             if (base_file)
2417               {
2418 	        row_found = (file && g_file_equal (base_file, file));
2419 		g_object_unref (base_file);
2420               }
2421 	  }
2422 	  break;
2423 	default:
2424 	  row_found = FALSE;
2425 	  break;
2426 	}
2427 
2428       if (row_found)
2429 	{
2430 	  g_signal_handlers_block_by_func (priv->combo_box, combo_box_changed_cb, button);
2431 	  gtk_combo_box_set_active_iter (GTK_COMBO_BOX (priv->combo_box),
2432 					 &iter);
2433 	  g_signal_handlers_unblock_by_func (priv->combo_box, combo_box_changed_cb, button);
2434 	}
2435     }
2436   while (!row_found && gtk_tree_model_iter_next (priv->filter_model, &iter));
2437 
2438   if (!row_found)
2439     {
2440       gint pos;
2441 
2442       /* If it hasn't been found already, update & select the current-folder row. */
2443       if (file)
2444 	{
2445 	  model_update_current_folder (button, file);
2446 	  pos = model_get_type_position (button, ROW_TYPE_CURRENT_FOLDER);
2447 	}
2448       else
2449 	{
2450 	  /* No selection; switch to that row */
2451 
2452 	  pos = model_get_type_position (button, ROW_TYPE_EMPTY_SELECTION);
2453 	}
2454 
2455       gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
2456 
2457       select_combo_box_row_no_notify (button, pos);
2458     }
2459 
2460   if (file)
2461     g_object_unref (file);
2462 }
2463 
2464 /* Button */
2465 static void
update_label_get_info_cb(GCancellable * cancellable,GFileInfo * info,const GError * error,gpointer data)2466 update_label_get_info_cb (GCancellable *cancellable,
2467 			  GFileInfo    *info,
2468 			  const GError *error,
2469 			  gpointer      data)
2470 {
2471   gboolean cancelled = g_cancellable_is_cancelled (cancellable);
2472   cairo_surface_t *surface;
2473   GtkFileChooserButton *button = data;
2474   GtkFileChooserButtonPrivate *priv = button->priv;
2475 
2476   if (cancellable != priv->update_button_cancellable)
2477     goto out;
2478 
2479   priv->update_button_cancellable = NULL;
2480 
2481   if (cancelled || error)
2482     goto out;
2483 
2484   gtk_label_set_text (GTK_LABEL (priv->label), g_file_info_get_display_name (info));
2485 
2486   surface = _gtk_file_info_render_icon (info, GTK_WIDGET (priv->image), priv->icon_size);
2487   gtk_image_set_from_surface (GTK_IMAGE (priv->image), surface);
2488   if (surface)
2489     cairo_surface_destroy (surface);
2490 
2491 out:
2492   emit_selection_changed_if_changing_selection (button);
2493 
2494   g_object_unref (button);
2495   g_object_unref (cancellable);
2496 }
2497 
2498 static void
update_label_and_image(GtkFileChooserButton * button)2499 update_label_and_image (GtkFileChooserButton *button)
2500 {
2501   GtkFileChooserButtonPrivate *priv = button->priv;
2502   gchar *label_text;
2503   GFile *file;
2504   gboolean done_changing_selection;
2505 
2506   file = get_selected_file (button);
2507 
2508   label_text = NULL;
2509   done_changing_selection = FALSE;
2510 
2511   if (priv->update_button_cancellable)
2512     {
2513       g_cancellable_cancel (priv->update_button_cancellable);
2514       priv->update_button_cancellable = NULL;
2515     }
2516 
2517   if (file)
2518     {
2519       GtkFileSystemVolume *volume = NULL;
2520 
2521       volume = _gtk_file_system_get_volume_for_file (priv->fs, file);
2522       if (volume)
2523         {
2524           GFile *base_file;
2525 
2526           base_file = _gtk_file_system_volume_get_root (volume);
2527           if (base_file && g_file_equal (base_file, file))
2528             {
2529               cairo_surface_t *surface;
2530 
2531               label_text = _gtk_file_system_volume_get_display_name (volume);
2532               surface = _gtk_file_system_volume_render_icon (volume,
2533 							     GTK_WIDGET (button),
2534 							     priv->icon_size,
2535 							     NULL);
2536               gtk_image_set_from_surface (GTK_IMAGE (priv->image), surface);
2537               if (surface)
2538                 cairo_surface_destroy (surface);
2539             }
2540 
2541           if (base_file)
2542             g_object_unref (base_file);
2543 
2544           _gtk_file_system_volume_unref (volume);
2545 
2546           if (label_text)
2547 	    {
2548 	      done_changing_selection = TRUE;
2549 	      goto out;
2550 	    }
2551         }
2552 
2553       if (g_file_is_native (file) ||
2554           !_gtk_bookmarks_manager_has_bookmark (button->priv->bookmarks_manager, file))
2555         {
2556           priv->update_button_cancellable =
2557             _gtk_file_system_get_info (priv->fs, file,
2558                                        "standard::icon,standard::display-name",
2559                                        update_label_get_info_cb,
2560                                        g_object_ref (button));
2561         }
2562       else
2563         {
2564           cairo_surface_t *surface;
2565 
2566           label_text = _gtk_bookmarks_manager_get_bookmark_label (button->priv->bookmarks_manager, file);
2567           surface = gtk_icon_theme_load_surface (get_icon_theme (GTK_WIDGET (priv->image)),
2568 						 "text-x-generic",
2569 						 priv->icon_size,
2570 						 gtk_widget_get_scale_factor (GTK_WIDGET (button)),
2571 						 gtk_widget_get_window (GTK_WIDGET (button)),
2572 						 0, NULL);
2573           gtk_image_set_from_surface (GTK_IMAGE (priv->image), surface);
2574           if (surface)
2575             cairo_surface_destroy (surface);
2576 
2577 	  done_changing_selection = TRUE;
2578         }
2579     }
2580   else
2581     {
2582       /* We know the selection is empty */
2583       done_changing_selection = TRUE;
2584     }
2585 
2586 out:
2587 
2588   if (file)
2589     g_object_unref (file);
2590 
2591   if (label_text)
2592     {
2593       gtk_label_set_text (GTK_LABEL (priv->label), label_text);
2594       g_free (label_text);
2595     }
2596   else
2597     {
2598       gtk_label_set_text (GTK_LABEL (priv->label), _(FALLBACK_DISPLAY_NAME));
2599       gtk_image_set_from_surface (GTK_IMAGE (priv->image), NULL);
2600     }
2601 
2602   if (done_changing_selection)
2603     emit_selection_changed_if_changing_selection (button);
2604 }
2605 
2606 
2607 /* ************************ *
2608  *  Child Object Callbacks  *
2609  * ************************ */
2610 
2611 /* File System */
2612 static void
fs_volumes_changed_cb(GtkFileSystem * fs,gpointer user_data)2613 fs_volumes_changed_cb (GtkFileSystem *fs,
2614 		       gpointer       user_data)
2615 {
2616   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data);
2617   GtkFileChooserButtonPrivate *priv = button->priv;
2618   GSList *volumes;
2619 
2620   model_remove_rows (user_data,
2621 		     model_get_type_position (user_data, ROW_TYPE_VOLUME),
2622 		     priv->n_volumes);
2623 
2624   priv->n_volumes = 0;
2625 
2626   volumes = _gtk_file_system_list_volumes (fs);
2627   model_add_volumes (user_data, volumes);
2628   g_slist_free (volumes);
2629 
2630   gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
2631 
2632   update_label_and_image (user_data);
2633   update_combo_box (user_data);
2634 }
2635 
2636 static void
bookmarks_changed_cb(gpointer user_data)2637 bookmarks_changed_cb (gpointer user_data)
2638 {
2639   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data);
2640   GtkFileChooserButtonPrivate *priv = button->priv;
2641   GSList *bookmarks;
2642 
2643   bookmarks = _gtk_bookmarks_manager_list_bookmarks (priv->bookmarks_manager);
2644   model_remove_rows (user_data,
2645 		     model_get_type_position (user_data, ROW_TYPE_BOOKMARK_SEPARATOR),
2646 		     priv->n_bookmarks + priv->has_bookmark_separator);
2647   priv->has_bookmark_separator = FALSE;
2648   priv->n_bookmarks = 0;
2649   model_add_bookmarks (user_data, bookmarks);
2650   g_slist_free_full (bookmarks, g_object_unref);
2651 
2652   gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
2653 
2654   update_label_and_image (user_data);
2655   update_combo_box (user_data);
2656 }
2657 
2658 static void
save_inactive_state(GtkFileChooserButton * button)2659 save_inactive_state (GtkFileChooserButton *button)
2660 {
2661   GtkFileChooserButtonPrivate *priv = button->priv;
2662 
2663   if (priv->current_folder_while_inactive)
2664     g_object_unref (priv->current_folder_while_inactive);
2665 
2666   if (priv->selection_while_inactive)
2667     g_object_unref (priv->selection_while_inactive);
2668 
2669   priv->current_folder_while_inactive = gtk_file_chooser_get_current_folder_file (GTK_FILE_CHOOSER (priv->chooser));
2670   priv->selection_while_inactive = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (priv->chooser));
2671 }
2672 
2673 static void
restore_inactive_state(GtkFileChooserButton * button)2674 restore_inactive_state (GtkFileChooserButton *button)
2675 {
2676   GtkFileChooserButtonPrivate *priv = button->priv;
2677 
2678   if (priv->current_folder_while_inactive)
2679     gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (priv->chooser), priv->current_folder_while_inactive, NULL);
2680 
2681   if (priv->selection_while_inactive)
2682     gtk_file_chooser_select_file (GTK_FILE_CHOOSER (priv->chooser), priv->selection_while_inactive, NULL);
2683   else
2684     gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->chooser));
2685 }
2686 
2687 /* Dialog */
2688 static void
open_dialog(GtkFileChooserButton * button)2689 open_dialog (GtkFileChooserButton *button)
2690 {
2691   GtkFileChooserButtonPrivate *priv = button->priv;
2692   GtkWidget *toplevel;
2693 
2694   toplevel = gtk_widget_get_toplevel (GTK_WIDGET (button));
2695 
2696   /* Setup the dialog parent to be chooser button's toplevel, and be modal
2697      as needed. */
2698   if (priv->dialog != NULL)
2699     {
2700       if (!gtk_widget_get_visible (priv->dialog))
2701         {
2702           if (gtk_widget_is_toplevel (toplevel) && GTK_IS_WINDOW (toplevel))
2703             {
2704               if (GTK_WINDOW (toplevel) != gtk_window_get_transient_for (GTK_WINDOW (priv->dialog)))
2705                 gtk_window_set_transient_for (GTK_WINDOW (priv->dialog),
2706                                               GTK_WINDOW (toplevel));
2707 
2708               gtk_window_set_modal (GTK_WINDOW (priv->dialog),
2709                                     gtk_window_get_modal (GTK_WINDOW (toplevel)));
2710             }
2711         }
2712     }
2713   else
2714     {
2715       if (!gtk_native_dialog_get_visible (GTK_NATIVE_DIALOG (priv->native)))
2716         {
2717           if (gtk_widget_is_toplevel (toplevel) && GTK_IS_WINDOW (toplevel))
2718             {
2719               if (GTK_WINDOW (toplevel) != gtk_native_dialog_get_transient_for (GTK_NATIVE_DIALOG (priv->native)))
2720                 gtk_native_dialog_set_transient_for (GTK_NATIVE_DIALOG (priv->native),
2721                                                      GTK_WINDOW (toplevel));
2722 
2723               gtk_native_dialog_set_modal (GTK_NATIVE_DIALOG (priv->native),
2724                                            gtk_window_get_modal (GTK_WINDOW (toplevel)));
2725             }
2726         }
2727     }
2728 
2729   if (!priv->active)
2730     {
2731       restore_inactive_state (button);
2732       priv->active = TRUE;
2733 
2734       /* Only handle update-preview handler if it is handled on the button */
2735       if (g_signal_has_handler_pending (button,
2736                                         g_signal_lookup ("update-preview", GTK_TYPE_FILE_CHOOSER),
2737                                         0, TRUE))
2738         {
2739           g_signal_connect (priv->chooser, "update-preview",
2740                             G_CALLBACK (chooser_update_preview_cb), button);
2741         }
2742     }
2743 
2744   gtk_widget_set_sensitive (priv->combo_box, FALSE);
2745   if (priv->dialog)
2746     {
2747       G_GNUC_BEGIN_IGNORE_DEPRECATIONS
2748       gtk_window_present (GTK_WINDOW (priv->dialog));
2749       G_GNUC_END_IGNORE_DEPRECATIONS
2750     }
2751   else
2752     gtk_native_dialog_show (GTK_NATIVE_DIALOG (priv->native));
2753 }
2754 
2755 /* Combo Box */
2756 static void
combo_box_changed_cb(GtkComboBox * combo_box,gpointer user_data)2757 combo_box_changed_cb (GtkComboBox *combo_box,
2758 		      gpointer     user_data)
2759 {
2760   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data);
2761   GtkFileChooserButtonPrivate *priv = button->priv;
2762   GtkTreeIter iter;
2763   gboolean file_was_set;
2764 
2765   file_was_set = FALSE;
2766 
2767   if (gtk_combo_box_get_active_iter (combo_box, &iter))
2768     {
2769       gchar type;
2770       gpointer data;
2771 
2772       type = ROW_TYPE_INVALID;
2773       data = NULL;
2774 
2775       gtk_tree_model_get (priv->filter_model, &iter,
2776 			  TYPE_COLUMN, &type,
2777 			  DATA_COLUMN, &data,
2778 			  -1);
2779 
2780       switch (type)
2781 	{
2782 	case ROW_TYPE_SPECIAL:
2783 	case ROW_TYPE_SHORTCUT:
2784 	case ROW_TYPE_BOOKMARK:
2785 	case ROW_TYPE_CURRENT_FOLDER:
2786 	  if (data)
2787 	    {
2788 	      gtk_file_chooser_button_select_file (GTK_FILE_CHOOSER (button), data, NULL);
2789 	      file_was_set = TRUE;
2790 	    }
2791 	  break;
2792 	case ROW_TYPE_VOLUME:
2793 	  {
2794 	    GFile *base_file;
2795 
2796 	    base_file = _gtk_file_system_volume_get_root (data);
2797 	    if (base_file)
2798 	      {
2799 		gtk_file_chooser_button_select_file (GTK_FILE_CHOOSER (button), base_file, NULL);
2800 		file_was_set = TRUE;
2801 		g_object_unref (base_file);
2802 	      }
2803 	  }
2804 	  break;
2805 	case ROW_TYPE_OTHER:
2806 	  open_dialog (user_data);
2807 	  break;
2808 	default:
2809 	  break;
2810 	}
2811     }
2812 
2813   if (file_was_set)
2814     g_signal_emit (button, file_chooser_button_signals[FILE_SET], 0);
2815 }
2816 
2817 /* Calback for the "notify::popup-shown" signal on the combo box.
2818  * When the combo is popped up, we don’t want the ROW_TYPE_EMPTY_SELECTION to be visible
2819  * at all; otherwise we would be showing a “(None)” item in the combo box’s popup.
2820  *
2821  * However, when the combo box is *not* popped up, we want the empty-selection row
2822  * to be visible depending on the selection.
2823  *
2824  * Since all that is done through the filter_model_visible_func(), this means
2825  * that we need to refilter the model when the combo box pops up - hence the
2826  * present signal handler.
2827  */
2828 static void
combo_box_notify_popup_shown_cb(GObject * object,GParamSpec * pspec,gpointer user_data)2829 combo_box_notify_popup_shown_cb (GObject    *object,
2830 				 GParamSpec *pspec,
2831 				 gpointer    user_data)
2832 {
2833   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data);
2834   GtkFileChooserButtonPrivate *priv = button->priv;
2835   gboolean popup_shown;
2836 
2837   g_object_get (priv->combo_box,
2838 		"popup-shown", &popup_shown,
2839 		NULL);
2840 
2841   /* Indicate that the ROW_TYPE_EMPTY_SELECTION will change visibility... */
2842   gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
2843 
2844   /* If the combo box popup got dismissed, go back to showing the ROW_TYPE_EMPTY_SELECTION if needed */
2845   if (!popup_shown)
2846 
2847     {
2848       GFile *selected = get_selected_file (button);
2849 
2850       if (!selected)
2851 	{
2852 	  int pos;
2853 
2854 	  pos = model_get_type_position (button, ROW_TYPE_EMPTY_SELECTION);
2855 	  select_combo_box_row_no_notify (button, pos);
2856 	}
2857       else
2858 	g_object_unref (selected);
2859     }
2860 }
2861 
2862 /* Button */
2863 static void
button_clicked_cb(GtkButton * real_button,gpointer user_data)2864 button_clicked_cb (GtkButton *real_button,
2865 		   gpointer   user_data)
2866 {
2867   open_dialog (user_data);
2868 }
2869 
2870 /* Dialog */
2871 
2872 static void
chooser_update_preview_cb(GtkFileChooser * dialog,gpointer user_data)2873 chooser_update_preview_cb (GtkFileChooser *dialog,
2874                            gpointer        user_data)
2875 {
2876   g_signal_emit_by_name (user_data, "update-preview");
2877 }
2878 
2879 static void
chooser_notify_cb(GObject * dialog,GParamSpec * pspec,gpointer user_data)2880 chooser_notify_cb (GObject    *dialog,
2881                    GParamSpec *pspec,
2882                    gpointer    user_data)
2883 {
2884   gpointer iface;
2885 
2886   iface = g_type_interface_peek (g_type_class_peek (G_OBJECT_TYPE (dialog)),
2887 				 GTK_TYPE_FILE_CHOOSER);
2888   if (g_object_interface_find_property (iface, pspec->name))
2889     g_object_notify (user_data, pspec->name);
2890 
2891   if (g_ascii_strcasecmp (pspec->name, "local-only") == 0)
2892     {
2893       GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data);
2894       GtkFileChooserButtonPrivate *priv = button->priv;
2895 
2896       if (priv->has_current_folder)
2897 	{
2898 	  GtkTreeIter iter;
2899 	  gint pos;
2900 	  gpointer data;
2901 
2902 	  pos = model_get_type_position (user_data,
2903 					 ROW_TYPE_CURRENT_FOLDER);
2904 	  gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, pos);
2905 
2906 	  data = NULL;
2907 	  gtk_tree_model_get (priv->model, &iter, DATA_COLUMN, &data, -1);
2908 
2909 	  /* If the path isn't local but we're in local-only mode now, remove
2910 	   * the custom-folder row */
2911 	  if (data && _gtk_file_has_native_path (G_FILE (data)) &&
2912 	      gtk_file_chooser_get_local_only (GTK_FILE_CHOOSER (priv->chooser)))
2913 	    {
2914 	      pos--;
2915 	      model_remove_rows (user_data, pos, 2);
2916 	    }
2917 	}
2918 
2919       gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
2920       update_combo_box (user_data);
2921     }
2922 }
2923 
2924 static gboolean
dialog_delete_event_cb(GtkWidget * dialog,GdkEvent * event,gpointer user_data)2925 dialog_delete_event_cb (GtkWidget *dialog,
2926 			GdkEvent  *event,
2927 		        gpointer   user_data)
2928 {
2929   g_signal_emit_by_name (dialog, "response", GTK_RESPONSE_DELETE_EVENT);
2930 
2931   return TRUE;
2932 }
2933 
2934 static void
common_response_cb(GtkFileChooserButton * button,gint response)2935 common_response_cb (GtkFileChooserButton *button,
2936 		    gint       response)
2937 {
2938   GtkFileChooserButtonPrivate *priv = button->priv;
2939 
2940   if (response == GTK_RESPONSE_ACCEPT ||
2941       response == GTK_RESPONSE_OK)
2942     {
2943       save_inactive_state (button);
2944 
2945       g_signal_emit_by_name (button, "current-folder-changed");
2946       g_signal_emit_by_name (button, "selection-changed");
2947     }
2948   else
2949     {
2950       restore_inactive_state (button);
2951     }
2952 
2953   if (priv->active)
2954     {
2955       priv->active = FALSE;
2956 
2957       g_signal_handlers_disconnect_by_func (priv->chooser, chooser_update_preview_cb, button);
2958     }
2959 
2960   update_label_and_image (button);
2961   update_combo_box (button);
2962 
2963   gtk_widget_set_sensitive (priv->combo_box, TRUE);
2964 }
2965 
2966 
2967 static void
dialog_response_cb(GtkDialog * dialog,gint response,gpointer user_data)2968 dialog_response_cb (GtkDialog *dialog,
2969 		    gint       response,
2970 		    gpointer   user_data)
2971 {
2972   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data);
2973   GtkFileChooserButtonPrivate *priv = button->priv;
2974 
2975   common_response_cb (button, response);
2976 
2977   gtk_widget_hide (priv->dialog);
2978 
2979   if (response == GTK_RESPONSE_ACCEPT ||
2980       response == GTK_RESPONSE_OK)
2981     g_signal_emit (button, file_chooser_button_signals[FILE_SET], 0);
2982 }
2983 
2984 static void
native_response_cb(GtkFileChooserNative * native,gint response,gpointer user_data)2985 native_response_cb (GtkFileChooserNative *native,
2986 		    gint       response,
2987 		    gpointer   user_data)
2988 {
2989   GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data);
2990 
2991   common_response_cb (button, response);
2992 
2993   /* dialog already hidden */
2994 
2995   if (response == GTK_RESPONSE_ACCEPT ||
2996       response == GTK_RESPONSE_OK)
2997     g_signal_emit (button, file_chooser_button_signals[FILE_SET], 0);
2998 }
2999 
3000 
3001 /* ************************************************************************** *
3002  *  Public API                                                                *
3003  * ************************************************************************** */
3004 
3005 /**
3006  * gtk_file_chooser_button_new:
3007  * @title: the title of the browse dialog.
3008  * @action: the open mode for the widget.
3009  *
3010  * Creates a new file-selecting button widget.
3011  *
3012  * Returns: a new button widget.
3013  *
3014  * Since: 2.6
3015  */
3016 GtkWidget *
gtk_file_chooser_button_new(const gchar * title,GtkFileChooserAction action)3017 gtk_file_chooser_button_new (const gchar          *title,
3018 			     GtkFileChooserAction  action)
3019 {
3020   g_return_val_if_fail (action == GTK_FILE_CHOOSER_ACTION_OPEN ||
3021 			action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, NULL);
3022 
3023   return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON,
3024 		       "action", action,
3025 		       "title", (title ? title : _(DEFAULT_TITLE)),
3026 		       NULL);
3027 }
3028 
3029 /**
3030  * gtk_file_chooser_button_new_with_dialog:
3031  * @dialog: (type Gtk.Dialog): the widget to use as dialog
3032  *
3033  * Creates a #GtkFileChooserButton widget which uses @dialog as its
3034  * file-picking window.
3035  *
3036  * Note that @dialog must be a #GtkDialog (or subclass) which
3037  * implements the #GtkFileChooser interface and must not have
3038  * %GTK_DIALOG_DESTROY_WITH_PARENT set.
3039  *
3040  * Also note that the dialog needs to have its confirmative button
3041  * added with response %GTK_RESPONSE_ACCEPT or %GTK_RESPONSE_OK in
3042  * order for the button to take over the file selected in the dialog.
3043  *
3044  * Returns: a new button widget.
3045  *
3046  * Since: 2.6
3047  */
3048 GtkWidget *
gtk_file_chooser_button_new_with_dialog(GtkWidget * dialog)3049 gtk_file_chooser_button_new_with_dialog (GtkWidget *dialog)
3050 {
3051   g_return_val_if_fail (GTK_IS_FILE_CHOOSER (dialog) && GTK_IS_DIALOG (dialog), NULL);
3052 
3053   return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON,
3054 		       "dialog", dialog,
3055 		       NULL);
3056 }
3057 
3058 /**
3059  * gtk_file_chooser_button_set_title:
3060  * @button: the button widget to modify.
3061  * @title: the new browse dialog title.
3062  *
3063  * Modifies the @title of the browse dialog used by @button.
3064  *
3065  * Since: 2.6
3066  */
3067 void
gtk_file_chooser_button_set_title(GtkFileChooserButton * button,const gchar * title)3068 gtk_file_chooser_button_set_title (GtkFileChooserButton *button,
3069 				   const gchar          *title)
3070 {
3071   g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));
3072 
3073   if (button->priv->dialog)
3074     gtk_window_set_title (GTK_WINDOW (button->priv->dialog), title);
3075   else
3076     gtk_native_dialog_set_title (GTK_NATIVE_DIALOG (button->priv->native), title);
3077   g_object_notify (G_OBJECT (button), "title");
3078 }
3079 
3080 /**
3081  * gtk_file_chooser_button_get_title:
3082  * @button: the button widget to examine.
3083  *
3084  * Retrieves the title of the browse dialog used by @button. The returned value
3085  * should not be modified or freed.
3086  *
3087  * Returns: a pointer to the browse dialog’s title.
3088  *
3089  * Since: 2.6
3090  */
3091 const gchar *
gtk_file_chooser_button_get_title(GtkFileChooserButton * button)3092 gtk_file_chooser_button_get_title (GtkFileChooserButton *button)
3093 {
3094   g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), NULL);
3095 
3096   if (button->priv->dialog)
3097     return gtk_window_get_title (GTK_WINDOW (button->priv->dialog));
3098   else
3099     return gtk_native_dialog_get_title (GTK_NATIVE_DIALOG (button->priv->native));
3100 }
3101 
3102 /**
3103  * gtk_file_chooser_button_get_width_chars:
3104  * @button: the button widget to examine.
3105  *
3106  * Retrieves the width in characters of the @button widget’s entry and/or label.
3107  *
3108  * Returns: an integer width (in characters) that the button will use to size itself.
3109  *
3110  * Since: 2.6
3111  */
3112 gint
gtk_file_chooser_button_get_width_chars(GtkFileChooserButton * button)3113 gtk_file_chooser_button_get_width_chars (GtkFileChooserButton *button)
3114 {
3115   g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), -1);
3116 
3117   return gtk_label_get_width_chars (GTK_LABEL (button->priv->label));
3118 }
3119 
3120 /**
3121  * gtk_file_chooser_button_set_width_chars:
3122  * @button: the button widget to examine.
3123  * @n_chars: the new width, in characters.
3124  *
3125  * Sets the width (in characters) that @button will use to @n_chars.
3126  *
3127  * Since: 2.6
3128  */
3129 void
gtk_file_chooser_button_set_width_chars(GtkFileChooserButton * button,gint n_chars)3130 gtk_file_chooser_button_set_width_chars (GtkFileChooserButton *button,
3131 					 gint                  n_chars)
3132 {
3133   g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));
3134 
3135   gtk_label_set_width_chars (GTK_LABEL (button->priv->label), n_chars);
3136   g_object_notify (G_OBJECT (button), "width-chars");
3137 }
3138 
3139 /**
3140  * gtk_file_chooser_button_set_focus_on_click:
3141  * @button: a #GtkFileChooserButton
3142  * @focus_on_click: whether the button grabs focus when clicked with the mouse
3143  *
3144  * Sets whether the button will grab focus when it is clicked with the mouse.
3145  * Making mouse clicks not grab focus is useful in places like toolbars where
3146  * you don’t want the keyboard focus removed from the main area of the
3147  * application.
3148  *
3149  * Since: 2.10
3150  *
3151  * Deprecated: 3.20: Use gtk_widget_set_focus_on_click() instead
3152  */
3153 void
gtk_file_chooser_button_set_focus_on_click(GtkFileChooserButton * button,gboolean focus_on_click)3154 gtk_file_chooser_button_set_focus_on_click (GtkFileChooserButton *button,
3155 					    gboolean              focus_on_click)
3156 {
3157   g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));
3158 
3159   gtk_widget_set_focus_on_click (GTK_WIDGET (button), focus_on_click);
3160 }
3161 
3162 /**
3163  * gtk_file_chooser_button_get_focus_on_click:
3164  * @button: a #GtkFileChooserButton
3165  *
3166  * Returns whether the button grabs focus when it is clicked with the mouse.
3167  * See gtk_file_chooser_button_set_focus_on_click().
3168  *
3169  * Returns: %TRUE if the button grabs focus when it is clicked with
3170  *               the mouse.
3171  *
3172  * Since: 2.10
3173  *
3174  * Deprecated: 3.20: Use gtk_widget_get_focus_on_click() instead
3175  */
3176 gboolean
gtk_file_chooser_button_get_focus_on_click(GtkFileChooserButton * button)3177 gtk_file_chooser_button_get_focus_on_click (GtkFileChooserButton *button)
3178 {
3179   g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), FALSE);
3180 
3181   return gtk_widget_get_focus_on_click (GTK_WIDGET (button));
3182 }
3183