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