1 /* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
2 /* GTK - The GIMP Toolkit
3  * gtkfilechooserwidget.c: Embeddable file selector widget
4  * Copyright (C) 2003, Red Hat, Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "config.h"
21 
22 #include "gtkfilechooserwidget.h"
23 #include "gtkfilechooserwidgetprivate.h"
24 
25 #include "gtkaccessible.h"
26 #include "gtkbindings.h"
27 #include "gtkbutton.h"
28 #include "gtkcelllayout.h"
29 #include "gtkcellrendererpixbuf.h"
30 #include "gtkcellrenderertext.h"
31 #include "gtkcheckmenuitem.h"
32 #include "gtkclipboard.h"
33 #include "gtkcomboboxtext.h"
34 #include "gtkdragsource.h"
35 #include "gtkdragdest.h"
36 #include "gtkentry.h"
37 #include "gtkfilechooserprivate.h"
38 #include "gtkfilechooserdialog.h"
39 #include "gtkfilechooserembed.h"
40 #include "gtkfilechooserentry.h"
41 #include "gtkfilechooserutils.h"
42 #include "gtkfilechooser.h"
43 #include "gtkfilesystem.h"
44 #include "gtkfilesystemmodel.h"
45 #include "gtkgrid.h"
46 #include "gtkicontheme.h"
47 #include "gtklabel.h"
48 #include "gtkmarshalers.h"
49 #include "gtkmessagedialog.h"
50 #include "gtkmountoperation.h"
51 #include "gtkpaned.h"
52 #include "gtkpathbar.h"
53 #include "gtkplacessidebar.h"
54 #include "gtkplacessidebarprivate.h"
55 #include "gtkplacesviewprivate.h"
56 #include "gtkprivate.h"
57 #include "gtkrecentmanager.h"
58 #include "gtksearchentry.h"
59 #include "gtkseparatormenuitem.h"
60 #include "gtksettings.h"
61 #include "gtksizegroup.h"
62 #include "gtksizerequest.h"
63 #include "gtkstack.h"
64 #include "gtktooltip.h"
65 #include "gtktreednd.h"
66 #include "gtktreeprivate.h"
67 #include "gtktreeselection.h"
68 #include "gtkbox.h"
69 #include "gtkcheckbutton.h"
70 #include "gtkwindowgroup.h"
71 #include "gtkintl.h"
72 #include "a11y/gtkfilechooserwidgetaccessible.h"
73 #include "gtkshow.h"
74 #include "gtkmain.h"
75 #include "gtkscrollable.h"
76 #include "gtkpopover.h"
77 #include "gtkpopoverprivate.h"
78 #include "gtkrevealer.h"
79 #include "gtkspinner.h"
80 #include "gtkseparator.h"
81 #include "gtkmodelbutton.h"
82 #include "gtkgesturelongpress.h"
83 
84 #include <cairo-gobject.h>
85 
86 #ifdef HAVE_UNISTD_H
87 #include <unistd.h>
88 #endif
89 #ifdef G_OS_WIN32
90 #include <io.h>
91 #endif
92 
93 /**
94  * SECTION:gtkfilechooserwidget
95  * @Short_description: A file chooser widget
96  * @Title: GtkFileChooserWidget
97  * @See_also: #GtkFileChooserDialog
98  *
99  * #GtkFileChooserWidget is a widget for choosing files.
100  * It exposes the #GtkFileChooser interface, and you should
101  * use the methods of this interface to interact with the
102  * widget.
103  *
104  * # CSS nodes
105  *
106  * GtkFileChooserWidget has a single CSS node with name filechooser.
107  */
108 
109 
110 /* Values for GtkSelection-related "info" fields */
111 #define SELECTION_TEXT 0
112 #define SELECTION_URI  1
113 
114 /* 150 mseconds of delay */
115 #define LOCATION_CHANGED_TIMEOUT 150
116 
117 /* Profiling stuff */
118 #undef PROFILE_FILE_CHOOSER
119 #ifdef PROFILE_FILE_CHOOSER
120 
121 
122 #ifndef F_OK
123 #define F_OK 0
124 #endif
125 
126 #define PROFILE_INDENT 4
127 
128 static int profile_indent;
129 
130 static void
profile_add_indent(int indent)131 profile_add_indent (int indent)
132 {
133   profile_indent += indent;
134   if (profile_indent < 0)
135     g_error ("You screwed up your indentation");
136 }
137 
138 static void
_gtk_file_chooser_profile_log(const char * func,int indent,const char * msg1,const char * msg2)139 _gtk_file_chooser_profile_log (const char *func, int indent, const char *msg1, const char *msg2)
140 {
141   char *str;
142 
143   if (indent < 0)
144     profile_add_indent (indent);
145 
146   if (profile_indent == 0)
147     str = g_strdup_printf ("MARK: %s %s %s", func ? func : "", msg1 ? msg1 : "", msg2 ? msg2 : "");
148   else
149     str = g_strdup_printf ("MARK: %*c %s %s %s", profile_indent - 1, ' ', func ? func : "", msg1 ? msg1 : "", msg2 ? msg2 : "");
150 
151   access (str, F_OK);
152   g_free (str);
153 
154   if (indent > 0)
155     profile_add_indent (indent);
156 }
157 
158 #define profile_start(x, y) _gtk_file_chooser_profile_log (G_STRFUNC, PROFILE_INDENT, x, y)
159 #define profile_end(x, y) _gtk_file_chooser_profile_log (G_STRFUNC, -PROFILE_INDENT, x, y)
160 #define profile_msg(x, y) _gtk_file_chooser_profile_log (NULL, 0, x, y)
161 #else
162 #define profile_start(x, y)
163 #define profile_end(x, y)
164 #define profile_msg(x, y)
165 #endif
166 
167 enum {
168   PROP_SEARCH_MODE = 1,
169   PROP_SUBTITLE
170 };
171 
172 typedef enum {
173   LOAD_EMPTY,                   /* There is no model */
174   LOAD_PRELOAD,                 /* Model is loading and a timer is running; model isn't inserted into the tree yet */
175   LOAD_LOADING,                 /* Timeout expired, model is inserted into the tree, but not fully loaded yet */
176   LOAD_FINISHED                 /* Model is fully loaded and inserted into the tree */
177 } LoadState;
178 
179 typedef enum {
180   RELOAD_EMPTY,                 /* No folder has been set */
181   RELOAD_HAS_FOLDER             /* We have a folder, although it may not be completely loaded yet; no need to reload */
182 } ReloadState;
183 
184 typedef enum {
185   LOCATION_MODE_PATH_BAR,
186   LOCATION_MODE_FILENAME_ENTRY
187 } LocationMode;
188 
189 typedef enum {
190   OPERATION_MODE_BROWSE,
191   OPERATION_MODE_SEARCH,
192   OPERATION_MODE_ENTER_LOCATION,
193   OPERATION_MODE_OTHER_LOCATIONS,
194   OPERATION_MODE_RECENT
195 } OperationMode;
196 
197 typedef enum {
198   STARTUP_MODE_RECENT,
199   STARTUP_MODE_CWD
200 } StartupMode;
201 
202 typedef enum {
203   CLOCK_FORMAT_24,
204   CLOCK_FORMAT_12
205 } ClockFormat;
206 
207 typedef enum {
208   DATE_FORMAT_REGULAR,
209   DATE_FORMAT_WITH_TIME
210 } DateFormat;
211 
212 typedef enum {
213   TYPE_FORMAT_MIME,
214   TYPE_FORMAT_DESCRIPTION,
215   TYPE_FORMAT_CATEGORY
216 } TypeFormat;
217 
218 struct _GtkFileChooserWidgetPrivate {
219   GtkFileChooserAction action;
220 
221   GtkFileSystem *file_system;
222 
223   /* Save mode widgets */
224   GtkWidget *save_widgets;
225   GtkWidget *save_widgets_table;
226 
227   /* The file browsing widgets */
228   GtkWidget *browse_widgets_hpaned;
229   GtkWidget *browse_header_revealer;
230   GtkWidget *browse_header_stack;
231   GtkWidget *browse_files_stack;
232   GtkWidget *browse_files_swin;
233   GtkWidget *browse_files_tree_view;
234   GtkWidget *remote_warning_bar;
235 
236   GtkWidget *browse_files_popover;
237   GtkWidget *add_shortcut_item;
238   GtkWidget *hidden_files_item;
239   GtkWidget *size_column_item;
240   GtkWidget *type_column_item;
241   GtkWidget *copy_file_location_item;
242   GtkWidget *visit_file_item;
243   GtkWidget *open_folder_item;
244   GtkWidget *rename_file_item;
245   GtkWidget *trash_file_item;
246   GtkWidget *delete_file_item;
247   GtkWidget *sort_directories_item;
248   GtkWidget *show_time_item;
249 
250   GtkWidget *browse_new_folder_button;
251   GtkSizeGroup *browse_path_bar_size_group;
252   GtkWidget *browse_path_bar;
253   GtkWidget *new_folder_name_entry;
254   GtkWidget *new_folder_create_button;
255   GtkWidget *new_folder_error_label;
256   GtkWidget *new_folder_popover;
257   GtkWidget *rename_file_name_entry;
258   GtkWidget *rename_file_rename_button;
259   GtkWidget *rename_file_error_label;
260   GtkWidget *rename_file_popover;
261   GFile *rename_file_source_file;
262 
263   GtkGesture *long_press_gesture;
264 
265   GtkFileSystemModel *browse_files_model;
266   char *browse_files_last_selected_name;
267 
268   GtkWidget *places_sidebar;
269   GtkWidget *places_view;
270   StartupMode startup_mode;
271 
272   /* OPERATION_MODE_SEARCH */
273   GtkWidget *search_entry;
274   GtkWidget *search_spinner;
275   guint show_progress_timeout;
276   GtkSearchEngine *search_engine;
277   GtkQuery *search_query;
278   GtkFileSystemModel *search_model;
279   GtkFileSystemModel *model_for_search;
280 
281   /* OPERATION_MODE_RECENT */
282   GtkRecentManager *recent_manager;
283   GtkFileSystemModel *recent_model;
284   guint load_recent_id;
285 
286   GtkWidget *extra_and_filters;
287   GtkWidget *filter_combo_hbox;
288   GtkWidget *filter_combo;
289   GtkWidget *preview_box;
290   GtkWidget *preview_label;
291   GtkWidget *preview_widget;
292   GtkWidget *extra_align;
293   GtkWidget *extra_widget;
294 
295   GtkWidget *location_entry_box;
296   GtkWidget *location_entry;
297   LocationMode location_mode;
298 
299   GtkWidget *external_entry;
300 
301   GtkWidget *choice_box;
302   GHashTable *choices;
303 
304   /* Handles */
305   GCancellable *file_list_drag_data_received_cancellable;
306   GCancellable *update_current_folder_cancellable;
307   GCancellable *should_respond_get_info_cancellable;
308   GCancellable *file_exists_get_info_cancellable;
309 
310   LoadState load_state;
311   ReloadState reload_state;
312   guint load_timeout_id;
313 
314   OperationMode operation_mode;
315 
316   GSList *pending_select_files;
317 
318   GtkFileFilter *current_filter;
319   GSList *filters;
320 
321   GtkBookmarksManager *bookmarks_manager;
322 
323   int num_volumes;
324   int num_shortcuts;
325   int num_bookmarks;
326 
327   gulong volumes_changed_id;
328   gulong bookmarks_changed_id;
329 
330   GFile *current_volume_file;
331   GFile *current_folder;
332   GFile *preview_file;
333   char *preview_display_name;
334   GFile *renamed_file;
335 
336   GtkTreeViewColumn *list_name_column;
337   GtkCellRenderer *list_name_renderer;
338   GtkCellRenderer *list_pixbuf_renderer;
339   GtkTreeViewColumn *list_time_column;
340   GtkCellRenderer *list_date_renderer;
341   GtkCellRenderer *list_time_renderer;
342   GtkTreeViewColumn *list_size_column;
343   GtkCellRenderer *list_size_renderer;
344   GtkTreeViewColumn *list_type_column;
345   GtkCellRenderer *list_type_renderer;
346   GtkTreeViewColumn *list_location_column;
347   GtkCellRenderer *list_location_renderer;
348 
349   guint location_changed_id;
350 
351   gulong settings_signal_id;
352   int icon_size;
353 
354   GSource *focus_entry_idle;
355 
356   gulong toplevel_set_focus_id;
357   GtkWidget *toplevel_last_focus_widget;
358 
359   gint sort_column;
360   GtkSortType sort_order;
361 
362   TypeFormat type_format;
363 
364   /* Flags */
365 
366   guint local_only : 1;
367   guint preview_widget_active : 1;
368   guint use_preview_label : 1;
369   guint select_multiple : 1;
370   guint show_hidden : 1;
371   guint show_hidden_set : 1;
372   guint sort_directories_first : 1;
373   guint show_time : 1;
374   guint do_overwrite_confirmation : 1;
375   guint list_sort_ascending : 1;
376   guint shortcuts_current_folder_active : 1;
377   guint show_size_column : 1;
378   guint show_type_column : 1;
379   guint create_folders : 1;
380   guint auto_selecting_first_row : 1;
381   guint browse_files_interaction_frozen : 1;
382 };
383 
384 #define MAX_LOADING_TIME 500
385 
386 #define DEFAULT_NEW_FOLDER_NAME _("Type name of new folder")
387 
388 /* Signal IDs */
389 enum {
390   LOCATION_POPUP,
391   LOCATION_POPUP_ON_PASTE,
392   UP_FOLDER,
393   DOWN_FOLDER,
394   HOME_FOLDER,
395   DESKTOP_FOLDER,
396   QUICK_BOOKMARK,
397   LOCATION_TOGGLE_POPUP,
398   SHOW_HIDDEN,
399   SEARCH_SHORTCUT,
400   RECENT_SHORTCUT,
401   PLACES_SHORTCUT,
402 
403   LAST_SIGNAL
404 };
405 
406 static guint signals[LAST_SIGNAL] = { 0 };
407 
408 #define MODEL_ATTRIBUTES "standard::name,standard::type,standard::display-name," \
409                          "standard::is-hidden,standard::is-backup,standard::size," \
410                          "standard::content-type,standard::fast-content-type,time::modified,time::access," \
411                          "access::can-rename,access::can-delete,access::can-trash," \
412                          "standard::target-uri"
413 enum {
414   /* the first 4 must be these due to settings caching sort column */
415   MODEL_COL_NAME,
416   MODEL_COL_SIZE,
417   MODEL_COL_TYPE,
418   MODEL_COL_TIME,
419   MODEL_COL_FILE,
420   MODEL_COL_NAME_COLLATED,
421   MODEL_COL_IS_FOLDER,
422   MODEL_COL_IS_SENSITIVE,
423   MODEL_COL_SURFACE,
424   MODEL_COL_SIZE_TEXT,
425   MODEL_COL_DATE_TEXT,
426   MODEL_COL_TIME_TEXT,
427   MODEL_COL_LOCATION_TEXT,
428   MODEL_COL_ELLIPSIZE,
429   MODEL_COL_NUM_COLUMNS
430 };
431 
432 /* This list of types is passed to _gtk_file_system_model_new*() */
433 #define MODEL_COLUMN_TYPES                                      \
434         MODEL_COL_NUM_COLUMNS,                                  \
435         G_TYPE_STRING,            /* MODEL_COL_NAME */          \
436         G_TYPE_INT64,             /* MODEL_COL_SIZE */          \
437         G_TYPE_STRING,            /* MODEL_COL_TYPE */          \
438         G_TYPE_LONG,              /* MODEL_COL_TIME */          \
439         G_TYPE_FILE,              /* MODEL_COL_FILE */          \
440         G_TYPE_STRING,            /* MODEL_COL_NAME_COLLATED */ \
441         G_TYPE_BOOLEAN,           /* MODEL_COL_IS_FOLDER */     \
442         G_TYPE_BOOLEAN,           /* MODEL_COL_IS_SENSITIVE */  \
443         CAIRO_GOBJECT_TYPE_SURFACE,  /* MODEL_COL_SURFACE */    \
444         G_TYPE_STRING,            /* MODEL_COL_SIZE_TEXT */     \
445         G_TYPE_STRING,            /* MODEL_COL_DATE_TEXT */     \
446         G_TYPE_STRING,            /* MODEL_COL_TIME_TEXT */     \
447         G_TYPE_STRING,            /* MODEL_COL_LOCATION_TEXT */ \
448         PANGO_TYPE_ELLIPSIZE_MODE /* MODEL_COL_ELLIPSIZE */
449 
450 #define DEFAULT_RECENT_FILES_LIMIT 50
451 
452 /* Icon size for if we can't get it from the theme */
453 #define FALLBACK_ICON_SIZE 16
454 
455 #define PREVIEW_HBOX_SPACING 12
456 #define NUM_LINES 45
457 #define NUM_CHARS 60
458 
459 static void gtk_file_chooser_widget_iface_init       (GtkFileChooserIface        *iface);
460 static void gtk_file_chooser_embed_default_iface_init (GtkFileChooserEmbedIface   *iface);
461 
462 static void     gtk_file_chooser_widget_constructed  (GObject               *object);
463 static void     gtk_file_chooser_widget_finalize     (GObject               *object);
464 static void     gtk_file_chooser_widget_set_property (GObject               *object,
465                                                        guint                  prop_id,
466                                                        const GValue          *value,
467                                                        GParamSpec            *pspec);
468 static void     gtk_file_chooser_widget_get_property (GObject               *object,
469                                                        guint                  prop_id,
470                                                        GValue                *value,
471                                                        GParamSpec            *pspec);
472 static void     gtk_file_chooser_widget_dispose      (GObject               *object);
473 static void     gtk_file_chooser_widget_show_all       (GtkWidget             *widget);
474 static void     gtk_file_chooser_widget_realize        (GtkWidget             *widget);
475 static void     gtk_file_chooser_widget_map            (GtkWidget             *widget);
476 static void     gtk_file_chooser_widget_unmap          (GtkWidget             *widget);
477 static void     gtk_file_chooser_widget_hierarchy_changed (GtkWidget          *widget,
478                                                             GtkWidget          *previous_toplevel);
479 static void     gtk_file_chooser_widget_style_updated  (GtkWidget             *widget);
480 static void     gtk_file_chooser_widget_screen_changed (GtkWidget             *widget,
481                                                         GdkScreen             *previous_screen);
482 static gboolean gtk_file_chooser_widget_key_press_event (GtkWidget            *widget,
483                                                          GdkEventKey          *event);
484 
485 static gboolean       gtk_file_chooser_widget_set_current_folder           (GtkFileChooser    *chooser,
486                                                                             GFile             *folder,
487                                                                             GError           **error);
488 static gboolean       gtk_file_chooser_widget_update_current_folder        (GtkFileChooser    *chooser,
489                                                                             GFile             *folder,
490                                                                             gboolean           keep_trail,
491                                                                             gboolean           clear_entry,
492                                                                             GError           **error);
493 static GFile *        gtk_file_chooser_widget_get_current_folder           (GtkFileChooser    *chooser);
494 static void           gtk_file_chooser_widget_set_current_name             (GtkFileChooser    *chooser,
495                                                                             const gchar       *name);
496 static gchar *        gtk_file_chooser_widget_get_current_name             (GtkFileChooser    *chooser);
497 static gboolean       gtk_file_chooser_widget_select_file                  (GtkFileChooser    *chooser,
498                                                                             GFile             *file,
499                                                                             GError           **error);
500 static void           gtk_file_chooser_widget_unselect_file                (GtkFileChooser    *chooser,
501                                                                             GFile             *file);
502 static void           gtk_file_chooser_widget_select_all                   (GtkFileChooser    *chooser);
503 static void           gtk_file_chooser_widget_unselect_all                 (GtkFileChooser    *chooser);
504 static GSList *       gtk_file_chooser_widget_get_files                    (GtkFileChooser    *chooser);
505 static GFile *        gtk_file_chooser_widget_get_preview_file             (GtkFileChooser    *chooser);
506 static GtkFileSystem *gtk_file_chooser_widget_get_file_system              (GtkFileChooser    *chooser);
507 static void           gtk_file_chooser_widget_add_filter                   (GtkFileChooser    *chooser,
508                                                                             GtkFileFilter     *filter);
509 static void           gtk_file_chooser_widget_remove_filter                (GtkFileChooser    *chooser,
510                                                                             GtkFileFilter     *filter);
511 static GSList *       gtk_file_chooser_widget_list_filters                 (GtkFileChooser    *chooser);
512 static gboolean       gtk_file_chooser_widget_add_shortcut_folder    (GtkFileChooser    *chooser,
513                                                                        GFile             *file,
514                                                                        GError           **error);
515 static gboolean       gtk_file_chooser_widget_remove_shortcut_folder (GtkFileChooser    *chooser,
516                                                                        GFile             *file,
517                                                                        GError           **error);
518 static GSList *       gtk_file_chooser_widget_list_shortcut_folders  (GtkFileChooser    *chooser);
519 
520 static void           gtk_file_chooser_widget_get_default_size       (GtkFileChooserEmbed *chooser_embed,
521                                                                        gint                *default_width,
522                                                                        gint                *default_height);
523 static gboolean       gtk_file_chooser_widget_should_respond         (GtkFileChooserEmbed *chooser_embed);
524 static void           gtk_file_chooser_widget_initial_focus          (GtkFileChooserEmbed *chooser_embed);
525 
526 static void        gtk_file_chooser_widget_add_choice    (GtkFileChooser  *chooser,
527                                                           const char      *id,
528                                                           const char      *label,
529                                                           const char     **options,
530                                                           const char     **option_labels);
531 static void        gtk_file_chooser_widget_remove_choice (GtkFileChooser  *chooser,
532                                                           const char      *id);
533 static void        gtk_file_chooser_widget_set_choice    (GtkFileChooser  *chooser,
534                                                           const char      *id,
535                                                           const char      *option);
536 static const char *gtk_file_chooser_widget_get_choice    (GtkFileChooser  *chooser,
537                                                           const char      *id);
538 
539 
540 static void add_selection_to_recent_list (GtkFileChooserWidget *impl);
541 
542 static void location_popup_handler  (GtkFileChooserWidget *impl,
543                                      const gchar           *path);
544 static void location_popup_on_paste_handler (GtkFileChooserWidget *impl);
545 static void location_toggle_popup_handler   (GtkFileChooserWidget *impl);
546 static void up_folder_handler       (GtkFileChooserWidget *impl);
547 static void down_folder_handler     (GtkFileChooserWidget *impl);
548 static void home_folder_handler     (GtkFileChooserWidget *impl);
549 static void desktop_folder_handler  (GtkFileChooserWidget *impl);
550 static void quick_bookmark_handler  (GtkFileChooserWidget *impl,
551                                      gint                   bookmark_index);
552 static void show_hidden_handler     (GtkFileChooserWidget *impl);
553 static void search_shortcut_handler (GtkFileChooserWidget *impl);
554 static void recent_shortcut_handler (GtkFileChooserWidget *impl);
555 static void places_shortcut_handler (GtkFileChooserWidget *impl);
556 static void update_appearance       (GtkFileChooserWidget *impl);
557 
558 static void operation_mode_set (GtkFileChooserWidget *impl, OperationMode mode);
559 static void location_mode_set  (GtkFileChooserWidget *impl, LocationMode new_mode);
560 
561 static void set_current_filter   (GtkFileChooserWidget *impl,
562                                   GtkFileFilter         *filter);
563 static void check_preview_change (GtkFileChooserWidget *impl);
564 
565 static void filter_combo_changed       (GtkComboBox           *combo_box,
566                                         GtkFileChooserWidget *impl);
567 
568 static gboolean list_select_func   (GtkTreeSelection      *selection,
569                                     GtkTreeModel          *model,
570                                     GtkTreePath           *path,
571                                     gboolean               path_currently_selected,
572                                     gpointer               data);
573 
574 static void list_selection_changed     (GtkTreeSelection      *tree_selection,
575                                         GtkFileChooserWidget  *impl);
576 static void list_row_activated         (GtkTreeView           *tree_view,
577                                         GtkTreePath           *path,
578                                         GtkTreeViewColumn     *column,
579                                         GtkFileChooserWidget  *impl);
580 static void list_cursor_changed        (GtkTreeView           *treeview,
581                                         GtkFileChooserWidget  *impl);
582 
583 static void path_bar_clicked (GtkPathBar            *path_bar,
584                               GFile                 *file,
585                               GFile                 *child,
586                               gboolean               child_is_hidden,
587                               GtkFileChooserWidget *impl);
588 
589 static void update_cell_renderer_attributes (GtkFileChooserWidget *impl);
590 
591 static void load_remove_timer (GtkFileChooserWidget *impl, LoadState new_load_state);
592 static void browse_files_center_selected_row (GtkFileChooserWidget *impl);
593 
594 static void location_switch_to_path_bar (GtkFileChooserWidget *impl);
595 
596 static void stop_loading_and_clear_list_model (GtkFileChooserWidget *impl,
597                                                gboolean remove_from_treeview);
598 
599 static GSList  *get_selected_files           (GtkFileChooserWidget *impl);
600 static GSList  *get_selected_infos           (GtkFileChooserWidget *impl);
601 
602 static void     search_setup_widgets         (GtkFileChooserWidget *impl);
603 static void     search_stop_searching        (GtkFileChooserWidget *impl,
604                                               gboolean               remove_query);
605 static void     search_clear_model           (GtkFileChooserWidget *impl,
606                                               gboolean               remove_from_treeview);
607 static void     search_entry_activate_cb     (GtkFileChooserWidget *impl);
608 static void     search_entry_stop_cb         (GtkFileChooserWidget *impl);
609 static void     settings_load                (GtkFileChooserWidget *impl);
610 
611 static void     show_filters                 (GtkFileChooserWidget *impl,
612                                               gboolean               show);
613 
614 static gboolean recent_files_setting_is_enabled (GtkFileChooserWidget *impl);
615 static void     recent_start_loading         (GtkFileChooserWidget *impl);
616 static void     recent_stop_loading          (GtkFileChooserWidget *impl);
617 static void     recent_clear_model           (GtkFileChooserWidget *impl,
618                                               gboolean               remove_from_treeview);
619 static gboolean recent_should_respond        (GtkFileChooserWidget *impl);
620 static void     set_file_system_backend      (GtkFileChooserWidget *impl);
621 static void     unset_file_system_backend    (GtkFileChooserWidget *impl);
622 
623 static void     clear_model_cache            (GtkFileChooserWidget *impl,
624                                               gint                  column);
625 static void     set_model_filter             (GtkFileChooserWidget *impl,
626                                               GtkFileFilter        *filter);
627 static void     switch_to_home_dir           (GtkFileChooserWidget *impl);
628 
629 
630 
631 G_DEFINE_TYPE_WITH_CODE (GtkFileChooserWidget, gtk_file_chooser_widget, GTK_TYPE_BOX,
632                          G_ADD_PRIVATE (GtkFileChooserWidget)
633                          G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_CHOOSER,
634                                                 gtk_file_chooser_widget_iface_init)
635                          G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_CHOOSER_EMBED,
636                                                 gtk_file_chooser_embed_default_iface_init));
637 
638 static void
gtk_file_chooser_widget_iface_init(GtkFileChooserIface * iface)639 gtk_file_chooser_widget_iface_init (GtkFileChooserIface *iface)
640 {
641   iface->select_file = gtk_file_chooser_widget_select_file;
642   iface->unselect_file = gtk_file_chooser_widget_unselect_file;
643   iface->select_all = gtk_file_chooser_widget_select_all;
644   iface->unselect_all = gtk_file_chooser_widget_unselect_all;
645   iface->get_files = gtk_file_chooser_widget_get_files;
646   iface->get_preview_file = gtk_file_chooser_widget_get_preview_file;
647   iface->get_file_system = gtk_file_chooser_widget_get_file_system;
648   iface->set_current_folder = gtk_file_chooser_widget_set_current_folder;
649   iface->get_current_folder = gtk_file_chooser_widget_get_current_folder;
650   iface->set_current_name = gtk_file_chooser_widget_set_current_name;
651   iface->get_current_name = gtk_file_chooser_widget_get_current_name;
652   iface->add_filter = gtk_file_chooser_widget_add_filter;
653   iface->remove_filter = gtk_file_chooser_widget_remove_filter;
654   iface->list_filters = gtk_file_chooser_widget_list_filters;
655   iface->add_shortcut_folder = gtk_file_chooser_widget_add_shortcut_folder;
656   iface->remove_shortcut_folder = gtk_file_chooser_widget_remove_shortcut_folder;
657   iface->list_shortcut_folders = gtk_file_chooser_widget_list_shortcut_folders;
658   iface->add_choice = gtk_file_chooser_widget_add_choice;
659   iface->remove_choice = gtk_file_chooser_widget_remove_choice;
660   iface->set_choice = gtk_file_chooser_widget_set_choice;
661   iface->get_choice = gtk_file_chooser_widget_get_choice;
662 }
663 
664 static void
gtk_file_chooser_embed_default_iface_init(GtkFileChooserEmbedIface * iface)665 gtk_file_chooser_embed_default_iface_init (GtkFileChooserEmbedIface *iface)
666 {
667   iface->get_default_size = gtk_file_chooser_widget_get_default_size;
668   iface->should_respond = gtk_file_chooser_widget_should_respond;
669   iface->initial_focus = gtk_file_chooser_widget_initial_focus;
670 }
671 
672 static void
pending_select_files_free(GtkFileChooserWidget * impl)673 pending_select_files_free (GtkFileChooserWidget *impl)
674 {
675   GtkFileChooserWidgetPrivate *priv = impl->priv;
676 
677   g_slist_free_full (priv->pending_select_files, g_object_unref);
678   priv->pending_select_files = NULL;
679 }
680 
681 static void
pending_select_files_add(GtkFileChooserWidget * impl,GFile * file)682 pending_select_files_add (GtkFileChooserWidget *impl,
683                           GFile                 *file)
684 {
685   GtkFileChooserWidgetPrivate *priv = impl->priv;
686 
687   priv->pending_select_files =
688     g_slist_prepend (priv->pending_select_files, g_object_ref (file));
689 }
690 
691 static void
gtk_file_chooser_widget_finalize(GObject * object)692 gtk_file_chooser_widget_finalize (GObject *object)
693 {
694   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (object);
695   GtkFileChooserWidgetPrivate *priv = impl->priv;
696 
697   if (priv->choices)
698     g_hash_table_unref (priv->choices);
699 
700   if (priv->location_changed_id > 0)
701     g_source_remove (priv->location_changed_id);
702 
703   unset_file_system_backend (impl);
704 
705   g_free (priv->browse_files_last_selected_name);
706 
707   g_slist_free_full (priv->filters, g_object_unref);
708 
709   if (priv->current_filter)
710     g_object_unref (priv->current_filter);
711 
712   if (priv->current_volume_file)
713     g_object_unref (priv->current_volume_file);
714 
715   if (priv->current_folder)
716     g_object_unref (priv->current_folder);
717 
718   if (priv->preview_file)
719     g_object_unref (priv->preview_file);
720 
721   if (priv->browse_path_bar_size_group)
722     g_object_unref (priv->browse_path_bar_size_group);
723 
724   if (priv->renamed_file)
725     g_object_unref (priv->renamed_file);
726 
727   /* Free all the Models we have */
728   stop_loading_and_clear_list_model (impl, FALSE);
729   search_clear_model (impl, FALSE);
730   recent_clear_model (impl, FALSE);
731   g_clear_object (&impl->priv->model_for_search);
732 
733   /* stopping the load above should have cleared this */
734   g_assert (priv->load_timeout_id == 0);
735 
736   g_free (priv->preview_display_name);
737 
738   impl->priv = NULL;
739 
740   G_OBJECT_CLASS (gtk_file_chooser_widget_parent_class)->finalize (object);
741 }
742 
743 /* Shows an error dialog set as transient for the specified window */
744 static void
error_message_with_parent(GtkWindow * parent,const char * msg,const char * detail)745 error_message_with_parent (GtkWindow  *parent,
746                            const char *msg,
747                            const char *detail)
748 {
749   GtkWidget *dialog;
750 
751   dialog = gtk_message_dialog_new (parent,
752                                    GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
753                                    GTK_MESSAGE_ERROR,
754                                    GTK_BUTTONS_OK,
755                                    "%s",
756                                    msg);
757   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
758                                             "%s", detail);
759 
760   if (parent && gtk_window_has_group (parent))
761     gtk_window_group_add_window (gtk_window_get_group (parent),
762                                  GTK_WINDOW (dialog));
763 
764   gtk_dialog_run (GTK_DIALOG (dialog));
765   gtk_widget_destroy (dialog);
766 }
767 
768 /* Returns a toplevel GtkWindow, or NULL if none */
769 static GtkWindow *
get_toplevel(GtkWidget * widget)770 get_toplevel (GtkWidget *widget)
771 {
772   GtkWidget *toplevel;
773 
774   toplevel = gtk_widget_get_toplevel (widget);
775   if (!gtk_widget_is_toplevel (toplevel))
776     return NULL;
777   else
778     return GTK_WINDOW (toplevel);
779 }
780 
781 /* Shows an error dialog for the file chooser */
782 static void
error_message(GtkFileChooserWidget * impl,const char * msg,const char * detail)783 error_message (GtkFileChooserWidget *impl,
784                const char            *msg,
785                const char            *detail)
786 {
787   error_message_with_parent (get_toplevel (GTK_WIDGET (impl)), msg, detail);
788 }
789 
790 /* Shows a simple error dialog relative to a path.  Frees the GError as well. */
791 static void
error_dialog(GtkFileChooserWidget * impl,const char * msg,GError * error)792 error_dialog (GtkFileChooserWidget *impl,
793               const char            *msg,
794               GError                *error)
795 {
796   if (error)
797     {
798       error_message (impl, msg, error->message);
799       g_error_free (error);
800     }
801 }
802 
803 /* Shows an error dialog about not being able to create a folder */
804 static void
error_creating_folder_dialog(GtkFileChooserWidget * impl,GFile * file,GError * error)805 error_creating_folder_dialog (GtkFileChooserWidget *impl,
806                               GFile                 *file,
807                               GError                *error)
808 {
809   error_dialog (impl,
810                 _("The folder could not be created"),
811                 error);
812 }
813 
814 /* Shows an error about not being able to create a folder because a file with
815  * the same name is already there.
816  */
817 static void
error_creating_folder_over_existing_file_dialog(GtkFileChooserWidget * impl,GFile * file)818 error_creating_folder_over_existing_file_dialog (GtkFileChooserWidget *impl,
819                                                  GFile                 *file)
820 {
821   error_message (impl,
822                  _("The folder could not be created, as a file with the same "
823                    "name already exists."),
824                  _("Try using a different name for the folder, or rename the "
825                    "file first."));
826 }
827 
828 static void
error_with_file_under_nonfolder(GtkFileChooserWidget * impl,GFile * parent_file)829 error_with_file_under_nonfolder (GtkFileChooserWidget *impl,
830                                  GFile *parent_file)
831 {
832   GError *error;
833   char *uri, *msg;
834 
835   error = NULL;
836   g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY,
837                        _("You need to choose a valid filename."));
838 
839   uri = g_file_get_uri (parent_file);
840   msg = g_strdup_printf (_("Cannot create a file under %s as it is not a folder"), uri);
841   g_free (uri);
842   error_dialog (impl, msg, error);
843   g_free (msg);
844 }
845 
846 static void
error_filename_to_long_dialog(GtkFileChooserWidget * impl)847 error_filename_to_long_dialog (GtkFileChooserWidget *impl)
848 {
849   error_message (impl,
850                  _("Cannot create file as the filename is too long"),
851                  _("Try using a shorter name."));
852 }
853 
854 /* Shows an error about not being able to select a folder because a file with
855  * the same name is already there.
856  */
857 static void
error_selecting_folder_over_existing_file_dialog(GtkFileChooserWidget * impl)858 error_selecting_folder_over_existing_file_dialog (GtkFileChooserWidget *impl)
859 {
860   error_message (impl,
861                  _("You may only select folders"),
862                  _("The item that you selected is not a folder try using a different item."));
863 }
864 
865 /* Shows an error dialog about not being able to create a filename */
866 static void
error_building_filename_dialog(GtkFileChooserWidget * impl,GError * error)867 error_building_filename_dialog (GtkFileChooserWidget *impl,
868                                 GError                *error)
869 {
870   error_dialog (impl, _("Invalid file name"), error);
871 }
872 
873 /* Shows an error dialog when we cannot switch to a folder */
874 static void
error_changing_folder_dialog(GtkFileChooserWidget * impl,GFile * file,GError * error)875 error_changing_folder_dialog (GtkFileChooserWidget *impl,
876                               GFile                 *file,
877                               GError                *error)
878 {
879   error_dialog (impl, _("The folder contents could not be displayed"), error);
880 }
881 
882 static void
error_deleting_file(GtkFileChooserWidget * impl,GFile * file,GError * error)883 error_deleting_file (GtkFileChooserWidget *impl,
884                      GFile                *file,
885                      GError               *error)
886 {
887   error_dialog (impl, _("The file could not be deleted"), error);
888 }
889 
890 static void
error_trashing_file(GtkFileChooserWidget * impl,GFile * file,GError * error)891 error_trashing_file (GtkFileChooserWidget *impl,
892                      GFile                *file,
893                      GError               *error)
894 {
895   error_dialog (impl, _("The file could not be moved to the Trash"), error);
896 }
897 
898 /* Changes folders, displaying an error dialog if this fails */
899 static gboolean
change_folder_and_display_error(GtkFileChooserWidget * impl,GFile * file,gboolean clear_entry)900 change_folder_and_display_error (GtkFileChooserWidget *impl,
901                                  GFile                 *file,
902                                  gboolean               clear_entry)
903 {
904   GError *error;
905   gboolean result;
906 
907   g_return_val_if_fail (G_IS_FILE (file), FALSE);
908 
909   /* We copy the path because of this case:
910    *
911    * list_row_activated()
912    *   fetches path from model; path belongs to the model (*)
913    *   calls change_folder_and_display_error()
914    *     calls gtk_file_chooser_set_current_folder_file()
915    *       changing folders fails, sets model to NULL, thus freeing the path in (*)
916    */
917 
918   error = NULL;
919   result = gtk_file_chooser_widget_update_current_folder (GTK_FILE_CHOOSER (impl), file, TRUE, clear_entry, &error);
920 
921   if (!result)
922     error_changing_folder_dialog (impl, file, error);
923 
924   return result;
925 }
926 
927 static void
emit_default_size_changed(GtkFileChooserWidget * impl)928 emit_default_size_changed (GtkFileChooserWidget *impl)
929 {
930   profile_msg ("    emit default-size-changed start", NULL);
931   g_signal_emit_by_name (impl, "default-size-changed");
932   profile_msg ("    emit default-size-changed end", NULL);
933 }
934 
935 static void
update_preview_widget_visibility(GtkFileChooserWidget * impl)936 update_preview_widget_visibility (GtkFileChooserWidget *impl)
937 {
938   GtkFileChooserWidgetPrivate *priv = impl->priv;
939 
940   if (priv->use_preview_label)
941     {
942       if (!priv->preview_label)
943         {
944           priv->preview_label = gtk_label_new (priv->preview_display_name);
945           gtk_box_pack_start (GTK_BOX (priv->preview_box), priv->preview_label, FALSE, FALSE, 0);
946           gtk_box_reorder_child (GTK_BOX (priv->preview_box), priv->preview_label, 0);
947           gtk_label_set_ellipsize (GTK_LABEL (priv->preview_label), PANGO_ELLIPSIZE_MIDDLE);
948           gtk_widget_show (priv->preview_label);
949         }
950     }
951   else
952     {
953       if (priv->preview_label)
954         {
955           gtk_widget_destroy (priv->preview_label);
956           priv->preview_label = NULL;
957         }
958     }
959 
960   if (priv->preview_widget_active && priv->preview_widget)
961     gtk_widget_show (priv->preview_box);
962   else
963     gtk_widget_hide (priv->preview_box);
964 
965   if (!gtk_widget_get_mapped (GTK_WIDGET (impl)))
966     emit_default_size_changed (impl);
967 }
968 
969 static void
set_preview_widget(GtkFileChooserWidget * impl,GtkWidget * preview_widget)970 set_preview_widget (GtkFileChooserWidget *impl,
971                     GtkWidget             *preview_widget)
972 {
973   GtkFileChooserWidgetPrivate *priv = impl->priv;
974 
975   if (preview_widget == priv->preview_widget)
976     return;
977 
978   if (priv->preview_widget)
979     gtk_container_remove (GTK_CONTAINER (priv->preview_box),
980                           priv->preview_widget);
981 
982   priv->preview_widget = preview_widget;
983   if (priv->preview_widget)
984     {
985       gtk_widget_show (priv->preview_widget);
986       gtk_box_pack_start (GTK_BOX (priv->preview_box), priv->preview_widget, TRUE, TRUE, 0);
987       gtk_box_reorder_child (GTK_BOX (priv->preview_box),
988                              priv->preview_widget,
989                              (priv->use_preview_label && priv->preview_label) ? 1 : 0);
990     }
991 
992   update_preview_widget_visibility (impl);
993 }
994 
995 static void
new_folder_popover_active(GtkWidget * button,GParamSpec * pspec,GtkFileChooserWidget * impl)996 new_folder_popover_active (GtkWidget            *button,
997                            GParamSpec           *pspec,
998                            GtkFileChooserWidget *impl)
999 {
1000   GtkFileChooserWidgetPrivate *priv = impl->priv;
1001 
1002   gtk_entry_set_text (GTK_ENTRY (priv->new_folder_name_entry), "");
1003   gtk_widget_set_sensitive (priv->new_folder_create_button, FALSE);
1004   gtk_label_set_text (GTK_LABEL (priv->new_folder_error_label), "");
1005 }
1006 
1007 struct FileExistsData
1008 {
1009   GtkFileChooserWidget *impl;
1010   gboolean file_exists_and_is_not_folder;
1011   GFile *parent_file;
1012   GFile *file;
1013   GtkWidget *error_label;
1014   GtkWidget *button;
1015 };
1016 
1017 static void
name_exists_get_info_cb(GCancellable * cancellable,GFileInfo * info,const GError * error,gpointer user_data)1018 name_exists_get_info_cb (GCancellable *cancellable,
1019                          GFileInfo    *info,
1020                          const GError *error,
1021                          gpointer      user_data)
1022 {
1023   struct FileExistsData *data = user_data;
1024   GtkFileChooserWidget *impl = data->impl;
1025   GtkFileChooserWidgetPrivate *priv = impl->priv;
1026 
1027   if (cancellable != priv->file_exists_get_info_cancellable)
1028     goto out;
1029 
1030   priv->file_exists_get_info_cancellable = NULL;
1031 
1032   if (g_cancellable_is_cancelled (cancellable))
1033     goto out;
1034 
1035   if (info != NULL)
1036     {
1037       const gchar *msg;
1038 
1039       if (_gtk_file_info_consider_as_directory (info))
1040         msg = _("A folder with that name already exists");
1041       else
1042         msg = _("A file with that name already exists");
1043 
1044       gtk_widget_set_sensitive (data->button, FALSE);
1045       gtk_label_set_text (GTK_LABEL (data->error_label), msg);
1046     }
1047   else
1048     {
1049       gtk_widget_set_sensitive (data->button, TRUE);
1050       /* Don't clear the label here, it may contain a warning */
1051     }
1052 
1053 out:
1054   g_object_unref (impl);
1055   g_object_unref (data->file);
1056   g_free (data);
1057   g_object_unref (cancellable);
1058 }
1059 
1060 static void
check_valid_child_name(GtkFileChooserWidget * impl,GFile * parent,const gchar * name,gboolean is_folder,GFile * original,GtkWidget * error_label,GtkWidget * button)1061 check_valid_child_name (GtkFileChooserWidget *impl,
1062                         GFile                *parent,
1063                         const gchar          *name,
1064                         gboolean              is_folder,
1065                         GFile                *original,
1066                         GtkWidget            *error_label,
1067                         GtkWidget            *button)
1068 {
1069   GtkFileChooserWidgetPrivate *priv = impl->priv;
1070 
1071   gtk_widget_set_sensitive (button, FALSE);
1072 
1073   if (name[0] == '\0')
1074     gtk_label_set_text (GTK_LABEL (error_label), "");
1075   else if (strcmp (name, ".") == 0)
1076     gtk_label_set_text (GTK_LABEL (error_label),
1077                         is_folder ? _("A folder cannot be called “.”")
1078                                   : _("A file cannot be called “.”"));
1079   else if (strcmp (name, "..") == 0)
1080     gtk_label_set_text (GTK_LABEL (error_label),
1081                         is_folder ? _("A folder cannot be called “..”")
1082                                   : _("A file cannot be called “..”"));
1083   else if (strchr (name, '/') != NULL)
1084     gtk_label_set_text (GTK_LABEL (error_label),
1085                         is_folder ? _("Folder names cannot contain “/”")
1086                                   : _("File names cannot contain “/”"));
1087   else
1088     {
1089       GFile *file;
1090       GError *error = NULL;
1091 
1092       gtk_label_set_text (GTK_LABEL (error_label), "");
1093 
1094       file = g_file_get_child_for_display_name (parent, name, &error);
1095       if (file == NULL)
1096         {
1097           gtk_label_set_text (GTK_LABEL (error_label), error->message);
1098           g_error_free (error);
1099         }
1100       else if (original && g_file_equal (original, file))
1101         {
1102           gtk_widget_set_sensitive (button, TRUE);
1103           g_object_unref (file);
1104         }
1105       else
1106         {
1107           struct FileExistsData *data;
1108 
1109           /* Warn the user about questionable names that are technically valid */
1110           if (g_ascii_isspace (name[0]))
1111             gtk_label_set_text (GTK_LABEL (error_label),
1112                                 is_folder ? _("Folder names should not begin with a space")
1113                                           : _("File names should not begin with a space"));
1114 
1115           else if (g_ascii_isspace (name[strlen (name) - 1]))
1116             gtk_label_set_text (GTK_LABEL (error_label),
1117                                 is_folder ? _("Folder names should not end with a space")
1118                                           : _("File names should not end with a space"));
1119           else if (name[0] == '.')
1120             gtk_label_set_text (GTK_LABEL (error_label),
1121                                 is_folder ? _("Folder names starting with a “.” are hidden")
1122                                           : _("File names starting with a “.” are hidden"));
1123 
1124           data = g_new0 (struct FileExistsData, 1);
1125           data->impl = g_object_ref (impl);
1126           data->file = g_object_ref (file);
1127           data->error_label = error_label;
1128           data->button = button;
1129 
1130           if (priv->file_exists_get_info_cancellable)
1131             g_cancellable_cancel (priv->file_exists_get_info_cancellable);
1132 
1133           priv->file_exists_get_info_cancellable =
1134             _gtk_file_system_get_info (priv->file_system,
1135                                        file,
1136                                        "standard::type",
1137                                        name_exists_get_info_cb,
1138                                        data);
1139 
1140           g_object_unref (file);
1141         }
1142     }
1143 }
1144 
1145 static void
new_folder_name_changed(GtkEntry * entry,GtkFileChooserWidget * impl)1146 new_folder_name_changed (GtkEntry             *entry,
1147                          GtkFileChooserWidget *impl)
1148 {
1149   GtkFileChooserWidgetPrivate *priv = impl->priv;
1150 
1151   check_valid_child_name (impl,
1152                           priv->current_folder,
1153                           gtk_entry_get_text (entry),
1154                           TRUE,
1155                           NULL,
1156                           priv->new_folder_error_label,
1157                           priv->new_folder_create_button);
1158 }
1159 
1160 static void
new_folder_create_clicked(GtkButton * button,GtkFileChooserWidget * impl)1161 new_folder_create_clicked (GtkButton            *button,
1162                            GtkFileChooserWidget *impl)
1163 {
1164   GtkFileChooserWidgetPrivate *priv = impl->priv;
1165   GError *error = NULL;
1166   GFile *file;
1167   const gchar *name;
1168 
1169   name = gtk_entry_get_text (GTK_ENTRY (priv->new_folder_name_entry));
1170   file = g_file_get_child_for_display_name (priv->current_folder, name, &error);
1171 
1172   gtk_popover_popdown (GTK_POPOVER (priv->new_folder_popover));
1173 
1174   if (file)
1175     {
1176       if (g_file_make_directory (file, NULL, &error))
1177         change_folder_and_display_error (impl, file, FALSE);
1178       else
1179         error_creating_folder_dialog (impl, file, error);
1180       g_object_unref (file);
1181     }
1182   else
1183     error_creating_folder_dialog (impl, file, error);
1184 }
1185 
1186 struct selection_check_closure {
1187   GtkFileChooserWidget *impl;
1188   int num_selected;
1189   gboolean all_files;
1190   gboolean all_folders;
1191 };
1192 
1193 /* Used from gtk_tree_selection_selected_foreach() */
1194 static void
selection_check_foreach_cb(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)1195 selection_check_foreach_cb (GtkTreeModel *model,
1196                             GtkTreePath  *path,
1197                             GtkTreeIter  *iter,
1198                             gpointer      data)
1199 {
1200   struct selection_check_closure *closure;
1201   gboolean is_folder;
1202   GFile *file;
1203 
1204   gtk_tree_model_get (model, iter,
1205                       MODEL_COL_FILE, &file,
1206                       MODEL_COL_IS_FOLDER, &is_folder,
1207                       -1);
1208 
1209   if (file == NULL)
1210     return;
1211 
1212   g_object_unref (file);
1213 
1214   closure = data;
1215   closure->num_selected++;
1216 
1217   closure->all_folders = closure->all_folders && is_folder;
1218   closure->all_files = closure->all_files && !is_folder;
1219 }
1220 
1221 /* Checks whether the selected items in the file list are all files or all folders */
1222 static void
selection_check(GtkFileChooserWidget * impl,gint * num_selected,gboolean * all_files,gboolean * all_folders)1223 selection_check (GtkFileChooserWidget *impl,
1224                  gint                  *num_selected,
1225                  gboolean              *all_files,
1226                  gboolean              *all_folders)
1227 {
1228   GtkFileChooserWidgetPrivate *priv = impl->priv;
1229   struct selection_check_closure closure;
1230   GtkTreeSelection *selection;
1231 
1232   closure.impl = impl;
1233   closure.num_selected = 0;
1234   closure.all_files = TRUE;
1235   closure.all_folders = TRUE;
1236 
1237   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view));
1238   gtk_tree_selection_selected_foreach (selection,
1239                                        selection_check_foreach_cb,
1240                                        &closure);
1241 
1242   g_assert (closure.num_selected == 0 || !(closure.all_files && closure.all_folders));
1243 
1244   if (num_selected)
1245     *num_selected = closure.num_selected;
1246 
1247   if (all_files)
1248     *all_files = closure.all_files;
1249 
1250   if (all_folders)
1251     *all_folders = closure.all_folders;
1252 }
1253 
1254 static gboolean
file_is_recent_uri(GFile * file)1255 file_is_recent_uri (GFile *file)
1256 {
1257   GFile *recent;
1258   gboolean same;
1259 
1260   recent = g_file_new_for_uri ("recent:///");
1261   same = g_file_equal (file, recent);
1262   g_object_unref (recent);
1263 
1264   return same;
1265 }
1266 
1267 static void
places_sidebar_open_location_cb(GtkPlacesSidebar * sidebar,GFile * location,GtkPlacesOpenFlags open_flags,GtkFileChooserWidget * impl)1268 places_sidebar_open_location_cb (GtkPlacesSidebar     *sidebar,
1269                                  GFile                *location,
1270                                  GtkPlacesOpenFlags    open_flags,
1271                                  GtkFileChooserWidget *impl)
1272 {
1273   GtkFileChooserWidgetPrivate *priv = impl->priv;
1274   gboolean clear_entry;
1275 
1276   /* In the Save modes, we want to preserve what the user typed in the filename
1277    * entry, so that he may choose another folder without erasing his typed name.
1278    */
1279   if (priv->location_entry
1280       && !(priv->action == GTK_FILE_CHOOSER_ACTION_SAVE
1281            || priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER))
1282     clear_entry = TRUE;
1283   else
1284     clear_entry = FALSE;
1285 
1286   location_mode_set (impl, LOCATION_MODE_PATH_BAR);
1287 
1288   if (file_is_recent_uri (location))
1289     operation_mode_set (impl, OPERATION_MODE_RECENT);
1290   else
1291     change_folder_and_display_error (impl, location, clear_entry);
1292 }
1293 
1294 /* Callback used when the places sidebar needs us to display an error message */
1295 static void
places_sidebar_show_error_message_cb(GtkPlacesSidebar * sidebar,const char * primary,const char * secondary,GtkFileChooserWidget * impl)1296 places_sidebar_show_error_message_cb (GtkPlacesSidebar *sidebar,
1297                                       const char       *primary,
1298                                       const char       *secondary,
1299                                       GtkFileChooserWidget *impl)
1300 {
1301   error_message (impl, primary, secondary);
1302 }
1303 
1304 static gboolean
key_is_left_or_right(GdkEventKey * event)1305 key_is_left_or_right (GdkEventKey *event)
1306 {
1307   guint modifiers;
1308 
1309   modifiers = gtk_accelerator_get_default_mod_mask ();
1310 
1311   return ((event->keyval == GDK_KEY_Right
1312            || event->keyval == GDK_KEY_KP_Right
1313            || event->keyval == GDK_KEY_Left
1314            || event->keyval == GDK_KEY_KP_Left)
1315           && (event->state & modifiers) == 0);
1316 }
1317 
1318 static gboolean
should_trigger_location_entry(GtkFileChooserWidget * impl,GdkEventKey * event)1319 should_trigger_location_entry (GtkFileChooserWidget *impl,
1320                                GdkEventKey          *event)
1321 {
1322   GdkModifierType no_text_input_mask;
1323 
1324   if (impl->priv->operation_mode == OPERATION_MODE_SEARCH)
1325     return FALSE;
1326 
1327   no_text_input_mask =
1328     gtk_widget_get_modifier_mask (GTK_WIDGET (impl), GDK_MODIFIER_INTENT_NO_TEXT_INPUT);
1329 
1330   if ((event->keyval == GDK_KEY_slash
1331        || event->keyval == GDK_KEY_KP_Divide
1332        || event->keyval == GDK_KEY_period
1333 #ifdef G_OS_UNIX
1334        || event->keyval == GDK_KEY_asciitilde
1335 #endif
1336        ) && !(event->state & no_text_input_mask))
1337     return TRUE;
1338 
1339   return FALSE;
1340 }
1341 
1342 /* Handles key press events on the file list, so that we can trap Enter to
1343  * activate the default button on our own.  Also, checks to see if “/” has been
1344  * pressed.
1345  */
1346 static gboolean
browse_files_key_press_event_cb(GtkWidget * widget,GdkEventKey * event,gpointer data)1347 browse_files_key_press_event_cb (GtkWidget   *widget,
1348                                  GdkEventKey *event,
1349                                  gpointer     data)
1350 {
1351   GtkFileChooserWidget *impl = (GtkFileChooserWidget *) data;
1352   GtkFileChooserWidgetPrivate *priv = impl->priv;
1353 
1354   if (priv->browse_files_interaction_frozen)
1355     return GDK_EVENT_STOP;
1356 
1357   if (should_trigger_location_entry (impl, event) &&
1358       (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
1359        priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER))
1360     {
1361       location_popup_handler (impl, event->string);
1362       return TRUE;
1363     }
1364 
1365   if (key_is_left_or_right (event))
1366     {
1367       if (gtk_widget_child_focus (priv->places_sidebar, GTK_DIR_LEFT))
1368         return TRUE;
1369     }
1370 
1371   if ((event->keyval == GDK_KEY_Return
1372        || event->keyval == GDK_KEY_ISO_Enter
1373        || event->keyval == GDK_KEY_KP_Enter
1374        || event->keyval == GDK_KEY_space
1375        || event->keyval == GDK_KEY_KP_Space)
1376       && !(event->state & gtk_accelerator_get_default_mod_mask ())
1377       && !(priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER ||
1378            priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER))
1379     {
1380       GtkWindow *window;
1381 
1382       window = get_toplevel (widget);
1383       if (window)
1384         {
1385           GtkWidget *default_widget, *focus_widget;
1386 
1387           default_widget = gtk_window_get_default_widget (window);
1388           focus_widget = gtk_window_get_focus (window);
1389 
1390           if (widget != default_widget &&
1391               !(widget == focus_widget && (!default_widget || !gtk_widget_get_sensitive (default_widget))))
1392             {
1393               gtk_window_activate_default (window);
1394 
1395               return TRUE;
1396             }
1397         }
1398     }
1399 
1400   if (event->keyval == GDK_KEY_Escape &&
1401       priv->operation_mode == OPERATION_MODE_SEARCH)
1402     {
1403       gtk_search_entry_handle_event (GTK_SEARCH_ENTRY (priv->search_entry), (GdkEvent *)event);
1404       return TRUE;
1405     }
1406 
1407   return FALSE;
1408 }
1409 
1410 static gboolean
gtk_file_chooser_widget_key_press_event(GtkWidget * widget,GdkEventKey * event)1411 gtk_file_chooser_widget_key_press_event (GtkWidget   *widget,
1412                                          GdkEventKey *event)
1413 {
1414   GtkFileChooserWidget *impl = (GtkFileChooserWidget *) widget;
1415   GtkFileChooserWidgetPrivate *priv = impl->priv;
1416 
1417   if (should_trigger_location_entry (impl, event))
1418     {
1419       if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
1420           priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
1421         {
1422           location_popup_handler (impl, event->string);
1423           return TRUE;
1424         }
1425     }
1426   else if (gtk_search_entry_handle_event (GTK_SEARCH_ENTRY (priv->search_entry), (GdkEvent *)event))
1427     {
1428       if (priv->operation_mode != OPERATION_MODE_SEARCH)
1429         operation_mode_set (impl, OPERATION_MODE_SEARCH);
1430       return TRUE;
1431     }
1432 
1433   if (GTK_WIDGET_CLASS (gtk_file_chooser_widget_parent_class)->key_press_event (widget, event))
1434     return TRUE;
1435 
1436   return FALSE;
1437 }
1438 
1439 /* Callback used from gtk_tree_selection_selected_foreach(); adds a bookmark for
1440  * each selected item in the file list.
1441  */
1442 static void
add_bookmark_foreach_cb(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)1443 add_bookmark_foreach_cb (GtkTreeModel *model,
1444                          GtkTreePath  *path,
1445                          GtkTreeIter  *iter,
1446                          gpointer      data)
1447 {
1448   GtkFileChooserWidget *impl = (GtkFileChooserWidget *) data;
1449   GtkFileChooserWidgetPrivate *priv = impl->priv;
1450   GFile *file;
1451 
1452   gtk_tree_model_get (model, iter,
1453                       MODEL_COL_FILE, &file,
1454                       -1);
1455 
1456   _gtk_bookmarks_manager_insert_bookmark (priv->bookmarks_manager, file, 0, NULL); /* NULL-GError */
1457 
1458   g_object_unref (file);
1459 }
1460 
1461 /* Callback used when the "Add to Bookmarks" menu item is activated */
1462 static void
add_to_shortcuts_cb(GSimpleAction * action,GVariant * parameter,gpointer data)1463 add_to_shortcuts_cb (GSimpleAction *action,
1464                      GVariant      *parameter,
1465                      gpointer       data)
1466 {
1467   GtkFileChooserWidget *impl = data;
1468   GtkFileChooserWidgetPrivate *priv = impl->priv;
1469   GtkTreeSelection *selection;
1470 
1471   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view));
1472 
1473   gtk_tree_selection_selected_foreach (selection,
1474                                        add_bookmark_foreach_cb,
1475                                        impl);
1476 }
1477 
1478 static gboolean
confirm_delete(GtkFileChooserWidget * impl,GFileInfo * info)1479 confirm_delete (GtkFileChooserWidget *impl,
1480                 GFileInfo            *info)
1481 {
1482   GtkWindow *toplevel;
1483   GtkWidget *dialog;
1484   gint response;
1485   const gchar *name;
1486 
1487   name = g_file_info_get_display_name (info);
1488 
1489   toplevel = get_toplevel (GTK_WIDGET (impl));
1490 
1491   dialog = gtk_message_dialog_new (toplevel,
1492                                    GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
1493                                    GTK_MESSAGE_QUESTION,
1494                                    GTK_BUTTONS_NONE,
1495                                    _("Are you sure you want to permanently delete “%s”?"),
1496                                    name);
1497   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1498                                             _("If you delete an item, it will be permanently lost."));
1499   gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Cancel"), GTK_RESPONSE_CANCEL);
1500   gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Delete"), GTK_RESPONSE_ACCEPT);
1501 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
1502   gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
1503                                            GTK_RESPONSE_ACCEPT,
1504                                            GTK_RESPONSE_CANCEL,
1505                                            -1);
1506 G_GNUC_END_IGNORE_DEPRECATIONS
1507   gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
1508 
1509   if (gtk_window_has_group (toplevel))
1510     gtk_window_group_add_window (gtk_window_get_group (toplevel), GTK_WINDOW (dialog));
1511 
1512   response = gtk_dialog_run (GTK_DIALOG (dialog));
1513 
1514   gtk_widget_destroy (dialog);
1515 
1516   return (response == GTK_RESPONSE_ACCEPT);
1517 }
1518 
1519 static void
delete_selected_cb(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)1520 delete_selected_cb (GtkTreeModel *model,
1521                     GtkTreePath  *path,
1522                     GtkTreeIter  *iter,
1523                     gpointer      data)
1524 {
1525   GtkFileChooserWidget *impl = data;
1526   GFile *file;
1527   GFileInfo *info;
1528   GError *error = NULL;
1529 
1530   file = _gtk_file_system_model_get_file (GTK_FILE_SYSTEM_MODEL (model), iter);
1531   info = _gtk_file_system_model_get_info (GTK_FILE_SYSTEM_MODEL (model), iter);
1532 
1533   if (confirm_delete (impl, info))
1534     {
1535       if (!g_file_delete (file, NULL, &error))
1536         error_deleting_file (impl, file, error);
1537     }
1538 }
1539 
1540 static void
delete_file_cb(GSimpleAction * action,GVariant * parameter,gpointer data)1541 delete_file_cb (GSimpleAction *action,
1542                 GVariant      *parameter,
1543                 gpointer       data)
1544 {
1545   GtkFileChooserWidget *impl = data;
1546   GtkFileChooserWidgetPrivate *priv = impl->priv;
1547   GtkTreeSelection *selection;
1548 
1549   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view));
1550   gtk_tree_selection_selected_foreach (selection, delete_selected_cb, impl);
1551 }
1552 
1553 static void
trash_selected_cb(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)1554 trash_selected_cb (GtkTreeModel *model,
1555                    GtkTreePath  *path,
1556                    GtkTreeIter  *iter,
1557                    gpointer      data)
1558 {
1559   GtkFileChooserWidget *impl = data;
1560   GFile *file;
1561   GError *error = NULL;
1562 
1563   file = _gtk_file_system_model_get_file (GTK_FILE_SYSTEM_MODEL (model), iter);
1564 
1565   if (!g_file_trash (file, NULL, &error))
1566     error_trashing_file (impl, file, error);
1567 }
1568 
1569 
1570 static void
trash_file_cb(GSimpleAction * action,GVariant * parameter,gpointer data)1571 trash_file_cb (GSimpleAction *action,
1572                GVariant      *parameter,
1573                gpointer       data)
1574 {
1575   GtkFileChooserWidget *impl = data;
1576   GtkFileChooserWidgetPrivate *priv = impl->priv;
1577   GtkTreeSelection *selection;
1578 
1579   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view));
1580   gtk_tree_selection_selected_foreach (selection, trash_selected_cb, impl);
1581 }
1582 
1583 static void
rename_file_name_changed(GtkEntry * entry,GtkFileChooserWidget * impl)1584 rename_file_name_changed (GtkEntry             *entry,
1585                           GtkFileChooserWidget *impl)
1586 {
1587   GtkFileChooserWidgetPrivate *priv = impl->priv;
1588   GFileType file_type;
1589 
1590   file_type = g_file_query_file_type (priv->rename_file_source_file,
1591                                       G_FILE_QUERY_INFO_NONE, NULL);
1592 
1593   check_valid_child_name (impl,
1594                           priv->current_folder,
1595                           gtk_entry_get_text (entry),
1596                           file_type == G_FILE_TYPE_DIRECTORY,
1597                           priv->rename_file_source_file,
1598                           priv->rename_file_error_label,
1599                           priv->rename_file_rename_button);
1600 }
1601 
1602 static void
rename_file_end(GtkPopover * popover,GtkFileChooserWidget * impl)1603 rename_file_end (GtkPopover           *popover,
1604                  GtkFileChooserWidget *impl)
1605 {
1606   g_object_unref (impl->priv->rename_file_source_file);
1607 }
1608 
1609 static void
rename_file_rename_clicked(GtkButton * button,GtkFileChooserWidget * impl)1610 rename_file_rename_clicked (GtkButton            *button,
1611                             GtkFileChooserWidget *impl)
1612 {
1613   GtkFileChooserWidgetPrivate *priv = impl->priv;
1614   GFile *dest;
1615   const gchar* new_name;
1616 
1617   gtk_popover_popdown (GTK_POPOVER (priv->rename_file_popover));
1618 
1619   new_name = gtk_entry_get_text (GTK_ENTRY (priv->rename_file_name_entry));
1620   dest = g_file_get_parent (priv->rename_file_source_file);
1621 
1622   if (priv->renamed_file)
1623     g_clear_object (&priv->renamed_file);
1624 
1625   if (dest)
1626     {
1627       GFile *child;
1628       GError *error = NULL;
1629 
1630       child = g_file_get_child (dest, new_name);
1631       if (child)
1632         {
1633           if (!g_file_move (priv->rename_file_source_file, child, G_FILE_COPY_NONE,
1634                             NULL, NULL, NULL, &error))
1635             error_dialog (impl, _("The file could not be renamed"), error);
1636           else
1637             {
1638               /* Rename succeded, save renamed file so it will
1639                * be revealed by our "row-changed" handler */
1640               priv->renamed_file = g_object_ref (child);
1641             }
1642 
1643           g_object_unref (child);
1644         }
1645 
1646       g_object_unref (dest);
1647     }
1648 }
1649 
1650 static void
rename_selected_cb(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)1651 rename_selected_cb (GtkTreeModel *model,
1652                     GtkTreePath  *path,
1653                     GtkTreeIter  *iter,
1654                     gpointer      data)
1655 {
1656   GtkFileChooserWidget *impl = data;
1657   GtkFileChooserWidgetPrivate *priv = impl->priv;
1658   GdkRectangle rect;
1659   gchar *filename;
1660 
1661   gtk_tree_model_get (model, iter,
1662                       MODEL_COL_FILE, &priv->rename_file_source_file,
1663                       -1);
1664 
1665   gtk_tree_view_get_cell_area (GTK_TREE_VIEW (priv->browse_files_tree_view),
1666                                path, priv->list_name_column, &rect);
1667 
1668   gtk_tree_view_convert_bin_window_to_widget_coords (GTK_TREE_VIEW (priv->browse_files_tree_view),
1669                                                      rect.x, rect.y, &rect.x, &rect.y);
1670 
1671   filename = g_file_get_basename (priv->rename_file_source_file);
1672   gtk_entry_set_text (GTK_ENTRY(priv->rename_file_name_entry), filename);
1673   g_free (filename);
1674 
1675   gtk_popover_set_pointing_to (GTK_POPOVER (priv->rename_file_popover), &rect);
1676   gtk_popover_popup (GTK_POPOVER (priv->rename_file_popover));
1677   gtk_widget_grab_focus (priv->rename_file_popover);
1678 }
1679 
1680 static void
rename_file_cb(GSimpleAction * action,GVariant * parameter,gpointer data)1681 rename_file_cb (GSimpleAction *action,
1682                 GVariant      *parameter,
1683                 gpointer       data)
1684 {
1685   GtkFileChooserWidget *impl = data;
1686   GtkFileChooserWidgetPrivate *priv = impl->priv;
1687   GtkTreeSelection *selection;
1688   GtkWidget *prev_default;
1689   GtkWindow *window;
1690 
1691   prev_default = gtk_popover_get_prev_default (GTK_POPOVER (priv->browse_files_popover));
1692   if (prev_default) {
1693     /* set 'default' early so rename popover can get it */
1694     window = GTK_WINDOW (gtk_widget_get_ancestor (priv->browse_files_popover, GTK_TYPE_WINDOW));
1695     if (window)
1696       gtk_window_set_default (window, prev_default);
1697   }
1698   /* insensitive until we change the name */
1699   gtk_widget_set_sensitive (priv->rename_file_rename_button, FALSE);
1700 
1701   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view));
1702   gtk_tree_selection_selected_foreach (selection, rename_selected_cb, impl);
1703 }
1704 
1705 /* callback used to set data to clipboard */
1706 static void
copy_file_get_cb(GtkClipboard * clipboard,GtkSelectionData * selection_data,guint info,gpointer data)1707 copy_file_get_cb  (GtkClipboard     *clipboard,
1708                    GtkSelectionData *selection_data,
1709                    guint             info,
1710                    gpointer          data)
1711 {
1712   GSList *selected_files = data;
1713 
1714   if (selected_files)
1715     {
1716       gint num_files = g_slist_length (selected_files);
1717       gchar **uris;
1718       gint i;
1719       GSList *l;
1720 
1721       uris = g_new (gchar *, num_files + 1);
1722       uris[num_files] = NULL; /* null terminator */
1723 
1724       i = 0;
1725 
1726       for (l = selected_files; l; l = l->next)
1727         {
1728           GFile *file = (GFile *) l->data;
1729 
1730           if (info == SELECTION_URI)
1731             uris[i] = g_file_get_uri (file);
1732           else /* if (info == SELECTION_TEXT) - let this be the fallback */
1733             uris[i] = g_file_get_parse_name (file);
1734 
1735           i++;
1736         }
1737 
1738       if (info == SELECTION_URI)
1739         gtk_selection_data_set_uris (selection_data, uris);
1740       else /* if (info == SELECTION_TEXT) - let this be the fallback */
1741         {
1742           char *str = g_strjoinv (" ", uris);
1743           gtk_selection_data_set_text (selection_data, str, -1);
1744           g_free (str);
1745         }
1746 
1747       g_strfreev (uris);
1748     }
1749 }
1750 
1751 /* callback used to clear the clipboard data */
1752 static void
copy_file_clear_cb(GtkClipboard * clipboard,gpointer data)1753 copy_file_clear_cb (GtkClipboard *clipboard,
1754                     gpointer      data)
1755 {
1756   GSList *selected_files = data;
1757 
1758   g_slist_free_full (selected_files, g_object_unref);
1759 }
1760 
1761 /* Callback used when the "Copy file’s location" menu item is activated */
1762 static void
copy_file_location_cb(GSimpleAction * action,GVariant * parameter,gpointer data)1763 copy_file_location_cb (GSimpleAction *action,
1764                        GVariant      *parameter,
1765                        gpointer       data)
1766 {
1767   GtkFileChooserWidget *impl = data;
1768   GSList *selected_files = NULL;
1769 
1770   selected_files = get_selected_files (impl);
1771 
1772   if (selected_files)
1773     {
1774       GtkClipboard *clipboard;
1775       GtkTargetList *target_list;
1776       GtkTargetEntry *targets;
1777       int n_targets;
1778 
1779       clipboard = gtk_widget_get_clipboard (GTK_WIDGET (impl), GDK_SELECTION_CLIPBOARD);
1780 
1781       target_list = gtk_target_list_new (NULL, 0);
1782       gtk_target_list_add_text_targets (target_list, SELECTION_TEXT);
1783       gtk_target_list_add_uri_targets (target_list, SELECTION_URI);
1784 
1785       targets = gtk_target_table_new_from_list (target_list, &n_targets);
1786       gtk_target_list_unref (target_list);
1787 
1788       gtk_clipboard_set_with_data (clipboard, targets, n_targets,
1789                                    copy_file_get_cb,
1790                                    copy_file_clear_cb,
1791                                    selected_files);
1792 
1793       gtk_target_table_free (targets, n_targets);
1794     }
1795 }
1796 
1797 /* Callback used when the "Visit this file" menu item is activated */
1798 static void
visit_file_cb(GSimpleAction * action,GVariant * parameter,gpointer data)1799 visit_file_cb (GSimpleAction *action,
1800                GVariant      *parameter,
1801                gpointer       data)
1802 {
1803   GtkFileChooserWidget *impl = data;
1804   GSList *files;
1805 
1806   files = get_selected_files (impl);
1807 
1808   /* Sigh, just use the first one */
1809   if (files)
1810     {
1811       GFile *file = files->data;
1812 
1813       gtk_file_chooser_widget_select_file (GTK_FILE_CHOOSER (impl), file, NULL); /* NULL-GError */
1814     }
1815 
1816   g_slist_free_full (files, g_object_unref);
1817 }
1818 
1819 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
1820 /* Callback used when the "Open this folder" menu item is activated */
1821 static void
open_folder_cb(GSimpleAction * action,GVariant * parameter,gpointer data)1822 open_folder_cb (GSimpleAction *action,
1823                 GVariant      *parameter,
1824                 gpointer       data)
1825 {
1826   GtkFileChooserWidget *impl = data;
1827   GSList *files;
1828 
1829   files = get_selected_files (impl);
1830 
1831   /* Sigh, just use the first one */
1832   if (files)
1833     {
1834       GFile *file = files->data;
1835       gchar *uri;
1836 
1837       uri = g_file_get_uri (file);
1838       gtk_show_uri (gtk_widget_get_screen (GTK_WIDGET (impl)), uri, gtk_get_current_event_time (), NULL);
1839       g_free (uri);
1840     }
1841 
1842   g_slist_free_full (files, g_object_unref);
1843 }
1844 G_GNUC_END_IGNORE_DEPRECATIONS
1845 
1846 /* callback used when the "Show Hidden Files" menu item is toggled */
1847 static void
change_show_hidden_state(GSimpleAction * action,GVariant * state,gpointer data)1848 change_show_hidden_state (GSimpleAction *action,
1849                           GVariant      *state,
1850                           gpointer       data)
1851 {
1852   GtkFileChooserWidget *impl = data;
1853 
1854   g_simple_action_set_state (action, state);
1855   g_object_set (impl, "show-hidden", g_variant_get_boolean (state), NULL);
1856 }
1857 
1858 /* Callback used when the "Show Size Column" menu item is toggled */
1859 static void
change_show_size_state(GSimpleAction * action,GVariant * state,gpointer data)1860 change_show_size_state (GSimpleAction *action,
1861                         GVariant      *state,
1862                         gpointer       data)
1863 {
1864   GtkFileChooserWidget *impl = data;
1865   GtkFileChooserWidgetPrivate *priv = impl->priv;
1866 
1867   g_simple_action_set_state (action, state);
1868   priv->show_size_column = g_variant_get_boolean (state);
1869 
1870   gtk_tree_view_column_set_visible (priv->list_size_column,
1871                                     priv->show_size_column);
1872 }
1873 
1874 /* Callback used when the "Show Type Column" menu item is toggled */
1875 static void
change_show_type_state(GSimpleAction * action,GVariant * state,gpointer data)1876 change_show_type_state (GSimpleAction *action,
1877                         GVariant      *state,
1878                         gpointer       data)
1879 {
1880   GtkFileChooserWidget *impl = data;
1881   GtkFileChooserWidgetPrivate *priv = impl->priv;
1882 
1883   g_simple_action_set_state (action, state);
1884   priv->show_type_column = g_variant_get_boolean (state);
1885 
1886   gtk_tree_view_column_set_visible (priv->list_type_column,
1887                                     priv->show_type_column);
1888 }
1889 
1890 static void
change_sort_directories_first_state(GSimpleAction * action,GVariant * state,gpointer data)1891 change_sort_directories_first_state (GSimpleAction *action,
1892                                      GVariant      *state,
1893                                      gpointer       data)
1894 {
1895   GtkFileChooserWidget *impl = data;
1896   GtkFileChooserWidgetPrivate *priv = impl->priv;
1897   GtkTreeSortable *sortable;
1898 
1899   g_simple_action_set_state (action, state);
1900   priv->sort_directories_first = g_variant_get_boolean (state);
1901 
1902   /* force resorting */
1903   sortable = GTK_TREE_SORTABLE (priv->browse_files_model);
1904   if (sortable == NULL)
1905     return;
1906 
1907   gtk_tree_sortable_set_sort_column_id (sortable,
1908                                         GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID,
1909                                         priv->sort_order);
1910   gtk_tree_sortable_set_sort_column_id (sortable,
1911                                         priv->sort_column,
1912                                         priv->sort_order);
1913 }
1914 
1915 static void
clear_model_cache(GtkFileChooserWidget * impl,gint column)1916 clear_model_cache (GtkFileChooserWidget *impl,
1917                    gint                  column)
1918 {
1919   GtkFileChooserWidgetPrivate *priv = impl->priv;
1920 
1921   if (priv->browse_files_model)
1922     _gtk_file_system_model_clear_cache (priv->browse_files_model, column);
1923 
1924   if (priv->search_model)
1925     _gtk_file_system_model_clear_cache (priv->search_model, column);
1926 
1927   if (priv->recent_model)
1928     _gtk_file_system_model_clear_cache (priv->recent_model, column);
1929 }
1930 
1931 static void
set_model_filter(GtkFileChooserWidget * impl,GtkFileFilter * filter)1932 set_model_filter (GtkFileChooserWidget *impl,
1933                   GtkFileFilter        *filter)
1934 {
1935   GtkFileChooserWidgetPrivate *priv = impl->priv;
1936 
1937   if (priv->browse_files_model)
1938     _gtk_file_system_model_set_filter (priv->browse_files_model, filter);
1939 
1940   if (priv->search_model)
1941     _gtk_file_system_model_set_filter (priv->search_model, filter);
1942 
1943   if (priv->recent_model)
1944     _gtk_file_system_model_set_filter (priv->recent_model, filter);
1945 }
1946 
1947 static void
update_time_renderer_visible(GtkFileChooserWidget * impl)1948 update_time_renderer_visible (GtkFileChooserWidget *impl)
1949 {
1950   GtkFileChooserWidgetPrivate *priv = impl->priv;
1951 
1952   g_object_set (priv->list_time_renderer,
1953                 "visible", priv->show_time,
1954                 NULL);
1955   clear_model_cache (impl, MODEL_COL_DATE_TEXT);
1956   clear_model_cache (impl, MODEL_COL_TIME_TEXT);
1957   gtk_widget_queue_draw (priv->browse_files_tree_view);
1958 }
1959 
1960 static void
change_show_time_state(GSimpleAction * action,GVariant * state,gpointer data)1961 change_show_time_state (GSimpleAction *action,
1962                         GVariant      *state,
1963                         gpointer       data)
1964 {
1965   GtkFileChooserWidget *impl = data;
1966   GtkFileChooserWidgetPrivate *priv = impl->priv;
1967 
1968   g_simple_action_set_state (action, state);
1969   priv->show_time = g_variant_get_boolean (state);
1970   update_time_renderer_visible (impl);
1971 }
1972 
1973 /* Shows an error dialog about not being able to select a dragged file */
1974 static void
error_selecting_dragged_file_dialog(GtkFileChooserWidget * impl,GFile * file,GError * error)1975 error_selecting_dragged_file_dialog (GtkFileChooserWidget *impl,
1976                                      GFile                 *file,
1977                                      GError                *error)
1978 {
1979   error_dialog (impl,
1980                 _("Could not select file"),
1981                 error);
1982 }
1983 
1984 static void
file_list_drag_data_select_uris(GtkFileChooserWidget * impl,gchar ** uris)1985 file_list_drag_data_select_uris (GtkFileChooserWidget  *impl,
1986                                  gchar                 **uris)
1987 {
1988   int i;
1989   char *uri;
1990   GtkFileChooser *chooser = GTK_FILE_CHOOSER (impl);
1991 
1992   for (i = 1; uris[i]; i++)
1993     {
1994       GFile *file;
1995       GError *error = NULL;
1996 
1997       uri = uris[i];
1998       file = g_file_new_for_uri (uri);
1999 
2000       gtk_file_chooser_widget_select_file (chooser, file, &error);
2001       if (error)
2002         error_selecting_dragged_file_dialog (impl, file, error);
2003 
2004       g_object_unref (file);
2005     }
2006 }
2007 
2008 struct FileListDragData
2009 {
2010   GtkFileChooserWidget *impl;
2011   gchar **uris;
2012   GFile *file;
2013 };
2014 
2015 static void
file_list_drag_data_received_get_info_cb(GCancellable * cancellable,GFileInfo * info,const GError * error,gpointer user_data)2016 file_list_drag_data_received_get_info_cb (GCancellable *cancellable,
2017                                           GFileInfo    *info,
2018                                           const GError *error,
2019                                           gpointer      user_data)
2020 {
2021   gboolean cancelled = g_cancellable_is_cancelled (cancellable);
2022   struct FileListDragData *data = user_data;
2023   GtkFileChooser *chooser = GTK_FILE_CHOOSER (data->impl);
2024   GtkFileChooserWidgetPrivate *priv = data->impl->priv;
2025 
2026   if (cancellable != priv->file_list_drag_data_received_cancellable)
2027     goto out;
2028 
2029   priv->file_list_drag_data_received_cancellable = NULL;
2030 
2031   if (cancelled || error)
2032     goto out;
2033 
2034   if ((priv->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
2035        priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) &&
2036       data->uris[1] == 0 && !error && _gtk_file_info_consider_as_directory (info))
2037     change_folder_and_display_error (data->impl, data->file, FALSE);
2038   else
2039     {
2040       GError *local_error = NULL;
2041 
2042       gtk_file_chooser_widget_unselect_all (chooser);
2043       gtk_file_chooser_widget_select_file (chooser, data->file, &local_error);
2044       if (local_error)
2045         error_selecting_dragged_file_dialog (data->impl, data->file, local_error);
2046       else
2047         browse_files_center_selected_row (data->impl);
2048     }
2049 
2050   if (priv->select_multiple)
2051     file_list_drag_data_select_uris (data->impl, data->uris);
2052 
2053 out:
2054   g_object_unref (data->impl);
2055   g_strfreev (data->uris);
2056   g_object_unref (data->file);
2057   g_free (data);
2058 
2059   g_object_unref (cancellable);
2060 }
2061 
2062 static void
file_list_drag_data_received_cb(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * selection_data,guint info,guint time_,gpointer user_data)2063 file_list_drag_data_received_cb (GtkWidget        *widget,
2064                                  GdkDragContext   *context,
2065                                  gint              x,
2066                                  gint              y,
2067                                  GtkSelectionData *selection_data,
2068                                  guint             info,
2069                                  guint             time_,
2070                                  gpointer          user_data)
2071 {
2072   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (user_data);
2073   GtkFileChooserWidgetPrivate *priv = impl->priv;
2074   gchar **uris;
2075   char *uri;
2076   GFile *file;
2077 
2078   /* Allow only drags from other widgets; see bug #533891. */
2079   if (gtk_drag_get_source_widget (context) == widget)
2080     {
2081       g_signal_stop_emission_by_name (widget, "drag-data-received");
2082       return;
2083     }
2084 
2085   /* Parse the text/uri-list string, navigate to the first one */
2086   uris = gtk_selection_data_get_uris (selection_data);
2087   if (uris && uris[0])
2088     {
2089       struct FileListDragData *data;
2090 
2091       uri = uris[0];
2092       file = g_file_new_for_uri (uri);
2093 
2094       data = g_new0 (struct FileListDragData, 1);
2095       data->impl = g_object_ref (impl);
2096       data->uris = uris;
2097       data->file = file;
2098 
2099       if (priv->file_list_drag_data_received_cancellable)
2100         g_cancellable_cancel (priv->file_list_drag_data_received_cancellable);
2101 
2102       priv->file_list_drag_data_received_cancellable =
2103         _gtk_file_system_get_info (priv->file_system, file,
2104                                    "standard::type",
2105                                    file_list_drag_data_received_get_info_cb,
2106                                    data);
2107     }
2108 
2109   g_signal_stop_emission_by_name (widget, "drag-data-received");
2110 }
2111 
2112 /* Don't do anything with the drag_drop signal */
2113 static gboolean
file_list_drag_drop_cb(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time_,GtkFileChooserWidget * impl)2114 file_list_drag_drop_cb (GtkWidget             *widget,
2115                         GdkDragContext        *context,
2116                         gint                   x,
2117                         gint                   y,
2118                         guint                  time_,
2119                         GtkFileChooserWidget *impl)
2120 {
2121   g_signal_stop_emission_by_name (widget, "drag-drop");
2122   return TRUE;
2123 }
2124 
2125 static void
file_list_drag_begin_cb(GtkWidget * widget,GdkDragContext * context,GtkFileChooserWidget * impl)2126 file_list_drag_begin_cb (GtkWidget            *widget,
2127                          GdkDragContext       *context,
2128                          GtkFileChooserWidget *impl)
2129 {
2130   gtk_places_sidebar_set_drop_targets_visible (GTK_PLACES_SIDEBAR (impl->priv->places_sidebar),
2131                                                TRUE,
2132                                                context);
2133 }
2134 
2135 /* Disable the normal tree drag motion handler, it makes it look like you're
2136    dropping the dragged item onto a tree item */
2137 static gboolean
file_list_drag_motion_cb(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time_,GtkFileChooserWidget * impl)2138 file_list_drag_motion_cb (GtkWidget             *widget,
2139                           GdkDragContext        *context,
2140                           gint                   x,
2141                           gint                   y,
2142                           guint                  time_,
2143                           GtkFileChooserWidget *impl)
2144 {
2145   g_signal_stop_emission_by_name (widget, "drag-motion");
2146   return TRUE;
2147 }
2148 
2149 static void
file_list_drag_end_cb(GtkWidget * widget,GdkDragContext * context,gpointer user_data)2150 file_list_drag_end_cb (GtkWidget      *widget,
2151                        GdkDragContext *context,
2152                        gpointer        user_data)
2153 {
2154   GtkFileChooserWidget *impl;
2155 
2156   impl = GTK_FILE_CHOOSER_WIDGET (user_data);
2157   gtk_places_sidebar_set_drop_targets_visible (GTK_PLACES_SIDEBAR (impl->priv->places_sidebar),
2158                                                FALSE,
2159                                                context);
2160 }
2161 
2162 /* Sensitizes the "Copy file’s location" and other context menu items if there is actually
2163  * a selection active.
2164  */
2165 static void
check_file_list_popover_sensitivity(GtkFileChooserWidget * impl)2166 check_file_list_popover_sensitivity (GtkFileChooserWidget *impl)
2167 {
2168   GtkFileChooserWidgetPrivate *priv = impl->priv;
2169   gint num_selected;
2170   gboolean all_files;
2171   gboolean all_folders;
2172   gboolean active;
2173   GActionGroup *actions;
2174   GAction *action, *action2;
2175 
2176   actions = gtk_widget_get_action_group (priv->browse_files_tree_view, "item");
2177 
2178   selection_check (impl, &num_selected, &all_files, &all_folders);
2179 
2180   active = (num_selected != 0);
2181 
2182   action = g_action_map_lookup_action (G_ACTION_MAP (actions), "copy-location");
2183   g_simple_action_set_enabled (G_SIMPLE_ACTION (action), active);
2184 
2185   action = g_action_map_lookup_action (G_ACTION_MAP (actions), "add-shortcut");
2186   g_simple_action_set_enabled (G_SIMPLE_ACTION (action), active && all_folders);
2187 
2188   action = g_action_map_lookup_action (G_ACTION_MAP (actions), "visit");
2189   g_simple_action_set_enabled (G_SIMPLE_ACTION (action), active);
2190 
2191   action = g_action_map_lookup_action (G_ACTION_MAP (actions), "open");
2192   g_simple_action_set_enabled (G_SIMPLE_ACTION (action), (num_selected == 1) && all_folders);
2193 
2194   action = g_action_map_lookup_action (G_ACTION_MAP (actions), "rename");
2195   if (num_selected == 1)
2196     {
2197       GSList *infos;
2198       GFileInfo *info;
2199 
2200       infos = get_selected_infos (impl);
2201       info = G_FILE_INFO (infos->data);
2202 
2203       g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
2204                                    g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME));
2205 
2206       g_slist_free_full (infos, g_object_unref);
2207     }
2208   else
2209     g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
2210 
2211   action = g_action_map_lookup_action (G_ACTION_MAP (actions), "delete");
2212   action2 = g_action_map_lookup_action (G_ACTION_MAP (actions), "trash");
2213 
2214   if (num_selected == 1)
2215     {
2216       GSList *infos;
2217       GFileInfo *info;
2218 
2219       infos = get_selected_infos (impl);
2220       info = G_FILE_INFO (infos->data);
2221 
2222       if (g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH))
2223         {
2224           g_simple_action_set_enabled (G_SIMPLE_ACTION (action2), TRUE);
2225           gtk_widget_set_visible (priv->delete_file_item, FALSE);
2226           gtk_widget_set_visible (priv->trash_file_item, TRUE);
2227         }
2228       else if (g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE))
2229         {
2230           g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE);
2231           gtk_widget_set_visible (priv->delete_file_item, TRUE);
2232           gtk_widget_set_visible (priv->trash_file_item, FALSE);
2233         }
2234       else
2235         {
2236           g_simple_action_set_enabled (G_SIMPLE_ACTION (action2), FALSE);
2237           gtk_widget_set_visible (priv->delete_file_item, FALSE);
2238           gtk_widget_set_visible (priv->trash_file_item, TRUE);
2239         }
2240 
2241       g_slist_free_full (infos, g_object_unref);
2242     }
2243   else
2244     {
2245       gtk_widget_set_visible (priv->delete_file_item, FALSE);
2246       gtk_widget_set_visible (priv->trash_file_item, TRUE);
2247       g_simple_action_set_enabled (G_SIMPLE_ACTION (action2), FALSE);
2248     }
2249 }
2250 
2251 static GActionEntry entries[] = {
2252   { "visit", visit_file_cb, NULL, NULL, NULL },
2253   { "open", open_folder_cb, NULL, NULL, NULL },
2254   { "copy-location", copy_file_location_cb, NULL, NULL, NULL },
2255   { "add-shortcut", add_to_shortcuts_cb, NULL, NULL, NULL },
2256   { "rename", rename_file_cb, NULL, NULL, NULL },
2257   { "delete", delete_file_cb, NULL, NULL, NULL },
2258   { "trash", trash_file_cb, NULL, NULL, NULL },
2259   { "toggle-show-hidden", NULL, NULL, "false", change_show_hidden_state },
2260   { "toggle-show-size", NULL, NULL, "false", change_show_size_state },
2261   { "toggle-show-type", NULL, NULL, "false", change_show_type_state },
2262   { "toggle-show-time", NULL, NULL, "false", change_show_time_state },
2263   { "toggle-sort-dirs-first", NULL, NULL, "false", change_sort_directories_first_state }
2264 };
2265 
2266 static void
add_actions(GtkFileChooserWidget * impl)2267 add_actions (GtkFileChooserWidget *impl)
2268 {
2269   GActionGroup *actions;
2270 
2271   actions = G_ACTION_GROUP (g_simple_action_group_new ());
2272   g_action_map_add_action_entries (G_ACTION_MAP (actions),
2273                                    entries, G_N_ELEMENTS (entries),
2274                                    impl);
2275   gtk_widget_insert_action_group (GTK_WIDGET (impl->priv->browse_files_tree_view), "item", actions);
2276   g_object_unref (actions);
2277 }
2278 
2279 static GtkWidget *
append_separator(GtkWidget * box)2280 append_separator (GtkWidget *box)
2281 {
2282   GtkWidget *separator;
2283 
2284   separator = g_object_new (GTK_TYPE_SEPARATOR,
2285                             "orientation", GTK_ORIENTATION_HORIZONTAL,
2286                             "visible", TRUE,
2287                             "margin-start", 12,
2288                             "margin-end", 12,
2289                             "margin-top", 6,
2290                             "margin-bottom", 6,
2291                             NULL);
2292   gtk_container_add (GTK_CONTAINER (box), separator);
2293 
2294   return separator;
2295 }
2296 
2297 /* Constructs the popup menu for the file list if needed */
2298 static GtkWidget *
add_button(GtkWidget * box,const gchar * label,const gchar * action)2299 add_button (GtkWidget   *box,
2300             const gchar *label,
2301             const gchar *action)
2302 {
2303   GtkWidget *item;
2304 
2305  item = g_object_new (GTK_TYPE_MODEL_BUTTON,
2306                        "visible", TRUE,
2307                        "action-name", action,
2308                        "text", label,
2309                        NULL);
2310   gtk_container_add (GTK_CONTAINER (box), item);
2311 
2312   return item;
2313 }
2314 
2315 static void
file_list_build_popover(GtkFileChooserWidget * impl)2316 file_list_build_popover (GtkFileChooserWidget *impl)
2317 {
2318   GtkFileChooserWidgetPrivate *priv = impl->priv;
2319   GtkWidget *box;
2320 
2321   if (priv->browse_files_popover)
2322     return;
2323 
2324   priv->browse_files_popover = gtk_popover_new (priv->browse_files_tree_view);
2325   box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
2326   g_object_set (box, "margin", 10, NULL);
2327   gtk_widget_show (box);
2328   gtk_container_add (GTK_CONTAINER (priv->browse_files_popover), box);
2329 
2330   priv->visit_file_item = add_button (box, _("_Visit File"), "item.visit");
2331   priv->open_folder_item = add_button (box, _("_Open With File Manager"), "item.open");
2332   priv->copy_file_location_item = add_button (box, _("_Copy Location"), "item.copy-location");
2333   priv->add_shortcut_item = add_button (box, _("_Add to Bookmarks"), "item.add-shortcut");
2334   priv->rename_file_item = add_button (box, _("_Rename"), "item.rename");
2335   priv->delete_file_item = add_button (box, _("_Delete"), "item.delete");
2336   priv->trash_file_item = add_button (box, _("_Move to Trash"), "item.trash");
2337 
2338   append_separator (box);
2339 
2340   priv->hidden_files_item = add_button (box, _("Show _Hidden Files"), "item.toggle-show-hidden");
2341   priv->size_column_item = add_button (box, _("Show _Size Column"), "item.toggle-show-size");
2342   priv->type_column_item = add_button (box, _("Show T_ype Column"), "item.toggle-show-type");
2343   priv->show_time_item = add_button (box, _("Show _Time"), "item.toggle-show-time");
2344   priv->sort_directories_item = add_button (box, _("Sort _Folders before Files"), "item.toggle-sort-dirs-first");
2345 }
2346 
2347 /* Updates the popover for the file list, creating it if necessary */
2348 static void
file_list_update_popover(GtkFileChooserWidget * impl)2349 file_list_update_popover (GtkFileChooserWidget *impl)
2350 {
2351   GtkFileChooserWidgetPrivate *priv = impl->priv;
2352   GActionGroup *actions;
2353   GAction *action;
2354 
2355   file_list_build_popover (impl);
2356   check_file_list_popover_sensitivity (impl);
2357 
2358   /* The sensitivity of the Add to Bookmarks item is set in
2359    * bookmarks_check_add_sensitivity()
2360    */
2361 
2362   if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
2363       priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER ||
2364       priv->operation_mode != OPERATION_MODE_BROWSE)
2365     {
2366       gtk_widget_set_visible (priv->rename_file_item, FALSE);
2367       gtk_widget_set_visible (priv->delete_file_item, FALSE);
2368       gtk_widget_set_visible (priv->trash_file_item, FALSE);
2369     }
2370 
2371   gtk_widget_set_visible (priv->visit_file_item, (priv->operation_mode != OPERATION_MODE_BROWSE));
2372 
2373   actions = gtk_widget_get_action_group (priv->browse_files_tree_view, "item");
2374   action = g_action_map_lookup_action (G_ACTION_MAP (actions), "toggle-show-hidden");
2375   g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (priv->show_hidden));
2376 
2377   action = g_action_map_lookup_action (G_ACTION_MAP (actions), "toggle-show-size");
2378   g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (priv->show_size_column));
2379 
2380   action = g_action_map_lookup_action (G_ACTION_MAP (actions), "toggle-show-type");
2381   g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (priv->show_type_column));
2382 
2383   action = g_action_map_lookup_action (G_ACTION_MAP (actions), "toggle-show-time");
2384   g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (priv->show_time));
2385 
2386   action = g_action_map_lookup_action (G_ACTION_MAP (actions), "toggle-sort-dirs-first");
2387   g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (priv->sort_directories_first));
2388 }
2389 
2390 static void
file_list_show_popover(GtkFileChooserWidget * impl,gdouble x,gdouble y)2391 file_list_show_popover (GtkFileChooserWidget *impl,
2392                         gdouble               x,
2393                         gdouble               y)
2394 {
2395   GtkFileChooserWidgetPrivate *priv = impl->priv;
2396   GdkRectangle rect;
2397   GtkTreeSelection *selection;
2398   GtkTreeModel *model;
2399   GList *list;
2400   GtkTreePath *path;
2401 
2402 
2403   file_list_update_popover (impl);
2404 
2405   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view));
2406   list = gtk_tree_selection_get_selected_rows (selection, &model);
2407   if (list)
2408     {
2409       path = list->data;
2410       gtk_tree_view_get_cell_area (GTK_TREE_VIEW (priv->browse_files_tree_view), path, NULL, &rect);
2411       gtk_tree_view_convert_bin_window_to_widget_coords (GTK_TREE_VIEW (priv->browse_files_tree_view),
2412                                                      rect.x, rect.y, &rect.x, &rect.y);
2413 
2414       rect.x = CLAMP (x - 20, 0, gtk_widget_get_allocated_width (priv->browse_files_tree_view) - 40);
2415       rect.width = 40;
2416 
2417       g_list_free_full (list, (GDestroyNotify) gtk_tree_path_free);
2418     }
2419   else
2420     {
2421       rect.x = x;
2422       rect.y = y;
2423       rect.width = 1;
2424       rect.height = 1;
2425     }
2426 
2427   gtk_popover_set_pointing_to (GTK_POPOVER (priv->browse_files_popover), &rect);
2428   gtk_popover_popup (GTK_POPOVER (priv->browse_files_popover));
2429 }
2430 
2431 /* Callback used for the GtkWidget::popup-menu signal of the file list */
2432 static gboolean
list_popup_menu_cb(GtkWidget * widget,GtkFileChooserWidget * impl)2433 list_popup_menu_cb (GtkWidget            *widget,
2434                     GtkFileChooserWidget *impl)
2435 {
2436   GtkFileChooserWidgetPrivate *priv = impl->priv;
2437 
2438   file_list_show_popover (impl,
2439                           0.5 * gtk_widget_get_allocated_width (GTK_WIDGET (priv->browse_files_tree_view)),
2440                           0.5 * gtk_widget_get_allocated_height (GTK_WIDGET (priv->browse_files_tree_view)));
2441   return TRUE;
2442 }
2443 
2444 /* Callback used when a button is pressed on the file list.  We trap button 3 to
2445  * bring up a popup menu.
2446  */
2447 static gboolean
list_button_press_event_cb(GtkWidget * widget,GdkEventButton * event,GtkFileChooserWidget * impl)2448 list_button_press_event_cb (GtkWidget            *widget,
2449                             GdkEventButton       *event,
2450                             GtkFileChooserWidget *impl)
2451 {
2452   GtkFileChooserWidgetPrivate *priv = impl->priv;
2453   static gboolean in_press = FALSE;
2454 
2455   if (priv->browse_files_interaction_frozen)
2456     return GDK_EVENT_STOP;
2457 
2458   if (in_press)
2459     return FALSE;
2460 
2461   if (!gdk_event_triggers_context_menu ((GdkEvent *) event))
2462     return FALSE;
2463 
2464   in_press = TRUE;
2465   gtk_widget_event (priv->browse_files_tree_view, (GdkEvent *) event);
2466   in_press = FALSE;
2467 
2468   file_list_show_popover (impl, event->x, event->y);
2469 
2470   return TRUE;
2471 }
2472 
2473 static void
long_press_cb(GtkGesture * gesture,gdouble x,gdouble y,GtkFileChooserWidget * impl)2474 long_press_cb (GtkGesture           *gesture,
2475                gdouble               x,
2476                gdouble               y,
2477                GtkFileChooserWidget *impl)
2478 {
2479   file_list_show_popover (impl, x, y);
2480 }
2481 
2482 typedef struct {
2483   OperationMode operation_mode;
2484   gint general_column;
2485   gint model_column;
2486 } ColumnMap;
2487 
2488 /* Sets the sort column IDs for the file list; needs to be done whenever we
2489  * change the model on the treeview.
2490  */
2491 static void
file_list_set_sort_column_ids(GtkFileChooserWidget * impl)2492 file_list_set_sort_column_ids (GtkFileChooserWidget *impl)
2493 {
2494   GtkFileChooserWidgetPrivate *priv = impl->priv;
2495 
2496   gtk_tree_view_set_search_column (GTK_TREE_VIEW (priv->browse_files_tree_view), -1);
2497 
2498   gtk_tree_view_column_set_sort_column_id (priv->list_name_column, MODEL_COL_NAME);
2499   gtk_tree_view_column_set_sort_column_id (priv->list_time_column, MODEL_COL_TIME);
2500   gtk_tree_view_column_set_sort_column_id (priv->list_size_column, MODEL_COL_SIZE);
2501   gtk_tree_view_column_set_sort_column_id (priv->list_type_column, MODEL_COL_TYPE);
2502   gtk_tree_view_column_set_sort_column_id (priv->list_location_column, MODEL_COL_LOCATION_TEXT);
2503 }
2504 
2505 static gboolean
file_list_query_tooltip_cb(GtkWidget * widget,gint x,gint y,gboolean keyboard_tip,GtkTooltip * tooltip,gpointer user_data)2506 file_list_query_tooltip_cb (GtkWidget  *widget,
2507                             gint        x,
2508                             gint        y,
2509                             gboolean    keyboard_tip,
2510                             GtkTooltip *tooltip,
2511                             gpointer    user_data)
2512 {
2513   GtkFileChooserWidget *impl = user_data;
2514   GtkFileChooserWidgetPrivate *priv = impl->priv;
2515   GtkTreeModel *model;
2516   GtkTreePath *path;
2517   GtkTreeIter iter;
2518   GFile *file;
2519   gchar *filename;
2520 
2521   if (priv->operation_mode == OPERATION_MODE_BROWSE)
2522     return FALSE;
2523 
2524 
2525   if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (priv->browse_files_tree_view),
2526                                           &x, &y,
2527                                           keyboard_tip,
2528                                           &model, &path, &iter))
2529     return FALSE;
2530 
2531   gtk_tree_model_get (model, &iter,
2532                       MODEL_COL_FILE, &file,
2533                       -1);
2534 
2535   if (file == NULL)
2536     {
2537       gtk_tree_path_free (path);
2538       return FALSE;
2539     }
2540 
2541   filename = g_file_get_path (file);
2542   gtk_tooltip_set_text (tooltip, filename);
2543   gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (priv->browse_files_tree_view),
2544                                  tooltip,
2545                                  path);
2546 
2547   g_free (filename);
2548   g_object_unref (file);
2549   gtk_tree_path_free (path);
2550 
2551   return TRUE;
2552 }
2553 
2554 static void
set_icon_cell_renderer_fixed_size(GtkFileChooserWidget * impl)2555 set_icon_cell_renderer_fixed_size (GtkFileChooserWidget *impl)
2556 {
2557   GtkFileChooserWidgetPrivate *priv = impl->priv;
2558   gint xpad, ypad;
2559 
2560   gtk_cell_renderer_get_padding (priv->list_pixbuf_renderer, &xpad, &ypad);
2561   gtk_cell_renderer_set_fixed_size (priv->list_pixbuf_renderer,
2562                                     xpad * 2 + priv->icon_size,
2563                                     ypad * 2 + priv->icon_size);
2564 }
2565 
2566 static gboolean
location_changed_timeout_cb(gpointer user_data)2567 location_changed_timeout_cb (gpointer user_data)
2568 {
2569   GtkFileChooserWidget *impl = user_data;
2570   GtkFileChooserWidgetPrivate *priv = impl->priv;
2571 
2572   gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (impl));
2573   check_preview_change (impl);
2574   g_signal_emit_by_name (impl, "selection-changed", 0);
2575 
2576   priv->location_changed_id = 0;
2577 
2578   return G_SOURCE_REMOVE;
2579 }
2580 
2581 static void
reset_location_timeout(GtkFileChooserWidget * impl)2582 reset_location_timeout (GtkFileChooserWidget *impl)
2583 {
2584   GtkFileChooserWidgetPrivate *priv = impl->priv;
2585 
2586   if (priv->location_changed_id > 0)
2587     g_source_remove (priv->location_changed_id);
2588   priv->location_changed_id = g_timeout_add (LOCATION_CHANGED_TIMEOUT,
2589                                             location_changed_timeout_cb,
2590                                             impl);
2591   g_source_set_name_by_id (priv->location_changed_id, "[gtk+] location_changed_timeout_cb");
2592 }
2593 
2594 static void
location_entry_changed_cb(GtkEditable * editable,GtkFileChooserWidget * impl)2595 location_entry_changed_cb (GtkEditable          *editable,
2596                            GtkFileChooserWidget *impl)
2597 {
2598   GtkFileChooserWidgetPrivate *priv = impl->priv;
2599 
2600   if (priv->operation_mode == OPERATION_MODE_SEARCH)
2601     {
2602       operation_mode_set (impl, OPERATION_MODE_BROWSE);
2603       if (priv->current_folder)
2604         change_folder_and_display_error (impl, priv->current_folder, FALSE);
2605       else
2606         switch_to_home_dir (impl);
2607     }
2608 
2609   if (priv->action != GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
2610     reset_location_timeout (impl);
2611 }
2612 
2613 static void
location_entry_close_clicked(GtkFileChooserWidget * impl)2614 location_entry_close_clicked (GtkFileChooserWidget *impl)
2615 {
2616   location_mode_set (impl, LOCATION_MODE_PATH_BAR);
2617 }
2618 
2619 static void
location_entry_setup(GtkFileChooserWidget * impl)2620 location_entry_setup (GtkFileChooserWidget *impl)
2621 {
2622   GtkFileChooserWidgetPrivate *priv = impl->priv;
2623 
2624   if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
2625       priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
2626     gtk_entry_set_placeholder_text (GTK_ENTRY (priv->location_entry), _("Location"));
2627 
2628   g_signal_connect (priv->location_entry, "changed",
2629                     G_CALLBACK (location_entry_changed_cb), impl);
2630   g_signal_connect_swapped (priv->location_entry, "hide-entry",
2631                             G_CALLBACK (location_entry_close_clicked), impl);
2632 
2633   _gtk_file_chooser_entry_set_local_only (GTK_FILE_CHOOSER_ENTRY (priv->location_entry), priv->local_only);
2634   _gtk_file_chooser_entry_set_action (GTK_FILE_CHOOSER_ENTRY (priv->location_entry), priv->action);
2635   _gtk_file_chooser_entry_set_file_filter (GTK_FILE_CHOOSER_ENTRY (priv->location_entry),
2636                                            priv->current_filter);
2637   gtk_entry_set_width_chars (GTK_ENTRY (priv->location_entry), 45);
2638   gtk_entry_set_activates_default (GTK_ENTRY (priv->location_entry), TRUE);
2639 }
2640 
2641 static void
location_entry_disconnect(GtkFileChooserWidget * impl)2642 location_entry_disconnect (GtkFileChooserWidget *impl)
2643 {
2644   GtkFileChooserWidgetPrivate *priv = impl->priv;
2645 
2646   if (priv->location_entry)
2647     g_signal_handlers_disconnect_by_func (priv->location_entry, location_entry_changed_cb, impl);
2648 }
2649 
2650 static void
location_entry_create(GtkFileChooserWidget * impl)2651 location_entry_create (GtkFileChooserWidget *impl)
2652 {
2653   GtkFileChooserWidgetPrivate *priv = impl->priv;
2654 
2655   if (!priv->location_entry)
2656     {
2657       gboolean eat_escape;
2658 
2659       eat_escape = priv->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
2660                    priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
2661 
2662       priv->location_entry = _gtk_file_chooser_entry_new (TRUE, eat_escape);
2663       location_entry_setup (impl);
2664     }
2665 }
2666 
2667 static gboolean
external_entry_key_press(GtkWidget * entry,GdkEventKey * event,GtkFileChooserWidget * impl)2668 external_entry_key_press (GtkWidget            *entry,
2669                           GdkEventKey          *event,
2670                           GtkFileChooserWidget *impl)
2671 {
2672   /* Since the entry is not a descendent of the file chooser widget
2673    * in this case, we need to manually make our bindings apply.
2674    */
2675   return gtk_bindings_activate_event (G_OBJECT (impl), event);
2676 }
2677 
2678 /* Creates the widgets specific to Save mode */
2679 static void
save_widgets_create(GtkFileChooserWidget * impl)2680 save_widgets_create (GtkFileChooserWidget *impl)
2681 {
2682   GtkFileChooserWidgetPrivate *priv = impl->priv;
2683   GtkWidget *vbox;
2684   GtkWidget *widget;
2685 
2686   if (priv->save_widgets != NULL ||
2687       (priv->external_entry && priv->location_entry == priv->external_entry))
2688     return;
2689 
2690   location_switch_to_path_bar (impl);
2691 
2692   gtk_places_sidebar_set_location (GTK_PLACES_SIDEBAR (priv->places_sidebar), priv->current_folder);
2693 
2694   if (priv->external_entry)
2695     {
2696       location_entry_disconnect (impl);
2697       priv->location_entry = priv->external_entry;
2698       location_entry_setup (impl);
2699 
2700       g_signal_connect_after (priv->external_entry, "key-press-event",
2701                               G_CALLBACK (external_entry_key_press), impl);
2702       return;
2703     }
2704 
2705   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
2706   gtk_style_context_add_class (gtk_widget_get_style_context (vbox), "search-bar");
2707 
2708   gtk_container_set_border_width (GTK_CONTAINER (vbox), 0);
2709 
2710   priv->save_widgets_table = gtk_grid_new ();
2711   gtk_container_set_border_width (GTK_CONTAINER (priv->save_widgets_table), 10);
2712   gtk_box_pack_start (GTK_BOX (vbox), priv->save_widgets_table, FALSE, FALSE, 0);
2713   gtk_widget_show (priv->save_widgets_table);
2714   gtk_grid_set_row_spacing (GTK_GRID (priv->save_widgets_table), 12);
2715   gtk_grid_set_column_spacing (GTK_GRID (priv->save_widgets_table), 12);
2716 
2717   /* Label */
2718 
2719   widget = gtk_label_new_with_mnemonic (_("_Name:"));
2720   gtk_widget_set_halign (widget, GTK_ALIGN_START);
2721   gtk_widget_set_valign (widget, GTK_ALIGN_CENTER);
2722   gtk_grid_attach (GTK_GRID (priv->save_widgets_table), widget, 0, 0, 1, 1);
2723   gtk_widget_show (widget);
2724 
2725   /* Location entry */
2726 
2727   location_entry_create (impl);
2728   gtk_widget_set_hexpand (priv->location_entry, TRUE);
2729   gtk_grid_attach (GTK_GRID (priv->save_widgets_table), priv->location_entry, 1, 0, 1, 1);
2730   gtk_widget_show (priv->location_entry);
2731   gtk_label_set_mnemonic_widget (GTK_LABEL (widget), priv->location_entry);
2732 
2733   priv->save_widgets = vbox;
2734   gtk_box_pack_start (GTK_BOX (impl), priv->save_widgets, FALSE, FALSE, 0);
2735   gtk_box_reorder_child (GTK_BOX (impl), priv->save_widgets, 0);
2736   gtk_widget_show (priv->save_widgets);
2737 }
2738 
2739 /* Destroys the widgets specific to Save mode */
2740 static void
save_widgets_destroy(GtkFileChooserWidget * impl)2741 save_widgets_destroy (GtkFileChooserWidget *impl)
2742 {
2743   GtkFileChooserWidgetPrivate *priv = impl->priv;
2744 
2745   if (priv->external_entry && priv->external_entry == priv->location_entry)
2746     {
2747       g_signal_handlers_disconnect_by_func (priv->external_entry, external_entry_key_press, impl);
2748 
2749       location_entry_disconnect (impl);
2750       priv->location_entry = NULL;
2751     }
2752 
2753   if (priv->save_widgets == NULL)
2754     return;
2755 
2756   gtk_widget_destroy (priv->save_widgets);
2757   priv->save_widgets = NULL;
2758   priv->save_widgets_table = NULL;
2759   priv->location_entry = NULL;
2760 }
2761 
2762 /* Turns on the path bar widget.  Can be called even if we are already in that
2763  * mode.
2764  */
2765 static void
location_switch_to_path_bar(GtkFileChooserWidget * impl)2766 location_switch_to_path_bar (GtkFileChooserWidget *impl)
2767 {
2768   GtkFileChooserWidgetPrivate *priv = impl->priv;
2769 
2770   if (priv->location_entry)
2771     {
2772       gtk_widget_destroy (priv->location_entry);
2773       priv->location_entry = NULL;
2774     }
2775 
2776   gtk_stack_set_visible_child_name (GTK_STACK (priv->browse_header_stack), "pathbar");
2777 }
2778 
2779 /* Turns on the location entry.  Can be called even if we are already in that
2780  * mode.
2781  */
2782 static void
location_switch_to_filename_entry(GtkFileChooserWidget * impl)2783 location_switch_to_filename_entry (GtkFileChooserWidget *impl)
2784 {
2785   GtkFileChooserWidgetPrivate *priv = impl->priv;
2786 
2787   /* when in search or recent files mode, we are not showing the
2788    * browse_header_box container, so there's no point in switching
2789    * to it.
2790    */
2791   if (priv->operation_mode == OPERATION_MODE_SEARCH ||
2792       priv->operation_mode == OPERATION_MODE_RECENT)
2793     return;
2794 
2795   gtk_revealer_set_reveal_child (GTK_REVEALER (priv->browse_header_revealer), TRUE);
2796 
2797   if (!priv->location_entry)
2798     {
2799       location_entry_create (impl);
2800       gtk_box_pack_start (GTK_BOX (priv->location_entry_box), priv->location_entry, TRUE, TRUE, 0);
2801     }
2802 
2803   _gtk_file_chooser_entry_set_base_folder (GTK_FILE_CHOOSER_ENTRY (priv->location_entry), priv->current_folder);
2804 
2805   gtk_widget_show (priv->location_entry);
2806 
2807   gtk_stack_set_visible_child_name (GTK_STACK (priv->browse_header_stack), "location");
2808 
2809   gtk_widget_grab_focus (priv->location_entry);
2810 }
2811 
2812 /* Sets a new location mode.
2813  */
2814 static void
location_mode_set(GtkFileChooserWidget * impl,LocationMode new_mode)2815 location_mode_set (GtkFileChooserWidget *impl,
2816                    LocationMode new_mode)
2817 {
2818   GtkFileChooserWidgetPrivate *priv = impl->priv;
2819 
2820   if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
2821       priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
2822     {
2823       GtkWindow *toplevel;
2824       GtkWidget *current_focus;
2825       gboolean switch_to_file_list;
2826 
2827       switch (new_mode)
2828         {
2829         case LOCATION_MODE_PATH_BAR:
2830 
2831           /* The location_entry will disappear when we switch to path bar mode.  So,
2832            * we'll focus the file list in that case, to avoid having a window with
2833            * no focused widget.
2834            */
2835           toplevel = get_toplevel (GTK_WIDGET (impl));
2836           switch_to_file_list = FALSE;
2837           if (toplevel)
2838             {
2839               current_focus = gtk_window_get_focus (toplevel);
2840               if (!current_focus || current_focus == priv->location_entry)
2841                 switch_to_file_list = TRUE;
2842             }
2843 
2844           location_switch_to_path_bar (impl);
2845 
2846           if (switch_to_file_list)
2847             gtk_widget_grab_focus (priv->browse_files_tree_view);
2848 
2849           break;
2850 
2851         case LOCATION_MODE_FILENAME_ENTRY:
2852           location_switch_to_filename_entry (impl);
2853           break;
2854 
2855         default:
2856           g_assert_not_reached ();
2857           return;
2858         }
2859     }
2860 
2861   priv->location_mode = new_mode;
2862   g_object_notify (G_OBJECT (impl), "subtitle");
2863 }
2864 
2865 /* Callback used when the places sidebar asks us to show other locations */
2866 static void
places_sidebar_show_other_locations_with_flags_cb(GtkPlacesSidebar * sidebar,GtkPlacesOpenFlags open_flags,GtkFileChooserWidget * impl)2867 places_sidebar_show_other_locations_with_flags_cb (GtkPlacesSidebar     *sidebar,
2868                                                    GtkPlacesOpenFlags    open_flags,
2869                                                    GtkFileChooserWidget *impl)
2870 {
2871   GtkFileChooserWidgetPrivate *priv = impl->priv;
2872 
2873   priv->preview_widget_active = FALSE;
2874 
2875   update_preview_widget_visibility (impl);
2876 
2877   operation_mode_set (impl, OPERATION_MODE_OTHER_LOCATIONS);
2878 }
2879 
2880 static void
location_toggle_popup_handler(GtkFileChooserWidget * impl)2881 location_toggle_popup_handler (GtkFileChooserWidget *impl)
2882 {
2883   GtkFileChooserWidgetPrivate *priv = impl->priv;
2884 
2885   if ((priv->operation_mode == OPERATION_MODE_RECENT ||
2886        priv->operation_mode == OPERATION_MODE_SEARCH) &&
2887       (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
2888        priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER))
2889     operation_mode_set (impl, OPERATION_MODE_BROWSE);
2890 
2891   /* If the file entry is not visible, show it (it is _always_
2892    * visible in save modes, handle these first).
2893    * If it is visible, turn it off only if it is focused.
2894    * Otherwise, switch to the entry.
2895    */
2896   if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE ||
2897       priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
2898     {
2899       gtk_widget_grab_focus (priv->location_entry);
2900     }
2901   else if (priv->location_mode == LOCATION_MODE_PATH_BAR)
2902     {
2903       location_mode_set (impl, LOCATION_MODE_FILENAME_ENTRY);
2904     }
2905   else if (priv->location_mode == LOCATION_MODE_FILENAME_ENTRY)
2906     {
2907       if (gtk_widget_has_focus (priv->location_entry))
2908         {
2909           location_mode_set (impl, LOCATION_MODE_PATH_BAR);
2910         }
2911       else
2912         {
2913           gtk_widget_grab_focus (priv->location_entry);
2914         }
2915     }
2916 }
2917 
2918 static void
gtk_file_chooser_widget_constructed(GObject * object)2919 gtk_file_chooser_widget_constructed (GObject *object)
2920 {
2921   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (object);
2922   GtkFileChooserWidgetPrivate *priv = impl->priv;
2923 
2924   profile_start ("start", NULL);
2925 
2926   G_OBJECT_CLASS (gtk_file_chooser_widget_parent_class)->constructed (object);
2927 
2928   g_assert (priv->file_system);
2929 
2930   update_appearance (impl);
2931 
2932   profile_end ("end", NULL);
2933 }
2934 
2935 static void
update_extra_and_filters(GtkFileChooserWidget * impl)2936 update_extra_and_filters (GtkFileChooserWidget *impl)
2937 {
2938   gtk_widget_set_visible (impl->priv->extra_and_filters,
2939                           gtk_widget_get_visible (impl->priv->extra_align) ||
2940                           gtk_widget_get_visible (impl->priv->filter_combo_hbox));
2941 }
2942 
2943 /* Sets the extra_widget by packing it in the appropriate place */
2944 static void
set_extra_widget(GtkFileChooserWidget * impl,GtkWidget * extra_widget)2945 set_extra_widget (GtkFileChooserWidget *impl,
2946                   GtkWidget             *extra_widget)
2947 {
2948   GtkFileChooserWidgetPrivate *priv = impl->priv;
2949 
2950   if (extra_widget)
2951     {
2952       g_object_ref (extra_widget);
2953       /* FIXME: is this right ? */
2954       gtk_widget_show (extra_widget);
2955     }
2956 
2957   if (priv->extra_widget)
2958     {
2959       gtk_container_remove (GTK_CONTAINER (priv->extra_align), priv->extra_widget);
2960       g_object_unref (priv->extra_widget);
2961     }
2962 
2963   priv->extra_widget = extra_widget;
2964   if (priv->extra_widget)
2965     {
2966       gtk_container_add (GTK_CONTAINER (priv->extra_align), priv->extra_widget);
2967       gtk_widget_show (priv->extra_align);
2968     }
2969   else
2970     gtk_widget_hide (priv->extra_align);
2971 
2972   /* Calls update_extra_and_filters */
2973   show_filters (impl, priv->filters != NULL);
2974 }
2975 
2976 static void
switch_to_home_dir(GtkFileChooserWidget * impl)2977 switch_to_home_dir (GtkFileChooserWidget *impl)
2978 {
2979   const gchar *home = g_get_home_dir ();
2980   GFile *home_file;
2981 
2982   if (home == NULL)
2983     return;
2984 
2985   home_file = g_file_new_for_path (home);
2986 
2987   gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (impl), home_file, NULL); /* NULL-GError */
2988 
2989   g_object_unref (home_file);
2990 }
2991 
2992 static void
set_local_only(GtkFileChooserWidget * impl,gboolean local_only)2993 set_local_only (GtkFileChooserWidget *impl,
2994                 gboolean               local_only)
2995 {
2996   GtkFileChooserWidgetPrivate *priv = impl->priv;
2997 
2998   if (local_only != priv->local_only)
2999     {
3000       priv->local_only = local_only;
3001 
3002       if (priv->location_entry)
3003         _gtk_file_chooser_entry_set_local_only (GTK_FILE_CHOOSER_ENTRY (priv->location_entry), local_only);
3004 
3005       gtk_places_sidebar_set_local_only (GTK_PLACES_SIDEBAR (priv->places_sidebar), local_only);
3006 
3007       if (local_only && priv->current_folder &&
3008            !_gtk_file_has_native_path (priv->current_folder))
3009         {
3010           /* If we are pointing to a non-local folder, make an effort to change
3011            * back to a local folder, but it's really up to the app to not cause
3012            * such a situation, so we ignore errors.
3013            */
3014           switch_to_home_dir (impl);
3015         }
3016     }
3017 }
3018 
3019 /* Sets the file chooser to multiple selection mode */
3020 static void
set_select_multiple(GtkFileChooserWidget * impl,gboolean select_multiple,gboolean property_notify)3021 set_select_multiple (GtkFileChooserWidget *impl,
3022                      gboolean               select_multiple,
3023                      gboolean               property_notify)
3024 {
3025   GtkFileChooserWidgetPrivate *priv = impl->priv;
3026   GtkTreeSelection *selection;
3027   GtkSelectionMode mode;
3028 
3029   if (select_multiple == priv->select_multiple)
3030     return;
3031 
3032   mode = select_multiple ? GTK_SELECTION_MULTIPLE : GTK_SELECTION_SINGLE;
3033 
3034   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view));
3035   gtk_tree_selection_set_mode (selection, mode);
3036 
3037   gtk_tree_view_set_rubber_banding (GTK_TREE_VIEW (priv->browse_files_tree_view), select_multiple);
3038 
3039   priv->select_multiple = select_multiple;
3040   g_object_notify (G_OBJECT (impl), "select-multiple");
3041 
3042   check_preview_change (impl);
3043 }
3044 
3045 static void
set_file_system_backend(GtkFileChooserWidget * impl)3046 set_file_system_backend (GtkFileChooserWidget *impl)
3047 {
3048   GtkFileChooserWidgetPrivate *priv = impl->priv;
3049 
3050   profile_start ("start for backend", "default");
3051 
3052   priv->file_system = _gtk_file_system_new ();
3053 
3054   profile_end ("end", NULL);
3055 }
3056 
3057 static void
unset_file_system_backend(GtkFileChooserWidget * impl)3058 unset_file_system_backend (GtkFileChooserWidget *impl)
3059 {
3060   GtkFileChooserWidgetPrivate *priv = impl->priv;
3061 
3062   g_object_unref (priv->file_system);
3063 
3064   priv->file_system = NULL;
3065 }
3066 
3067 /* Takes the folder stored in a row in the recent_model, and puts it in the pathbar */
3068 static void
put_recent_folder_in_pathbar(GtkFileChooserWidget * impl,GtkTreeIter * iter)3069 put_recent_folder_in_pathbar (GtkFileChooserWidget *impl, GtkTreeIter *iter)
3070 {
3071   GtkFileChooserWidgetPrivate *priv = impl->priv;
3072   GFile *file;
3073 
3074   gtk_tree_model_get (GTK_TREE_MODEL (priv->recent_model), iter,
3075                       MODEL_COL_FILE, &file,
3076                       -1);
3077   _gtk_path_bar_set_file (GTK_PATH_BAR (priv->browse_path_bar), file, FALSE);
3078   g_object_unref (file);
3079 }
3080 
3081 /* Sets the location bar in the appropriate mode according to the
3082  * current operation mode and action.  This is the central function
3083  * for dealing with the pathbar’s widgets; as long as impl->action and
3084  * impl->operation_mode are set correctly, then calling this function
3085  * will update all the pathbar’s widgets.
3086  */
3087 static void
location_bar_update(GtkFileChooserWidget * impl)3088 location_bar_update (GtkFileChooserWidget *impl)
3089 {
3090   GtkFileChooserWidgetPrivate *priv = impl->priv;
3091   gboolean visible = TRUE;
3092   gboolean create_folder_visible = FALSE;
3093 
3094   switch (priv->operation_mode)
3095     {
3096     case OPERATION_MODE_ENTER_LOCATION:
3097       break;
3098 
3099     case OPERATION_MODE_OTHER_LOCATIONS:
3100       break;
3101 
3102     case OPERATION_MODE_BROWSE:
3103       break;
3104 
3105     case OPERATION_MODE_RECENT:
3106       if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE)
3107         {
3108           GtkTreeSelection *selection;
3109           gboolean have_selected;
3110           GtkTreeIter iter;
3111 
3112           selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view));
3113 
3114           /* Save mode means single-selection mode, so the following is valid */
3115           have_selected = gtk_tree_selection_get_selected (selection, NULL, &iter);
3116 
3117           if (have_selected)
3118             put_recent_folder_in_pathbar (impl, &iter);
3119         }
3120       visible = FALSE;
3121       break;
3122 
3123     case OPERATION_MODE_SEARCH:
3124       break;
3125 
3126     default:
3127       g_assert_not_reached ();
3128       return;
3129     }
3130 
3131   if (visible)
3132     {
3133       if (priv->create_folders
3134           && priv->action != GTK_FILE_CHOOSER_ACTION_OPEN
3135           && priv->operation_mode != OPERATION_MODE_RECENT)
3136         create_folder_visible = TRUE;
3137     }
3138 
3139   gtk_widget_set_visible (priv->browse_new_folder_button, create_folder_visible);
3140 }
3141 
3142 static void
operation_mode_stop(GtkFileChooserWidget * impl,OperationMode mode)3143 operation_mode_stop (GtkFileChooserWidget *impl,
3144                      OperationMode         mode)
3145 {
3146   if (mode == OPERATION_MODE_SEARCH)
3147     {
3148       g_clear_object (&impl->priv->model_for_search);
3149       search_stop_searching (impl, TRUE);
3150       search_clear_model (impl, TRUE);
3151       gtk_widget_hide (impl->priv->remote_warning_bar);
3152     }
3153 }
3154 
3155 static void
operation_mode_set_enter_location(GtkFileChooserWidget * impl)3156 operation_mode_set_enter_location (GtkFileChooserWidget *impl)
3157 {
3158   GtkFileChooserWidgetPrivate *priv = impl->priv;
3159 
3160   gtk_stack_set_visible_child_name (GTK_STACK (priv->browse_files_stack), "list");
3161   gtk_stack_set_visible_child_name (GTK_STACK (priv->browse_header_stack), "location");
3162   gtk_revealer_set_reveal_child (GTK_REVEALER (priv->browse_header_revealer), TRUE);
3163   location_bar_update (impl);
3164   gtk_widget_set_sensitive (priv->filter_combo, TRUE);
3165   location_mode_set (impl, LOCATION_MODE_FILENAME_ENTRY);
3166 }
3167 
3168 static void
operation_mode_set_browse(GtkFileChooserWidget * impl)3169 operation_mode_set_browse (GtkFileChooserWidget *impl)
3170 {
3171   GtkFileChooserWidgetPrivate *priv = impl->priv;
3172 
3173   gtk_places_sidebar_set_location (GTK_PLACES_SIDEBAR (priv->places_sidebar), priv->current_folder);
3174   gtk_stack_set_visible_child_name (GTK_STACK (priv->browse_files_stack), "list");
3175   location_mode_set (impl, LOCATION_MODE_PATH_BAR);
3176   gtk_stack_set_visible_child_name (GTK_STACK (priv->browse_header_stack), "pathbar");
3177   gtk_revealer_set_reveal_child (GTK_REVEALER (priv->browse_header_revealer), TRUE);
3178   gtk_widget_set_sensitive (priv->filter_combo, TRUE);
3179   g_object_notify (G_OBJECT (impl), "subtitle");
3180 }
3181 
3182 static void
operation_mode_set_search(GtkFileChooserWidget * impl)3183 operation_mode_set_search (GtkFileChooserWidget *impl)
3184 {
3185   GtkFileChooserWidgetPrivate *priv = impl->priv;
3186   GtkWidget *visible_widget;
3187 
3188   g_assert (priv->search_model == NULL);
3189 
3190   visible_widget = gtk_stack_get_visible_child (GTK_STACK (priv->browse_files_stack));
3191 
3192   if (visible_widget != priv->places_view &&
3193       visible_widget != priv->browse_files_swin)
3194     {
3195       gtk_stack_set_visible_child_name (GTK_STACK (priv->browse_files_stack), "list");
3196     }
3197 
3198   gtk_entry_grab_focus_without_selecting (GTK_ENTRY (priv->search_entry));
3199   gtk_stack_set_visible_child_name (GTK_STACK (priv->browse_header_stack), "search");
3200   gtk_revealer_set_reveal_child (GTK_REVEALER (priv->browse_header_revealer), TRUE);
3201   location_bar_update (impl);
3202   search_setup_widgets (impl);
3203   gtk_widget_set_sensitive (priv->filter_combo, FALSE);
3204 }
3205 
3206 static void
operation_mode_set_recent(GtkFileChooserWidget * impl)3207 operation_mode_set_recent (GtkFileChooserWidget *impl)
3208 {
3209   GtkFileChooserWidgetPrivate *priv = impl->priv;
3210   GFile *file;
3211 
3212   gtk_stack_set_visible_child_name (GTK_STACK (priv->browse_files_stack), "list");
3213   gtk_stack_set_visible_child_name (GTK_STACK (priv->browse_header_stack), "pathbar");
3214   gtk_revealer_set_reveal_child (GTK_REVEALER (priv->browse_header_revealer), FALSE);
3215   location_bar_update (impl);
3216   recent_start_loading (impl);
3217   file = g_file_new_for_uri ("recent:///");
3218   gtk_places_sidebar_set_location (GTK_PLACES_SIDEBAR (priv->places_sidebar), file);
3219   g_object_notify (G_OBJECT (impl), "subtitle");
3220   g_object_unref (file);
3221   gtk_widget_set_sensitive (priv->filter_combo, TRUE);
3222 }
3223 
3224 static void
operation_mode_set_other_locations(GtkFileChooserWidget * impl)3225 operation_mode_set_other_locations (GtkFileChooserWidget *impl)
3226 {
3227   GtkFileChooserWidgetPrivate *priv = impl->priv;
3228 
3229   gtk_stack_set_visible_child_name (GTK_STACK (priv->browse_files_stack), "other_locations");
3230   gtk_stack_set_visible_child_name (GTK_STACK (priv->browse_header_stack), "pathbar");
3231   gtk_revealer_set_reveal_child (GTK_REVEALER (priv->browse_header_revealer), FALSE);
3232   location_bar_update (impl);
3233   stop_loading_and_clear_list_model (impl, TRUE);
3234   recent_stop_loading (impl);
3235   search_stop_searching (impl, TRUE);
3236   recent_clear_model (impl, TRUE);
3237   search_clear_model (impl, TRUE);
3238   gtk_widget_set_sensitive (priv->filter_combo, FALSE);
3239 }
3240 
3241 static void
operation_mode_set(GtkFileChooserWidget * impl,OperationMode mode)3242 operation_mode_set (GtkFileChooserWidget *impl, OperationMode mode)
3243 {
3244   GtkFileChooserWidgetPrivate *priv = impl->priv;
3245   OperationMode old_mode;
3246 
3247   operation_mode_stop (impl, priv->operation_mode);
3248 
3249   old_mode = priv->operation_mode;
3250   priv->operation_mode = mode;
3251 
3252   switch (priv->operation_mode)
3253     {
3254     case OPERATION_MODE_ENTER_LOCATION:
3255       operation_mode_set_enter_location (impl);
3256       break;
3257 
3258     case OPERATION_MODE_OTHER_LOCATIONS:
3259       operation_mode_set_other_locations (impl);
3260       break;
3261 
3262     case OPERATION_MODE_BROWSE:
3263       operation_mode_set_browse (impl);
3264       break;
3265 
3266     case OPERATION_MODE_SEARCH:
3267       operation_mode_set_search (impl);
3268       break;
3269 
3270     case OPERATION_MODE_RECENT:
3271       operation_mode_set_recent (impl);
3272       break;
3273 
3274     default:
3275       g_assert_not_reached ();
3276       return;
3277     }
3278 
3279   if ((old_mode == OPERATION_MODE_SEARCH) != (mode == OPERATION_MODE_SEARCH))
3280     g_object_notify (G_OBJECT (impl), "search-mode");
3281 
3282   g_object_notify (G_OBJECT (impl), "subtitle");
3283 }
3284 
3285 /* This function is basically a do_all function.
3286  *
3287  * It sets the visibility on all the widgets based on the current state, and
3288  * moves the custom_widget if needed.
3289  */
3290 static void
update_appearance(GtkFileChooserWidget * impl)3291 update_appearance (GtkFileChooserWidget *impl)
3292 {
3293   GtkFileChooserWidgetPrivate *priv = impl->priv;
3294 
3295   if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE ||
3296       priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
3297     {
3298       save_widgets_create (impl);
3299       gtk_places_sidebar_set_show_recent (GTK_PLACES_SIDEBAR (priv->places_sidebar), FALSE);
3300       gtk_places_sidebar_set_show_trash (GTK_PLACES_SIDEBAR (priv->places_sidebar), FALSE);
3301 
3302       if (priv->select_multiple)
3303         {
3304           g_warning ("Save mode cannot be set in conjunction with multiple selection mode.  "
3305                      "Re-setting to single selection mode.");
3306           set_select_multiple (impl, FALSE, TRUE);
3307         }
3308 
3309     }
3310   else if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
3311            priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
3312     {
3313       save_widgets_destroy (impl);
3314       gtk_places_sidebar_set_show_recent (GTK_PLACES_SIDEBAR (priv->places_sidebar), recent_files_setting_is_enabled (impl));
3315       location_mode_set (impl, priv->location_mode);
3316     }
3317 
3318   if (priv->location_entry)
3319     _gtk_file_chooser_entry_set_action (GTK_FILE_CHOOSER_ENTRY (priv->location_entry), priv->action);
3320 
3321   location_bar_update (impl);
3322 
3323   /* This *is* needed; we need to redraw the file list because the "sensitivity"
3324    * of files may change depending whether we are in a file or folder-only mode.
3325    */
3326   gtk_widget_queue_draw (priv->browse_files_tree_view);
3327 
3328   emit_default_size_changed (impl);
3329 }
3330 
3331 static gchar *
gtk_file_chooser_widget_get_subtitle(GtkFileChooserWidget * impl)3332 gtk_file_chooser_widget_get_subtitle (GtkFileChooserWidget *impl)
3333 {
3334   GtkFileChooserWidgetPrivate *priv = impl->priv;
3335   gchar *subtitle = NULL;
3336 
3337   if (priv->operation_mode == OPERATION_MODE_SEARCH)
3338     {
3339       gchar *location;
3340 
3341       location = gtk_places_sidebar_get_location_title (GTK_PLACES_SIDEBAR (priv->places_sidebar));
3342       if (location)
3343         {
3344           subtitle = g_strdup_printf (_("Searching in %s"), location);
3345           g_free (location);
3346         }
3347       else if (priv->current_folder)
3348         {
3349           GFileInfo *info;
3350 
3351           info = g_file_query_info (priv->current_folder,
3352                                     G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
3353                                     G_FILE_QUERY_INFO_NONE,
3354                                     NULL,
3355                                     NULL);
3356           if (info)
3357             {
3358               subtitle = g_strdup_printf (_("Searching in %s"), g_file_info_get_display_name (info));
3359               g_object_unref (info);
3360             }
3361         }
3362 
3363       if (subtitle == NULL)
3364         subtitle = g_strdup (_("Searching"));
3365     }
3366   else if (priv->operation_mode == OPERATION_MODE_ENTER_LOCATION ||
3367            (priv->operation_mode == OPERATION_MODE_BROWSE &&
3368             priv->location_mode == LOCATION_MODE_FILENAME_ENTRY))
3369     {
3370       if (priv->local_only)
3371         subtitle = g_strdup (_("Enter location"));
3372       else
3373         subtitle = g_strdup (_("Enter location or URL"));
3374     }
3375 
3376   return subtitle;
3377 }
3378 
3379 static void
set_show_hidden(GtkFileChooserWidget * impl,gboolean show_hidden)3380 set_show_hidden (GtkFileChooserWidget *impl,
3381                  gboolean              show_hidden)
3382 {
3383   GtkFileChooserWidgetPrivate *priv = impl->priv;
3384 
3385   if (priv->show_hidden != show_hidden)
3386     {
3387       priv->show_hidden = show_hidden;
3388 
3389       if (priv->browse_files_model)
3390         _gtk_file_system_model_set_show_hidden (priv->browse_files_model, show_hidden);
3391     }
3392 }
3393 
3394 static void
gtk_file_chooser_widget_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)3395 gtk_file_chooser_widget_set_property (GObject      *object,
3396                                       guint         prop_id,
3397                                       const GValue *value,
3398                                       GParamSpec   *pspec)
3399 
3400 {
3401   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (object);
3402   GtkFileChooserWidgetPrivate *priv = impl->priv;
3403 
3404   switch (prop_id)
3405     {
3406     case PROP_SEARCH_MODE:
3407       if (g_value_get_boolean (value))
3408         operation_mode_set (impl, OPERATION_MODE_SEARCH);
3409       else
3410         {
3411           if (gtk_stack_get_visible_child (GTK_STACK (priv->browse_files_stack)) != priv->places_view)
3412             {
3413               operation_mode_set (impl, OPERATION_MODE_BROWSE);
3414 
3415               if (priv->current_folder)
3416                 change_folder_and_display_error (impl, priv->current_folder, FALSE);
3417                else
3418                 switch_to_home_dir (impl);
3419             }
3420           else
3421             {
3422               operation_mode_set (impl, OPERATION_MODE_OTHER_LOCATIONS);
3423             }
3424         }
3425       break;
3426 
3427     case GTK_FILE_CHOOSER_PROP_ACTION:
3428       {
3429         GtkFileChooserAction action = g_value_get_enum (value);
3430 
3431         if (action != priv->action)
3432           {
3433             gtk_file_chooser_widget_unselect_all (GTK_FILE_CHOOSER (impl));
3434 
3435             if ((action == GTK_FILE_CHOOSER_ACTION_SAVE ||
3436                  action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
3437                 && priv->select_multiple)
3438               {
3439                 g_warning ("Tried to change the file chooser action to SAVE or CREATE_FOLDER, but "
3440                            "this is not allowed in multiple selection mode.  Resetting the file chooser "
3441                            "to single selection mode.");
3442                 set_select_multiple (impl, FALSE, TRUE);
3443               }
3444             priv->action = action;
3445             update_cell_renderer_attributes (impl);
3446             update_appearance (impl);
3447             settings_load (impl);
3448           }
3449       }
3450       break;
3451 
3452     case GTK_FILE_CHOOSER_PROP_FILTER:
3453       set_current_filter (impl, g_value_get_object (value));
3454       break;
3455 
3456     case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY:
3457       set_local_only (impl, g_value_get_boolean (value));
3458       break;
3459 
3460     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET:
3461       set_preview_widget (impl, g_value_get_object (value));
3462       break;
3463 
3464     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE:
3465       priv->preview_widget_active = g_value_get_boolean (value);
3466       update_preview_widget_visibility (impl);
3467       break;
3468 
3469     case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL:
3470       priv->use_preview_label = g_value_get_boolean (value);
3471       update_preview_widget_visibility (impl);
3472       break;
3473 
3474     case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET:
3475       set_extra_widget (impl, g_value_get_object (value));
3476       break;
3477 
3478     case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE:
3479       {
3480         gboolean select_multiple = g_value_get_boolean (value);
3481         if ((priv->action == GTK_FILE_CHOOSER_ACTION_SAVE ||
3482              priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
3483             && select_multiple)
3484           {
3485             g_warning ("Tried to set the file chooser to multiple selection mode, but this is "
3486                        "not allowed in SAVE or CREATE_FOLDER modes.  Ignoring the change and "
3487                        "leaving the file chooser in single selection mode.");
3488             return;
3489           }
3490 
3491         set_select_multiple (impl, select_multiple, FALSE);
3492       }
3493       break;
3494 
3495     case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN:
3496       priv->show_hidden_set = TRUE;
3497       set_show_hidden (impl, g_value_get_boolean (value));
3498       break;
3499 
3500     case GTK_FILE_CHOOSER_PROP_DO_OVERWRITE_CONFIRMATION:
3501       {
3502         gboolean do_overwrite_confirmation = g_value_get_boolean (value);
3503         priv->do_overwrite_confirmation = do_overwrite_confirmation;
3504       }
3505       break;
3506 
3507     case GTK_FILE_CHOOSER_PROP_CREATE_FOLDERS:
3508       {
3509         gboolean create_folders = g_value_get_boolean (value);
3510         priv->create_folders = create_folders;
3511         update_appearance (impl);
3512       }
3513       break;
3514 
3515     default:
3516       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
3517       break;
3518     }
3519 }
3520 
3521 static void
gtk_file_chooser_widget_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)3522 gtk_file_chooser_widget_get_property (GObject    *object,
3523                                        guint       prop_id,
3524                                        GValue     *value,
3525                                        GParamSpec *pspec)
3526 {
3527   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (object);
3528   GtkFileChooserWidgetPrivate *priv = impl->priv;
3529 
3530   switch (prop_id)
3531     {
3532     case PROP_SEARCH_MODE:
3533       g_value_set_boolean (value, priv->operation_mode == OPERATION_MODE_SEARCH);
3534       break;
3535 
3536     case PROP_SUBTITLE:
3537       g_value_take_string (value, gtk_file_chooser_widget_get_subtitle (impl));
3538       break;
3539 
3540     case GTK_FILE_CHOOSER_PROP_ACTION:
3541       g_value_set_enum (value, priv->action);
3542       break;
3543 
3544     case GTK_FILE_CHOOSER_PROP_FILTER:
3545       g_value_set_object (value, priv->current_filter);
3546       break;
3547 
3548     case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY:
3549       g_value_set_boolean (value, priv->local_only);
3550       break;
3551 
3552     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET:
3553       g_value_set_object (value, priv->preview_widget);
3554       break;
3555 
3556     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE:
3557       g_value_set_boolean (value, priv->preview_widget_active);
3558       break;
3559 
3560     case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL:
3561       g_value_set_boolean (value, priv->use_preview_label);
3562       break;
3563 
3564     case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET:
3565       g_value_set_object (value, priv->extra_widget);
3566       break;
3567 
3568     case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE:
3569       g_value_set_boolean (value, priv->select_multiple);
3570       break;
3571 
3572     case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN:
3573       g_value_set_boolean (value, priv->show_hidden);
3574       break;
3575 
3576     case GTK_FILE_CHOOSER_PROP_DO_OVERWRITE_CONFIRMATION:
3577       g_value_set_boolean (value, priv->do_overwrite_confirmation);
3578       break;
3579 
3580     case GTK_FILE_CHOOSER_PROP_CREATE_FOLDERS:
3581       g_value_set_boolean (value, priv->create_folders);
3582       break;
3583 
3584     default:
3585       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
3586       break;
3587     }
3588 }
3589 
3590 /* This cancels everything that may be going on in the background. */
3591 static void
cancel_all_operations(GtkFileChooserWidget * impl)3592 cancel_all_operations (GtkFileChooserWidget *impl)
3593 {
3594   GtkFileChooserWidgetPrivate *priv = impl->priv;
3595 
3596   pending_select_files_free (impl);
3597 
3598   if (priv->file_list_drag_data_received_cancellable)
3599     {
3600       g_cancellable_cancel (priv->file_list_drag_data_received_cancellable);
3601       priv->file_list_drag_data_received_cancellable = NULL;
3602     }
3603 
3604   if (priv->update_current_folder_cancellable)
3605     {
3606       g_cancellable_cancel (priv->update_current_folder_cancellable);
3607       priv->update_current_folder_cancellable = NULL;
3608     }
3609 
3610   if (priv->should_respond_get_info_cancellable)
3611     {
3612       g_cancellable_cancel (priv->should_respond_get_info_cancellable);
3613       priv->should_respond_get_info_cancellable = NULL;
3614     }
3615 
3616   if (priv->file_exists_get_info_cancellable)
3617     {
3618       g_cancellable_cancel (priv->file_exists_get_info_cancellable);
3619       priv->file_exists_get_info_cancellable = NULL;
3620     }
3621 
3622   search_stop_searching (impl, TRUE);
3623   recent_stop_loading (impl);
3624 }
3625 
3626 /* Removes the settings signal handler.  It's safe to call multiple times */
3627 static void
remove_settings_signal(GtkFileChooserWidget * impl,GdkScreen * screen)3628 remove_settings_signal (GtkFileChooserWidget *impl,
3629                         GdkScreen             *screen)
3630 {
3631   GtkFileChooserWidgetPrivate *priv = impl->priv;
3632 
3633   if (priv->settings_signal_id)
3634     {
3635       GtkSettings *settings;
3636 
3637       settings = gtk_settings_get_for_screen (screen);
3638       g_signal_handler_disconnect (settings,
3639                                    priv->settings_signal_id);
3640       priv->settings_signal_id = 0;
3641     }
3642 }
3643 
3644 static void
gtk_file_chooser_widget_dispose(GObject * object)3645 gtk_file_chooser_widget_dispose (GObject *object)
3646 {
3647   GtkFileChooserWidget *impl = (GtkFileChooserWidget *) object;
3648   GtkFileChooserWidgetPrivate *priv = impl->priv;
3649 
3650   cancel_all_operations (impl);
3651 
3652   if (priv->rename_file_popover)
3653     gtk_popover_set_relative_to (GTK_POPOVER (priv->rename_file_popover), NULL);
3654 
3655   if (priv->browse_files_popover)
3656     {
3657       gtk_widget_destroy (priv->browse_files_popover);
3658       priv->browse_files_popover = NULL;
3659     }
3660 
3661   if (priv->extra_widget)
3662     {
3663       g_object_unref (priv->extra_widget);
3664       priv->extra_widget = NULL;
3665     }
3666 
3667   remove_settings_signal (impl, gtk_widget_get_screen (GTK_WIDGET (impl)));
3668 
3669   if (priv->bookmarks_manager)
3670     {
3671       _gtk_bookmarks_manager_free (priv->bookmarks_manager);
3672       priv->bookmarks_manager = NULL;
3673     }
3674 
3675   if (priv->external_entry && priv->location_entry == priv->external_entry)
3676     {
3677       location_entry_disconnect (impl);
3678       priv->external_entry = NULL;
3679     }
3680 
3681   g_clear_object (&priv->long_press_gesture);
3682 
3683   G_OBJECT_CLASS (gtk_file_chooser_widget_parent_class)->dispose (object);
3684 }
3685 
3686 /* We override show-all since we have internal widgets that
3687  * shouldn’t be shown when you call show_all(), like the filter
3688  * combo box.
3689  */
3690 static void
gtk_file_chooser_widget_show_all(GtkWidget * widget)3691 gtk_file_chooser_widget_show_all (GtkWidget *widget)
3692 {
3693   GtkFileChooserWidget *impl = (GtkFileChooserWidget *) widget;
3694   GtkFileChooserWidgetPrivate *priv = impl->priv;
3695 
3696   gtk_widget_show (widget);
3697 
3698   if (priv->extra_widget)
3699     gtk_widget_show_all (priv->extra_widget);
3700 }
3701 
3702 /* Handler for GtkWindow::set-focus; this is where we save the last-focused
3703  * widget on our toplevel.  See gtk_file_chooser_widget_hierarchy_changed()
3704  */
3705 static void
toplevel_set_focus_cb(GtkWindow * window,GtkWidget * focus,GtkFileChooserWidget * impl)3706 toplevel_set_focus_cb (GtkWindow             *window,
3707                        GtkWidget             *focus,
3708                        GtkFileChooserWidget *impl)
3709 {
3710   GtkFileChooserWidgetPrivate *priv = impl->priv;
3711 
3712   priv->toplevel_last_focus_widget = gtk_window_get_focus (window);
3713 }
3714 
3715 /* We monitor the focus widget on our toplevel to be able to know which widget
3716  * was last focused at the time our “should_respond” method gets called.
3717  */
3718 static void
gtk_file_chooser_widget_hierarchy_changed(GtkWidget * widget,GtkWidget * previous_toplevel)3719 gtk_file_chooser_widget_hierarchy_changed (GtkWidget *widget,
3720                                             GtkWidget *previous_toplevel)
3721 {
3722   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (widget);
3723   GtkFileChooserWidgetPrivate *priv = impl->priv;
3724   GtkWidget *toplevel;
3725 
3726   toplevel = gtk_widget_get_toplevel (widget);
3727 
3728   if (previous_toplevel &&
3729       priv->toplevel_set_focus_id != 0)
3730     {
3731       g_signal_handler_disconnect (previous_toplevel,
3732                                    priv->toplevel_set_focus_id);
3733       priv->toplevel_set_focus_id = 0;
3734       priv->toplevel_last_focus_widget = NULL;
3735     }
3736 
3737   if (gtk_widget_is_toplevel (toplevel))
3738     {
3739       g_assert (priv->toplevel_set_focus_id == 0);
3740       priv->toplevel_set_focus_id = g_signal_connect (toplevel, "set-focus",
3741                                                       G_CALLBACK (toplevel_set_focus_cb), impl);
3742       priv->toplevel_last_focus_widget = gtk_window_get_focus (GTK_WINDOW (toplevel));
3743     }
3744 }
3745 
3746 /* Changes the icons wherever it is needed */
3747 static void
change_icon_theme(GtkFileChooserWidget * impl)3748 change_icon_theme (GtkFileChooserWidget *impl)
3749 {
3750   GtkFileChooserWidgetPrivate *priv = impl->priv;
3751   gint width, height;
3752 
3753   profile_start ("start", NULL);
3754 
3755   if (gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, &height))
3756     priv->icon_size = MAX (width, height);
3757   else
3758     priv->icon_size = FALLBACK_ICON_SIZE;
3759 
3760   /* the first cell in the first column is the icon column, and we have a fixed size there */
3761   set_icon_cell_renderer_fixed_size (impl);
3762 
3763   clear_model_cache (impl, MODEL_COL_SURFACE);
3764   gtk_widget_queue_resize (priv->browse_files_tree_view);
3765 
3766   profile_end ("end", NULL);
3767 }
3768 
3769 /* Callback used when a GtkSettings value changes */
3770 static void
settings_notify_cb(GObject * object,GParamSpec * pspec,GtkFileChooserWidget * impl)3771 settings_notify_cb (GObject               *object,
3772                     GParamSpec            *pspec,
3773                     GtkFileChooserWidget *impl)
3774 {
3775   const char *name;
3776 
3777   profile_start ("start", NULL);
3778 
3779   name = g_param_spec_get_name (pspec);
3780 
3781   if (strcmp (name, "gtk-icon-theme-name") == 0)
3782     change_icon_theme (impl);
3783 
3784   profile_end ("end", NULL);
3785 }
3786 
3787 /* Installs a signal handler for GtkSettings so that we can monitor changes in
3788  * the icon theme.
3789  */
3790 static void
check_icon_theme(GtkFileChooserWidget * impl)3791 check_icon_theme (GtkFileChooserWidget *impl)
3792 {
3793   GtkFileChooserWidgetPrivate *priv = impl->priv;
3794   GtkSettings *settings;
3795 
3796   profile_start ("start", NULL);
3797 
3798   if (priv->settings_signal_id)
3799     {
3800       profile_end ("end", NULL);
3801       return;
3802     }
3803 
3804   if (gtk_widget_has_screen (GTK_WIDGET (impl)))
3805     {
3806       settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (impl)));
3807       priv->settings_signal_id = g_signal_connect (settings, "notify",
3808                                                    G_CALLBACK (settings_notify_cb), impl);
3809 
3810       change_icon_theme (impl);
3811     }
3812 
3813   profile_end ("end", NULL);
3814 }
3815 
3816 static void
gtk_file_chooser_widget_style_updated(GtkWidget * widget)3817 gtk_file_chooser_widget_style_updated (GtkWidget *widget)
3818 {
3819   GtkFileChooserWidget *impl;
3820 
3821   profile_start ("start", NULL);
3822 
3823   impl = GTK_FILE_CHOOSER_WIDGET (widget);
3824 
3825   profile_msg ("    parent class style_udpated start", NULL);
3826   GTK_WIDGET_CLASS (gtk_file_chooser_widget_parent_class)->style_updated (widget);
3827   profile_msg ("    parent class style_updated end", NULL);
3828 
3829   if (gtk_widget_has_screen (GTK_WIDGET (impl)))
3830     change_icon_theme (impl);
3831 
3832   emit_default_size_changed (impl);
3833 
3834   profile_end ("end", NULL);
3835 }
3836 
3837 static void
gtk_file_chooser_widget_screen_changed(GtkWidget * widget,GdkScreen * previous_screen)3838 gtk_file_chooser_widget_screen_changed (GtkWidget *widget,
3839                                          GdkScreen *previous_screen)
3840 {
3841   GtkFileChooserWidget *impl;
3842 
3843   profile_start ("start", NULL);
3844 
3845   impl = GTK_FILE_CHOOSER_WIDGET (widget);
3846 
3847   if (GTK_WIDGET_CLASS (gtk_file_chooser_widget_parent_class)->screen_changed)
3848     GTK_WIDGET_CLASS (gtk_file_chooser_widget_parent_class)->screen_changed (widget, previous_screen);
3849 
3850   remove_settings_signal (impl, previous_screen);
3851   check_icon_theme (impl);
3852 
3853   emit_default_size_changed (impl);
3854 
3855   profile_end ("end", NULL);
3856 }
3857 
3858 static void
set_sort_column(GtkFileChooserWidget * impl)3859 set_sort_column (GtkFileChooserWidget *impl)
3860 {
3861   GtkFileChooserWidgetPrivate *priv = impl->priv;
3862   GtkTreeSortable *sortable;
3863 
3864   sortable = GTK_TREE_SORTABLE (gtk_tree_view_get_model (GTK_TREE_VIEW (priv->browse_files_tree_view)));
3865 
3866   /* can happen when we're still populating the model */
3867   if (sortable == NULL)
3868     return;
3869 
3870   gtk_tree_sortable_set_sort_column_id (sortable,
3871                                         priv->sort_column,
3872                                         priv->sort_order);
3873 }
3874 
3875 static void
settings_load(GtkFileChooserWidget * impl)3876 settings_load (GtkFileChooserWidget *impl)
3877 {
3878   GtkFileChooserWidgetPrivate *priv = impl->priv;
3879   gboolean show_hidden;
3880   gboolean show_size_column;
3881   gboolean show_type_column;
3882   gboolean sort_directories_first;
3883   DateFormat date_format;
3884   TypeFormat type_format;
3885   gint sort_column;
3886   GtkSortType sort_order;
3887   StartupMode startup_mode;
3888   gint sidebar_width;
3889   GSettings *settings;
3890 
3891   settings = _gtk_file_chooser_get_settings_for_widget (GTK_WIDGET (impl));
3892 
3893   show_hidden = g_settings_get_boolean (settings, SETTINGS_KEY_SHOW_HIDDEN);
3894   show_size_column = g_settings_get_boolean (settings, SETTINGS_KEY_SHOW_SIZE_COLUMN);
3895   show_type_column = g_settings_get_boolean (settings, SETTINGS_KEY_SHOW_TYPE_COLUMN);
3896   sort_column = g_settings_get_enum (settings, SETTINGS_KEY_SORT_COLUMN);
3897   sort_order = g_settings_get_enum (settings, SETTINGS_KEY_SORT_ORDER);
3898   sidebar_width = g_settings_get_int (settings, SETTINGS_KEY_SIDEBAR_WIDTH);
3899   startup_mode = g_settings_get_enum (settings, SETTINGS_KEY_STARTUP_MODE);
3900   sort_directories_first = g_settings_get_boolean (settings, SETTINGS_KEY_SORT_DIRECTORIES_FIRST);
3901   date_format = g_settings_get_enum (settings, SETTINGS_KEY_DATE_FORMAT);
3902   type_format = g_settings_get_enum (settings, SETTINGS_KEY_TYPE_FORMAT);
3903 
3904   if (!priv->show_hidden_set)
3905     set_show_hidden (impl, show_hidden);
3906   priv->show_size_column = show_size_column;
3907   gtk_tree_view_column_set_visible (priv->list_size_column, show_size_column);
3908   priv->show_type_column = show_type_column;
3909   gtk_tree_view_column_set_visible (priv->list_type_column, show_type_column);
3910 
3911   priv->sort_column = sort_column;
3912   priv->sort_order = sort_order;
3913   priv->startup_mode = startup_mode;
3914   priv->sort_directories_first = sort_directories_first;
3915   priv->show_time = date_format == DATE_FORMAT_WITH_TIME;
3916   priv->type_format = type_format;
3917 
3918   /* We don't call set_sort_column() here as the models may not have been
3919    * created yet.  The individual functions that create and set the models will
3920    * call set_sort_column() themselves.
3921    */
3922 
3923   update_time_renderer_visible (impl);
3924   gtk_paned_set_position (GTK_PANED (priv->browse_widgets_hpaned), sidebar_width);
3925 }
3926 
3927 static void
settings_save(GtkFileChooserWidget * impl)3928 settings_save (GtkFileChooserWidget *impl)
3929 {
3930   GtkFileChooserWidgetPrivate *priv = impl->priv;
3931   GSettings *settings;
3932 
3933   settings = _gtk_file_chooser_get_settings_for_widget (GTK_WIDGET (impl));
3934 
3935   /* All the other state */
3936 
3937   g_settings_set_enum (settings, SETTINGS_KEY_LOCATION_MODE, priv->location_mode);
3938   g_settings_set_boolean (settings, SETTINGS_KEY_SHOW_HIDDEN,
3939                           gtk_file_chooser_get_show_hidden (GTK_FILE_CHOOSER (impl)));
3940   g_settings_set_boolean (settings, SETTINGS_KEY_SHOW_SIZE_COLUMN, priv->show_size_column);
3941   g_settings_set_boolean (settings, SETTINGS_KEY_SHOW_TYPE_COLUMN, priv->show_type_column);
3942   g_settings_set_boolean (settings, SETTINGS_KEY_SORT_DIRECTORIES_FIRST, priv->sort_directories_first);
3943   g_settings_set_enum (settings, SETTINGS_KEY_SORT_COLUMN, priv->sort_column);
3944   g_settings_set_enum (settings, SETTINGS_KEY_SORT_ORDER, priv->sort_order);
3945   g_settings_set_int (settings, SETTINGS_KEY_SIDEBAR_WIDTH,
3946                       gtk_paned_get_position (GTK_PANED (priv->browse_widgets_hpaned)));
3947   g_settings_set_enum (settings, SETTINGS_KEY_DATE_FORMAT, priv->show_time ? DATE_FORMAT_WITH_TIME : DATE_FORMAT_REGULAR);
3948   g_settings_set_enum (settings, SETTINGS_KEY_TYPE_FORMAT, priv->type_format);
3949 
3950   /* Now apply the settings */
3951   g_settings_apply (settings);
3952 }
3953 
3954 /* GtkWidget::realize method */
3955 static void
gtk_file_chooser_widget_realize(GtkWidget * widget)3956 gtk_file_chooser_widget_realize (GtkWidget *widget)
3957 {
3958   GtkFileChooserWidget *impl;
3959 
3960   impl = GTK_FILE_CHOOSER_WIDGET (widget);
3961 
3962   GTK_WIDGET_CLASS (gtk_file_chooser_widget_parent_class)->realize (widget);
3963 
3964   emit_default_size_changed (impl);
3965 }
3966 
3967 /* Changes the current folder to $CWD */
3968 static void
switch_to_cwd(GtkFileChooserWidget * impl)3969 switch_to_cwd (GtkFileChooserWidget *impl)
3970 {
3971   char *current_working_dir;
3972 
3973   current_working_dir = g_get_current_dir ();
3974   gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (impl), current_working_dir);
3975   g_free (current_working_dir);
3976 }
3977 
3978 static gboolean
recent_files_setting_is_enabled(GtkFileChooserWidget * impl)3979 recent_files_setting_is_enabled (GtkFileChooserWidget *impl)
3980 {
3981   GtkSettings *settings;
3982   gboolean enabled;
3983 
3984   settings = gtk_widget_get_settings (GTK_WIDGET (impl));
3985   g_object_get (settings, "gtk-recent-files-enabled", &enabled, NULL);
3986   return enabled;
3987 }
3988 
3989 /* Sets the file chooser to showing Recent Files or $CWD, depending on the
3990  * user’s settings.
3991  */
3992 static void
set_startup_mode(GtkFileChooserWidget * impl)3993 set_startup_mode (GtkFileChooserWidget *impl)
3994 {
3995   GtkFileChooserWidgetPrivate *priv = impl->priv;
3996   GtkRevealerTransitionType revealer_transition;
3997   GtkStackTransitionType stack_transition;
3998 
3999   /* turn off animations for this setup */
4000   revealer_transition
4001     = gtk_revealer_get_transition_type (GTK_REVEALER (priv->browse_header_revealer));
4002   gtk_revealer_set_transition_type (GTK_REVEALER (priv->browse_header_revealer),
4003                                     GTK_REVEALER_TRANSITION_TYPE_NONE);
4004   stack_transition
4005     = gtk_stack_get_transition_type (GTK_STACK (priv->browse_header_stack));
4006   gtk_stack_set_transition_type (GTK_STACK (priv->browse_header_stack),
4007                                  GTK_STACK_TRANSITION_TYPE_NONE);
4008 
4009   switch (priv->startup_mode)
4010     {
4011     case STARTUP_MODE_RECENT:
4012       if (gtk_places_sidebar_get_show_recent (GTK_PLACES_SIDEBAR (priv->places_sidebar)))
4013         {
4014           operation_mode_set (impl, OPERATION_MODE_RECENT);
4015           break;
4016         }
4017       /* else fall thru */
4018 
4019     case STARTUP_MODE_CWD:
4020       switch_to_cwd (impl);
4021       break;
4022 
4023     default:
4024       g_assert_not_reached ();
4025     }
4026 
4027   gtk_stack_set_transition_type (GTK_STACK (priv->browse_header_stack),
4028                                  stack_transition);
4029   gtk_revealer_set_transition_type (GTK_REVEALER (priv->browse_header_revealer),
4030                                     revealer_transition);
4031 }
4032 
4033 static gboolean
shortcut_exists(GtkFileChooserWidget * impl,GFile * needle)4034 shortcut_exists (GtkFileChooserWidget *impl, GFile *needle)
4035 {
4036   GtkFileChooserWidgetPrivate *priv = impl->priv;
4037   GSList *haystack;
4038   GSList *l;
4039   gboolean exists;
4040 
4041   exists = FALSE;
4042 
4043   haystack = gtk_places_sidebar_list_shortcuts (GTK_PLACES_SIDEBAR (priv->places_sidebar));
4044   for (l = haystack; l; l = l->next)
4045     {
4046       GFile *hay;
4047 
4048       hay = G_FILE (l->data);
4049       if (g_file_equal (hay, needle))
4050         {
4051           exists = TRUE;
4052           break;
4053         }
4054     }
4055   g_slist_free_full (haystack, g_object_unref);
4056 
4057   return exists;
4058 }
4059 
4060 static void
add_cwd_to_sidebar_if_needed(GtkFileChooserWidget * impl)4061 add_cwd_to_sidebar_if_needed (GtkFileChooserWidget *impl)
4062 {
4063   GtkFileChooserWidgetPrivate *priv = impl->priv;
4064   char *cwd;
4065   GFile *cwd_file;
4066   GFile *home_file;
4067 
4068   cwd = g_get_current_dir ();
4069   cwd_file = g_file_new_for_path (cwd);
4070   g_free (cwd);
4071 
4072   if (shortcut_exists (impl, cwd_file))
4073     goto out;
4074 
4075   home_file = g_file_new_for_path (g_get_home_dir ());
4076 
4077   /* We only add an item for $CWD if it is different from $HOME.  This way,
4078    * applications which get launched from a shell in a terminal (by someone who
4079    * knows what they are doing) will get an item for $CWD in the places sidebar,
4080    * and "normal" applications launched from the desktop shell (whose $CWD is
4081    * $HOME) won't get any extra clutter in the sidebar.
4082    */
4083   if (!g_file_equal (home_file, cwd_file))
4084     gtk_places_sidebar_add_shortcut (GTK_PLACES_SIDEBAR (priv->places_sidebar), cwd_file);
4085 
4086   g_object_unref (home_file);
4087 
4088  out:
4089   g_object_unref (cwd_file);
4090 }
4091 
4092 /* GtkWidget::map method */
4093 static void
gtk_file_chooser_widget_map(GtkWidget * widget)4094 gtk_file_chooser_widget_map (GtkWidget *widget)
4095 {
4096   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (widget);
4097   GtkFileChooserWidgetPrivate *priv = impl->priv;
4098 
4099   profile_start ("start", NULL);
4100 
4101   priv->browse_files_interaction_frozen = FALSE;
4102 
4103   GTK_WIDGET_CLASS (gtk_file_chooser_widget_parent_class)->map (widget);
4104 
4105   settings_load (impl);
4106 
4107   add_cwd_to_sidebar_if_needed (impl);
4108 
4109   if (priv->operation_mode == OPERATION_MODE_BROWSE)
4110     {
4111       switch (priv->reload_state)
4112         {
4113         case RELOAD_EMPTY:
4114           set_startup_mode (impl);
4115           break;
4116 
4117         case RELOAD_HAS_FOLDER:
4118           /* Nothing; we are already loading or loaded, so we
4119            * don't need to reload
4120            */
4121           break;
4122 
4123         default:
4124           g_assert_not_reached ();
4125       }
4126     }
4127 
4128   profile_end ("end", NULL);
4129 }
4130 
4131 /* GtkWidget::unmap method */
4132 static void
gtk_file_chooser_widget_unmap(GtkWidget * widget)4133 gtk_file_chooser_widget_unmap (GtkWidget *widget)
4134 {
4135   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (widget);
4136   GtkFileChooserWidgetPrivate *priv = impl->priv;
4137 
4138   settings_save (impl);
4139 
4140   cancel_all_operations (impl);
4141   priv->reload_state = RELOAD_EMPTY;
4142 
4143   GTK_WIDGET_CLASS (gtk_file_chooser_widget_parent_class)->unmap (widget);
4144 }
4145 
4146 static gint
compare_directory(GtkFileSystemModel * model,GtkTreeIter * a,GtkTreeIter * b,GtkFileChooserWidget * impl)4147 compare_directory (GtkFileSystemModel   *model,
4148                    GtkTreeIter          *a,
4149                    GtkTreeIter          *b,
4150                    GtkFileChooserWidget *impl)
4151 {
4152   GtkFileChooserWidgetPrivate *priv = impl->priv;
4153   gboolean dir_a, dir_b;
4154 
4155   dir_a = g_value_get_boolean (_gtk_file_system_model_get_value (model, a, MODEL_COL_IS_FOLDER));
4156   dir_b = g_value_get_boolean (_gtk_file_system_model_get_value (model, b, MODEL_COL_IS_FOLDER));
4157 
4158   if (priv->sort_directories_first && dir_a != dir_b)
4159     return priv->list_sort_ascending ? (dir_a ? -1 : 1) : (dir_a ? 1 : -1);
4160 
4161   return 0;
4162 }
4163 
4164 static gint
compare_name(GtkFileSystemModel * model,GtkTreeIter * a,GtkTreeIter * b,GtkFileChooserWidget * impl)4165 compare_name (GtkFileSystemModel   *model,
4166               GtkTreeIter          *a,
4167               GtkTreeIter          *b,
4168               GtkFileChooserWidget *impl)
4169 {
4170   const char *key_a, *key_b;
4171   gint result;
4172 
4173   key_a = g_value_get_string (_gtk_file_system_model_get_value (model, a, MODEL_COL_NAME_COLLATED));
4174   key_b = g_value_get_string (_gtk_file_system_model_get_value (model, b, MODEL_COL_NAME_COLLATED));
4175 
4176   if (key_a && key_b)
4177     result = strcmp (key_a, key_b);
4178   else if (key_a)
4179     result = 1;
4180   else if (key_b)
4181     result = -1;
4182   else
4183     result = 0;
4184 
4185   return result;
4186 }
4187 
4188 static gint
compare_size(GtkFileSystemModel * model,GtkTreeIter * a,GtkTreeIter * b,GtkFileChooserWidget * impl)4189 compare_size (GtkFileSystemModel   *model,
4190               GtkTreeIter          *a,
4191               GtkTreeIter          *b,
4192               GtkFileChooserWidget *impl)
4193 {
4194   gint64 size_a, size_b;
4195 
4196   size_a = g_value_get_int64 (_gtk_file_system_model_get_value (model, a, MODEL_COL_SIZE));
4197   size_b = g_value_get_int64 (_gtk_file_system_model_get_value (model, b, MODEL_COL_SIZE));
4198 
4199   return size_a < size_b ? -1 : (size_a == size_b ? 0 : 1);
4200 }
4201 
4202 static gint
compare_type(GtkFileSystemModel * model,GtkTreeIter * a,GtkTreeIter * b,GtkFileChooserWidget * impl)4203 compare_type (GtkFileSystemModel   *model,
4204               GtkTreeIter          *a,
4205               GtkTreeIter          *b,
4206               GtkFileChooserWidget *impl)
4207 {
4208   const char *key_a, *key_b;
4209 
4210   key_a = g_value_get_string (_gtk_file_system_model_get_value (model, a, MODEL_COL_TYPE));
4211   key_b = g_value_get_string (_gtk_file_system_model_get_value (model, b, MODEL_COL_TYPE));
4212 
4213   return g_strcmp0 (key_a, key_b);
4214 }
4215 
4216 static gint
compare_time(GtkFileSystemModel * model,GtkTreeIter * a,GtkTreeIter * b,GtkFileChooserWidget * impl)4217 compare_time (GtkFileSystemModel   *model,
4218               GtkTreeIter          *a,
4219               GtkTreeIter          *b,
4220               GtkFileChooserWidget *impl)
4221 {
4222   glong ta, tb;
4223 
4224   ta = g_value_get_long (_gtk_file_system_model_get_value (model, a, MODEL_COL_TIME));
4225   tb = g_value_get_long (_gtk_file_system_model_get_value (model, b, MODEL_COL_TIME));
4226 
4227   return ta < tb ? -1 : (ta == tb ? 0 : 1);
4228 }
4229 
4230 static gint
compare_location(GtkFileSystemModel * model,GtkTreeIter * a,GtkTreeIter * b,GtkFileChooserWidget * impl)4231 compare_location (GtkFileSystemModel   *model,
4232                   GtkTreeIter          *a,
4233                   GtkTreeIter          *b,
4234                   GtkFileChooserWidget *impl)
4235 {
4236   const char *key_a, *key_b;
4237 
4238   key_a = g_value_get_string (_gtk_file_system_model_get_value (model, a, MODEL_COL_LOCATION_TEXT));
4239   key_b = g_value_get_string (_gtk_file_system_model_get_value (model, b, MODEL_COL_LOCATION_TEXT));
4240 
4241   return g_strcmp0 (key_a, key_b);
4242 }
4243 
4244 /* Sort callback for the filename column */
4245 static gint
name_sort_func(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user_data)4246 name_sort_func (GtkTreeModel *model,
4247                 GtkTreeIter  *a,
4248                 GtkTreeIter  *b,
4249                 gpointer      user_data)
4250 {
4251   GtkFileSystemModel *fs_model = GTK_FILE_SYSTEM_MODEL (model);
4252   GtkFileChooserWidget *impl = user_data;
4253   gint result;
4254 
4255   result = compare_directory (fs_model, a, b, impl);
4256 
4257   if (result == 0)
4258     result = compare_name (fs_model, a, b, impl);
4259 
4260   return result;
4261 }
4262 
4263 /* Sort callback for the size column */
4264 static gint
size_sort_func(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user_data)4265 size_sort_func (GtkTreeModel *model,
4266                 GtkTreeIter  *a,
4267                 GtkTreeIter  *b,
4268                 gpointer      user_data)
4269 {
4270   GtkFileSystemModel *fs_model = GTK_FILE_SYSTEM_MODEL (model);
4271   GtkFileChooserWidget *impl = user_data;
4272   gint result;
4273 
4274   result = compare_directory (fs_model, a, b, impl);
4275 
4276   if (result == 0)
4277     result = compare_size (fs_model, a, b, impl);
4278 
4279   return result;
4280 }
4281 
4282 /* Sort callback for the type column */
4283 static gint
type_sort_func(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user_data)4284 type_sort_func (GtkTreeModel *model,
4285                 GtkTreeIter  *a,
4286                 GtkTreeIter  *b,
4287                 gpointer      user_data)
4288 {
4289   GtkFileSystemModel *fs_model = GTK_FILE_SYSTEM_MODEL (model);
4290   GtkFileChooserWidget *impl = user_data;
4291   gint result;
4292 
4293   result = compare_directory (fs_model, a, b, impl);
4294 
4295   if (result == 0)
4296     result = compare_type (fs_model, a, b, impl);
4297 
4298   return result;
4299 }
4300 
4301 /* Sort callback for the time column */
4302 static gint
time_sort_func(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user_data)4303 time_sort_func (GtkTreeModel *model,
4304                 GtkTreeIter  *a,
4305                 GtkTreeIter  *b,
4306                 gpointer      user_data)
4307 {
4308   GtkFileSystemModel *fs_model = GTK_FILE_SYSTEM_MODEL (model);
4309   GtkFileChooserWidget *impl = user_data;
4310   gint result;
4311 
4312   result = compare_directory (fs_model, a, b, impl);
4313 
4314   if (result == 0)
4315     result = compare_time (fs_model, a, b, impl);
4316 
4317   return result;
4318 }
4319 
4320 static gint
recent_sort_func(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user_data)4321 recent_sort_func (GtkTreeModel *model,
4322                   GtkTreeIter  *a,
4323                   GtkTreeIter  *b,
4324                   gpointer      user_data)
4325 {
4326   GtkFileSystemModel *fs_model = GTK_FILE_SYSTEM_MODEL (model);
4327   GtkFileChooserWidget *impl = user_data;
4328   gint result;
4329 
4330   result = compare_time (fs_model, a, b, impl);
4331 
4332   if (result == 0)
4333     result = compare_name (fs_model, a, b, impl);
4334 
4335   if (result == 0)
4336     result = compare_location (fs_model, a, b, impl);
4337 
4338   return result;
4339 }
4340 
4341 static gint
search_sort_func(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user_data)4342 search_sort_func (GtkTreeModel *model,
4343                   GtkTreeIter  *a,
4344                   GtkTreeIter  *b,
4345                   gpointer      user_data)
4346 {
4347   GtkFileSystemModel *fs_model = GTK_FILE_SYSTEM_MODEL (model);
4348   GtkFileChooserWidget *impl = user_data;
4349   gint result;
4350 
4351   result = compare_location (fs_model, a, b, impl);
4352 
4353   if (result == 0)
4354     result = compare_name (fs_model, a, b, impl);
4355 
4356   if (result == 0)
4357     result = compare_time (fs_model, a, b, impl);
4358 
4359   return result;
4360 }
4361 
4362 /* Callback used when the sort column changes.  We cache the sort order for use
4363  * in name_sort_func().
4364  */
4365 static void
list_sort_column_changed_cb(GtkTreeSortable * sortable,GtkFileChooserWidget * impl)4366 list_sort_column_changed_cb (GtkTreeSortable       *sortable,
4367                              GtkFileChooserWidget *impl)
4368 {
4369   GtkFileChooserWidgetPrivate *priv = impl->priv;
4370   gint sort_column_id;
4371   GtkSortType sort_type;
4372 
4373   if (gtk_tree_sortable_get_sort_column_id (sortable, &sort_column_id, &sort_type))
4374     {
4375       priv->list_sort_ascending = (sort_type == GTK_SORT_ASCENDING);
4376       priv->sort_column = sort_column_id;
4377       priv->sort_order = sort_type;
4378     }
4379 }
4380 
4381 static void
set_busy_cursor(GtkFileChooserWidget * impl,gboolean busy)4382 set_busy_cursor (GtkFileChooserWidget *impl,
4383                  gboolean               busy)
4384 {
4385   GtkWidget *widget;
4386   GtkWindow *toplevel;
4387   GdkDisplay *display;
4388   GdkCursor *cursor;
4389 
4390   toplevel = get_toplevel (GTK_WIDGET (impl));
4391   widget = GTK_WIDGET (toplevel);
4392   if (!toplevel || !gtk_widget_get_realized (widget))
4393     return;
4394 
4395   display = gtk_widget_get_display (widget);
4396 
4397   if (busy)
4398     cursor = gdk_cursor_new_from_name (display, "progress");
4399   else
4400     cursor = NULL;
4401 
4402   gdk_window_set_cursor (gtk_widget_get_window (widget), cursor);
4403   gdk_display_flush (display);
4404 
4405   if (cursor)
4406     g_object_unref (cursor);
4407 }
4408 
4409 static void
update_columns(GtkFileChooserWidget * impl,gboolean location_visible,const gchar * time_title)4410 update_columns (GtkFileChooserWidget *impl,
4411                 gboolean              location_visible,
4412                 const gchar          *time_title)
4413 {
4414   GtkFileChooserWidgetPrivate *priv = impl->priv;
4415   gboolean need_resize = FALSE;
4416 
4417   if (gtk_tree_view_column_get_visible (priv->list_location_column) != location_visible)
4418     {
4419       gtk_tree_view_column_set_visible (priv->list_location_column, location_visible);
4420       need_resize = TRUE;
4421     }
4422 
4423   if (g_strcmp0 (gtk_tree_view_column_get_title (priv->list_time_column), time_title) != 0)
4424     {
4425       gtk_tree_view_column_set_title (priv->list_time_column, time_title);
4426       need_resize = TRUE;
4427     }
4428 
4429   if (need_resize)
4430     {
4431       /* This undoes user resizing of columns when the columns change. */
4432       gtk_tree_view_column_set_expand (priv->list_name_column, TRUE);
4433       gtk_tree_view_column_set_expand (priv->list_location_column, TRUE);
4434       gtk_tree_view_columns_autosize (GTK_TREE_VIEW (priv->browse_files_tree_view));
4435     }
4436 }
4437 
4438 /* Creates a sort model to wrap the file system model and sets it on the tree view */
4439 static void
load_set_model(GtkFileChooserWidget * impl)4440 load_set_model (GtkFileChooserWidget *impl)
4441 {
4442   GtkFileChooserWidgetPrivate *priv = impl->priv;
4443 
4444   profile_start ("start", NULL);
4445 
4446   g_assert (priv->browse_files_model != NULL);
4447 
4448   profile_msg ("    gtk_tree_view_set_model start", NULL);
4449   gtk_tree_view_set_model (GTK_TREE_VIEW (priv->browse_files_tree_view),
4450                            GTK_TREE_MODEL (priv->browse_files_model));
4451   update_columns (impl, FALSE, _("Modified"));
4452   file_list_set_sort_column_ids (impl);
4453   set_sort_column (impl);
4454   profile_msg ("    gtk_tree_view_set_model end", NULL);
4455   priv->list_sort_ascending = TRUE;
4456 
4457   g_set_object (&priv->model_for_search, priv->browse_files_model);
4458 
4459   profile_end ("end", NULL);
4460 }
4461 
4462 /* Timeout callback used when the loading timer expires */
4463 static gboolean
load_timeout_cb(gpointer data)4464 load_timeout_cb (gpointer data)
4465 {
4466   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (data);
4467   GtkFileChooserWidgetPrivate *priv = impl->priv;
4468 
4469   profile_start ("start", NULL);
4470 
4471   g_assert (priv->load_state == LOAD_PRELOAD);
4472   g_assert (priv->load_timeout_id != 0);
4473   g_assert (priv->browse_files_model != NULL);
4474 
4475   priv->load_timeout_id = 0;
4476   priv->load_state = LOAD_LOADING;
4477 
4478   load_set_model (impl);
4479 
4480   profile_end ("end", NULL);
4481 
4482   return FALSE;
4483 }
4484 
4485 /* Sets up a new load timer for the model and switches to the LOAD_PRELOAD state */
4486 static void
load_setup_timer(GtkFileChooserWidget * impl)4487 load_setup_timer (GtkFileChooserWidget *impl)
4488 {
4489   GtkFileChooserWidgetPrivate *priv = impl->priv;
4490 
4491   g_assert (priv->load_timeout_id == 0);
4492   g_assert (priv->load_state != LOAD_PRELOAD);
4493 
4494   priv->load_timeout_id = gdk_threads_add_timeout (MAX_LOADING_TIME, load_timeout_cb, impl);
4495   g_source_set_name_by_id (priv->load_timeout_id, "[gtk+] load_timeout_cb");
4496   priv->load_state = LOAD_PRELOAD;
4497 }
4498 
4499 /* Removes the load timeout; changes the impl->load_state to the specified value. */
4500 static void
load_remove_timer(GtkFileChooserWidget * impl,LoadState new_load_state)4501 load_remove_timer (GtkFileChooserWidget *impl, LoadState new_load_state)
4502 {
4503   GtkFileChooserWidgetPrivate *priv = impl->priv;
4504 
4505   if (priv->load_timeout_id != 0)
4506     {
4507       g_assert (priv->load_state == LOAD_PRELOAD);
4508 
4509       g_source_remove (priv->load_timeout_id);
4510       priv->load_timeout_id = 0;
4511     }
4512   else
4513     g_assert (priv->load_state == LOAD_EMPTY ||
4514               priv->load_state == LOAD_LOADING ||
4515               priv->load_state == LOAD_FINISHED);
4516 
4517   g_assert (new_load_state == LOAD_EMPTY ||
4518             new_load_state == LOAD_LOADING ||
4519             new_load_state == LOAD_FINISHED);
4520   priv->load_state = new_load_state;
4521 }
4522 
4523 /* Selects the first row in the file list */
4524 static void
browse_files_select_first_row(GtkFileChooserWidget * impl)4525 browse_files_select_first_row (GtkFileChooserWidget *impl)
4526 {
4527   GtkFileChooserWidgetPrivate *priv = impl->priv;
4528   GtkTreePath *path;
4529   GtkTreeIter dummy_iter;
4530   GtkTreeModel *tree_model;
4531 
4532   tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->browse_files_tree_view));
4533 
4534   if (!tree_model)
4535     return;
4536 
4537   path = gtk_tree_path_new_from_indices (0, -1);
4538 
4539   /* If the list is empty, do nothing. */
4540   if (gtk_tree_model_get_iter (tree_model, &dummy_iter, path))
4541     {
4542       /* Although the following call to gtk_tree_view_set_cursor() is intended to
4543        * only change the focus to the first row (not select it), GtkTreeView *will*
4544        * select the row anyway due to bug #492206.  So, we'll use a flag to
4545        * keep our own callbacks from changing the location_entry when the selection
4546        * is changed.  This entire function, browse_files_select_first_row(), may
4547        * go away when that bug is fixed in GtkTreeView.
4548        */
4549       priv->auto_selecting_first_row = TRUE;
4550 
4551       gtk_tree_view_set_cursor (GTK_TREE_VIEW (priv->browse_files_tree_view), path, NULL, FALSE);
4552 
4553       priv->auto_selecting_first_row = FALSE;
4554     }
4555   gtk_tree_path_free (path);
4556 }
4557 
4558 struct center_selected_row_closure {
4559   GtkFileChooserWidget *impl;
4560   gboolean already_centered;
4561 };
4562 
4563 /* Callback used from gtk_tree_selection_selected_foreach(); centers the
4564  * selected row in the tree view.
4565  */
4566 static void
center_selected_row_foreach_cb(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)4567 center_selected_row_foreach_cb (GtkTreeModel      *model,
4568                                 GtkTreePath       *path,
4569                                 GtkTreeIter       *iter,
4570                                 gpointer           data)
4571 {
4572   struct center_selected_row_closure *closure;
4573 
4574   closure = data;
4575   if (closure->already_centered)
4576     return;
4577 
4578   gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (closure->impl->priv->browse_files_tree_view), path, NULL, TRUE, 0.5, 0.0);
4579   closure->already_centered = TRUE;
4580 }
4581 
4582 /* Centers the selected row in the tree view */
4583 static void
browse_files_center_selected_row(GtkFileChooserWidget * impl)4584 browse_files_center_selected_row (GtkFileChooserWidget *impl)
4585 {
4586   GtkFileChooserWidgetPrivate *priv = impl->priv;
4587   struct center_selected_row_closure closure;
4588   GtkTreeSelection *selection;
4589 
4590   closure.impl = impl;
4591   closure.already_centered = FALSE;
4592 
4593   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view));
4594   gtk_tree_selection_selected_foreach (selection, center_selected_row_foreach_cb, &closure);
4595 }
4596 
4597 static gboolean
show_and_select_files(GtkFileChooserWidget * impl,GSList * files)4598 show_and_select_files (GtkFileChooserWidget *impl,
4599                        GSList               *files)
4600 {
4601   GtkFileChooserWidgetPrivate *priv = impl->priv;
4602   GtkTreeSelection *selection;
4603   GtkFileSystemModel *fsmodel;
4604   gboolean enabled_hidden, removed_filters;
4605   gboolean selected_a_file;
4606   GSList *walk;
4607 
4608   g_assert (priv->load_state == LOAD_FINISHED);
4609   g_assert (priv->browse_files_model != NULL);
4610 
4611   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view));
4612   fsmodel = GTK_FILE_SYSTEM_MODEL (gtk_tree_view_get_model (GTK_TREE_VIEW (priv->browse_files_tree_view)));
4613 
4614   g_assert (fsmodel == priv->browse_files_model);
4615 
4616   enabled_hidden = priv->show_hidden;
4617   removed_filters = (priv->current_filter == NULL);
4618 
4619   selected_a_file = FALSE;
4620 
4621   for (walk = files; walk; walk = walk->next)
4622     {
4623       GFile *file = walk->data;
4624       GtkTreeIter iter;
4625 
4626       /* Is it a hidden file? */
4627 
4628       if (!_gtk_file_system_model_get_iter_for_file (fsmodel, &iter, file))
4629         continue;
4630 
4631       if (!_gtk_file_system_model_iter_is_visible (fsmodel, &iter))
4632         {
4633           GFileInfo *info = _gtk_file_system_model_get_info (fsmodel, &iter);
4634 
4635           if (!enabled_hidden &&
4636               (g_file_info_get_is_hidden (info) ||
4637                g_file_info_get_is_backup (info)))
4638             {
4639               g_object_set (impl, "show-hidden", TRUE, NULL);
4640               enabled_hidden = TRUE;
4641             }
4642         }
4643 
4644       /* Is it a filtered file? */
4645 
4646       if (!_gtk_file_system_model_get_iter_for_file (fsmodel, &iter, file))
4647         continue; /* re-get the iter as it may change when the model refilters */
4648 
4649       if (!_gtk_file_system_model_iter_is_visible (fsmodel, &iter))
4650         {
4651           /* Maybe we should have a way to ask the fsmodel if it had filtered a file */
4652           if (!removed_filters)
4653             {
4654               set_current_filter (impl, NULL);
4655               removed_filters = TRUE;
4656             }
4657         }
4658 
4659       /* Okay, can we select the file now? */
4660       if (!_gtk_file_system_model_get_iter_for_file (fsmodel, &iter, file))
4661         continue;
4662 
4663       if (_gtk_file_system_model_iter_is_visible (fsmodel, &iter))
4664         {
4665           GtkTreePath *path;
4666 
4667           gtk_tree_selection_select_iter (selection, &iter);
4668 
4669           path = gtk_tree_model_get_path (GTK_TREE_MODEL (fsmodel), &iter);
4670           gtk_tree_view_set_cursor (GTK_TREE_VIEW (priv->browse_files_tree_view),
4671                                     path, NULL, FALSE);
4672           gtk_tree_path_free (path);
4673 
4674           selected_a_file = TRUE;
4675         }
4676     }
4677 
4678   browse_files_center_selected_row (impl);
4679 
4680   return selected_a_file;
4681 }
4682 
4683 /* Processes the pending operation when a folder is finished loading */
4684 static void
pending_select_files_process(GtkFileChooserWidget * impl)4685 pending_select_files_process (GtkFileChooserWidget *impl)
4686 {
4687   GtkFileChooserWidgetPrivate *priv = impl->priv;
4688 
4689   g_assert (priv->load_state == LOAD_FINISHED);
4690   g_assert (priv->browse_files_model != NULL);
4691 
4692   if (priv->pending_select_files)
4693     {
4694       show_and_select_files (impl, priv->pending_select_files);
4695       pending_select_files_free (impl);
4696       browse_files_center_selected_row (impl);
4697     }
4698   else
4699     {
4700       /* We only select the first row if the chooser is actually mapped ---
4701        * selecting the first row is to help the user when he is interacting with
4702        * the chooser, but sometimes a chooser works not on behalf of the user,
4703        * but rather on behalf of something else like GtkFileChooserButton.  In
4704        * that case, the chooser's selection should be what the caller expects,
4705        * as the user can't see that something else got selected.  See bug #165264.
4706        */
4707       if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN &&
4708           gtk_widget_get_mapped (GTK_WIDGET (impl)))
4709         browse_files_select_first_row (impl);
4710     }
4711 
4712   g_assert (priv->pending_select_files == NULL);
4713 }
4714 
4715 static void
show_error_on_reading_current_folder(GtkFileChooserWidget * impl,GError * error)4716 show_error_on_reading_current_folder (GtkFileChooserWidget *impl, GError *error)
4717 {
4718   GtkFileChooserWidgetPrivate *priv = impl->priv;
4719   GFileInfo *info;
4720   char *msg;
4721 
4722   info = g_file_query_info (priv->current_folder,
4723                             G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
4724                             G_FILE_QUERY_INFO_NONE,
4725                             NULL,
4726                             NULL);
4727   if (info)
4728     {
4729       msg = g_strdup_printf (_("Could not read the contents of %s"), g_file_info_get_display_name (info));
4730       g_object_unref (info);
4731     }
4732   else
4733     msg = g_strdup (_("Could not read the contents of the folder"));
4734 
4735   error_message (impl, msg, error->message);
4736   g_free (msg);
4737 }
4738 
4739 /* Callback used when the file system model finishes loading */
4740 static void
browse_files_model_finished_loading_cb(GtkFileSystemModel * model,GError * error,GtkFileChooserWidget * impl)4741 browse_files_model_finished_loading_cb (GtkFileSystemModel   *model,
4742                                         GError               *error,
4743                                         GtkFileChooserWidget *impl)
4744 {
4745   GtkFileChooserWidgetPrivate *priv = impl->priv;
4746 
4747   profile_start ("start", NULL);
4748 
4749   if (error)
4750     {
4751       set_busy_cursor (impl, FALSE);
4752       show_error_on_reading_current_folder (impl, error);
4753     }
4754 
4755   if (priv->load_state == LOAD_PRELOAD)
4756     {
4757       load_remove_timer (impl, LOAD_FINISHED);
4758       load_set_model (impl);
4759     }
4760   else if (priv->load_state == LOAD_LOADING)
4761     {
4762       /* Nothing */
4763     }
4764   else
4765     {
4766       /* We can't g_assert_not_reached(), as something other than us may have
4767        *  initiated a folder reload.  See #165556.
4768        */
4769       profile_end ("end", NULL);
4770       return;
4771     }
4772 
4773   g_assert (priv->load_timeout_id == 0);
4774 
4775   priv->load_state = LOAD_FINISHED;
4776 
4777   pending_select_files_process (impl);
4778   set_busy_cursor (impl, FALSE);
4779 #ifdef PROFILE_FILE_CHOOSER
4780   access ("MARK: *** FINISHED LOADING", F_OK);
4781 #endif
4782 
4783   profile_end ("end", NULL);
4784 }
4785 
4786 /* Callback used when file system model adds or updates a file.
4787  * We detect here when a new renamed file appears and reveal it */
4788 static void
browse_files_model_row_changed_cb(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)4789 browse_files_model_row_changed_cb (GtkTreeModel *model,
4790                                    GtkTreePath  *path,
4791                                    GtkTreeIter  *iter,
4792                                    gpointer      data)
4793 {
4794   GtkFileChooserWidget *impl = data;
4795   GtkFileChooserWidgetPrivate *priv = impl->priv;
4796   GFile *file;
4797   GSList files;
4798 
4799   if (priv->renamed_file)
4800     {
4801       gtk_tree_model_get (model, iter, MODEL_COL_FILE, &file, -1);
4802       if (g_file_equal (priv->renamed_file, file))
4803         {
4804           g_clear_object (&priv->renamed_file);
4805 
4806           files.data = (gpointer) file;
4807           files.next = NULL;
4808 
4809           show_and_select_files (impl, &files);
4810         }
4811 
4812       g_object_unref (file);
4813     }
4814 }
4815 
4816 static void
stop_loading_and_clear_list_model(GtkFileChooserWidget * impl,gboolean remove)4817 stop_loading_and_clear_list_model (GtkFileChooserWidget *impl,
4818                                    gboolean              remove)
4819 {
4820   GtkFileChooserWidgetPrivate *priv = impl->priv;
4821 
4822   load_remove_timer (impl, LOAD_EMPTY);
4823 
4824   g_set_object (&priv->browse_files_model, NULL);
4825 
4826   if (remove)
4827     gtk_tree_view_set_model (GTK_TREE_VIEW (priv->browse_files_tree_view), NULL);
4828 }
4829 
4830 /* Replace 'target' with 'replacement' in the input string. */
4831 static gchar *
string_replace(const gchar * input,const gchar * target,const gchar * replacement)4832 string_replace (const gchar *input,
4833                 const gchar *target,
4834                 const gchar *replacement)
4835 {
4836   gchar **pieces;
4837   gchar *output;
4838 
4839   pieces = g_strsplit (input, target, -1);
4840   output = g_strjoinv (replacement, pieces);
4841   g_strfreev (pieces);
4842 
4843   return output;
4844 }
4845 
4846 static void
replace_ratio(gchar ** str)4847 replace_ratio (gchar **str)
4848 {
4849   if (g_get_charset (NULL))
4850     {
4851       gchar *ret;
4852       ret = string_replace (*str, ":", "\xE2\x80\x8E∶");
4853       g_free (*str);
4854       *str = ret;
4855     }
4856 }
4857 
4858 static char *
my_g_format_date_for_display(GtkFileChooserWidget * impl,glong secs)4859 my_g_format_date_for_display (GtkFileChooserWidget *impl,
4860                               glong                 secs)
4861 {
4862   GtkFileChooserWidgetPrivate *priv = impl->priv;
4863   GDateTime *now, *time;
4864   GDateTime *now_date, *date;
4865   ClockFormat clock_format;
4866   const gchar *format;
4867   gchar *date_str;
4868   GSettings *settings;
4869   gint days_ago;
4870 
4871   time = g_date_time_new_from_unix_local (secs);
4872   date = g_date_time_new_local (g_date_time_get_year (time),
4873                                 g_date_time_get_month (time),
4874                                 g_date_time_get_day_of_month (time),
4875                                 0, 0, 0);
4876 
4877   settings = _gtk_file_chooser_get_settings_for_widget (GTK_WIDGET (impl));
4878   clock_format = g_settings_get_enum (settings, "clock-format");
4879 
4880   now = g_date_time_new_now_local ();
4881   now_date = g_date_time_new_local (g_date_time_get_year (now),
4882                                     g_date_time_get_month (now),
4883                                     g_date_time_get_day_of_month (now),
4884                                     0, 0, 0);
4885   days_ago = g_date_time_difference (now_date, date) / G_TIME_SPAN_DAY;
4886 
4887   /* Translators: see g_date_time_format() for details on the format */
4888   if (days_ago < 1)
4889     {
4890       if (priv->show_time)
4891         format = "";
4892       else if (clock_format == CLOCK_FORMAT_24)
4893         format = _("%H:%M");
4894       else
4895         format = _("%l:%M %p");
4896     }
4897   else if (days_ago < 2)
4898     {
4899       format = _("Yesterday");
4900     }
4901   else if (days_ago < 7)
4902     {
4903       format = "%a"; /* Days from last week */
4904     }
4905   else if (g_date_time_get_year (now) == g_date_time_get_year (time))
4906     {
4907       format = _("%-e %b");
4908     }
4909   else
4910     {
4911       format = N_("%-e %b %Y");
4912     }
4913 
4914   date_str = g_date_time_format (time, format);
4915   replace_ratio (&date_str);
4916 
4917   g_date_time_unref (now);
4918   g_date_time_unref (now_date);
4919   g_date_time_unref (time);
4920   g_date_time_unref (date);
4921 
4922   return date_str;
4923 }
4924 
4925 static char *
my_g_format_time_for_display(GtkFileChooserWidget * impl,glong secs)4926 my_g_format_time_for_display (GtkFileChooserWidget *impl,
4927                               glong                 secs)
4928 {
4929   GDateTime *time;
4930   ClockFormat clock_format;
4931   const gchar *format;
4932   gchar *date_str;
4933   GSettings *settings;
4934 
4935   time = g_date_time_new_from_unix_local (secs);
4936 
4937   settings = _gtk_file_chooser_get_settings_for_widget (GTK_WIDGET (impl));
4938   clock_format = g_settings_get_enum (settings, "clock-format");
4939 
4940   if (clock_format == CLOCK_FORMAT_24)
4941     format = _("%H:%M");
4942   else
4943     format = _("%l:%M %p");
4944 
4945   date_str = g_date_time_format (time, format);
4946   replace_ratio (&date_str);
4947 
4948   g_date_time_unref (time);
4949 
4950   return date_str;
4951 }
4952 
4953 static void
copy_attribute(GFileInfo * to,GFileInfo * from,const gchar * attribute)4954 copy_attribute (GFileInfo   *to,
4955                 GFileInfo   *from,
4956                 const gchar *attribute)
4957 {
4958   GFileAttributeType type;
4959   gpointer value;
4960 
4961   if (g_file_info_get_attribute_data (from, attribute, &type, &value, NULL))
4962     g_file_info_set_attribute (to, attribute, type, value);
4963 }
4964 
4965 static void
file_system_model_got_thumbnail(GObject * object,GAsyncResult * res,gpointer data)4966 file_system_model_got_thumbnail (GObject      *object,
4967                                  GAsyncResult *res,
4968                                  gpointer      data)
4969 {
4970   GtkFileSystemModel *model = data; /* might be unreffed if operation was cancelled */
4971   GFile *file = G_FILE (object);
4972   GFileInfo *queried, *info;
4973   GtkTreeIter iter;
4974 
4975   queried = g_file_query_info_finish (file, res, NULL);
4976   if (queried == NULL)
4977     return;
4978 
4979   gdk_threads_enter ();
4980 
4981   /* now we know model is valid */
4982 
4983   /* file was deleted */
4984   if (!_gtk_file_system_model_get_iter_for_file (model, &iter, file))
4985     {
4986       g_object_unref (queried);
4987       gdk_threads_leave ();
4988       return;
4989     }
4990 
4991   info = g_file_info_dup (_gtk_file_system_model_get_info (model, &iter));
4992 
4993   copy_attribute (info, queried, G_FILE_ATTRIBUTE_THUMBNAIL_PATH);
4994   copy_attribute (info, queried, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED);
4995   copy_attribute (info, queried, G_FILE_ATTRIBUTE_STANDARD_ICON);
4996 
4997   _gtk_file_system_model_update_file (model, file, info);
4998 
4999   g_object_unref (info);
5000   g_object_unref (queried);
5001 
5002   gdk_threads_leave ();
5003 }
5004 
5005 /* Copied from src/nautilus_file.c:get_description() */
5006 struct {
5007   const char *icon_name;
5008   const char *display_name;
5009 } mime_type_map[] = {
5010   { "application-x-executable", N_("Program") },
5011   { "audio-x-generic", N_("Audio") },
5012   { "font-x-generic", N_("Font") },
5013   { "image-x-generic", N_("Image") },
5014   { "package-x-generic", N_("Archive") },
5015   { "text-html", N_("Markup") },
5016   { "text-x-generic", N_("Text") },
5017   { "text-x-generic-template", N_("Text") },
5018   { "text-x-script", N_("Program") },
5019   { "video-x-generic", N_("Video") },
5020   { "x-office-address-book", N_("Contacts") },
5021   { "x-office-calendar", N_("Calendar") },
5022   { "x-office-document", N_("Document") },
5023   { "x-office-presentation", N_("Presentation") },
5024   { "x-office-spreadsheet", N_("Spreadsheet") },
5025 };
5026 
5027 static char *
get_category_from_content_type(const char * content_type)5028 get_category_from_content_type (const char *content_type)
5029 {
5030   char *icon_name;
5031   char *basic_type = NULL;
5032 
5033   icon_name = g_content_type_get_generic_icon_name (content_type);
5034   if (icon_name != NULL)
5035     {
5036       int i;
5037 
5038       for (i = 0; i < G_N_ELEMENTS (mime_type_map); i++)
5039         {
5040           if (strcmp (mime_type_map[i].icon_name, icon_name) == 0)
5041             {
5042               basic_type = g_strdup (_(mime_type_map[i].display_name));
5043               break;
5044             }
5045         }
5046 
5047       g_free (icon_name);
5048     }
5049 
5050   if (basic_type == NULL)
5051     {
5052       basic_type = g_content_type_get_description (content_type);
5053       if (basic_type == NULL)
5054         {
5055           basic_type = g_strdup (_("Unknown"));
5056         }
5057     }
5058 
5059   return basic_type;
5060 }
5061 
5062 static char *
get_type_information(GtkFileChooserWidget * impl,GFileInfo * info)5063 get_type_information (GtkFileChooserWidget *impl,
5064                       GFileInfo            *info)
5065 {
5066   const char *content_type;
5067   char *mime_type;
5068   char *description;
5069 
5070   content_type = g_file_info_get_content_type (info);
5071   if (!content_type)
5072     content_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE);
5073   if (!content_type)
5074     goto end;
5075 
5076   switch (impl->priv->type_format)
5077     {
5078     case TYPE_FORMAT_MIME:
5079       mime_type = g_content_type_get_mime_type (content_type);
5080       return mime_type ? mime_type : g_strdup (content_type);
5081 
5082     case TYPE_FORMAT_DESCRIPTION:
5083       description = g_content_type_get_description (content_type);
5084       return description ? description : g_strdup (content_type);
5085 
5086     case TYPE_FORMAT_CATEGORY:
5087       return get_category_from_content_type (content_type);
5088 
5089     default:
5090       g_assert_not_reached ();
5091     }
5092 
5093 end:
5094   return g_strdup ("");
5095 }
5096 
5097 static gboolean
file_system_model_set(GtkFileSystemModel * model,GFile * file,GFileInfo * info,int column,GValue * value,gpointer data)5098 file_system_model_set (GtkFileSystemModel *model,
5099                        GFile              *file,
5100                        GFileInfo          *info,
5101                        int                 column,
5102                        GValue             *value,
5103                        gpointer            data)
5104 {
5105   GtkFileChooserWidget *impl = data;
5106   GtkFileChooserWidgetPrivate *priv = impl->priv;
5107 
5108   switch (column)
5109     {
5110     case MODEL_COL_FILE:
5111       g_value_set_object (value, file);
5112       break;
5113     case MODEL_COL_NAME:
5114       if (info == NULL)
5115         g_value_set_string (value, DEFAULT_NEW_FOLDER_NAME);
5116       else
5117         g_value_set_string (value, g_file_info_get_display_name (info));
5118       break;
5119     case MODEL_COL_NAME_COLLATED:
5120       if (info == NULL)
5121         g_value_take_string (value, g_utf8_collate_key_for_filename (DEFAULT_NEW_FOLDER_NAME, -1));
5122       else
5123         g_value_take_string (value, g_utf8_collate_key_for_filename (g_file_info_get_display_name (info), -1));
5124       break;
5125     case MODEL_COL_IS_FOLDER:
5126       g_value_set_boolean (value, info == NULL || _gtk_file_info_consider_as_directory (info));
5127       break;
5128     case MODEL_COL_IS_SENSITIVE:
5129       if (info)
5130         {
5131           gboolean sensitive = TRUE;
5132 
5133           if (!(priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
5134                 || priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER))
5135             {
5136               sensitive = TRUE; /* for file modes... */
5137             }
5138           else if (!_gtk_file_info_consider_as_directory (info))
5139             {
5140               sensitive = FALSE; /* for folder modes, files are not sensitive... */
5141             }
5142           else
5143             {
5144               /* ... and for folder modes, folders are sensitive only if the filter says so */
5145               GtkTreeIter iter;
5146               if (!_gtk_file_system_model_get_iter_for_file (model, &iter, file))
5147                 g_assert_not_reached ();
5148               sensitive = !_gtk_file_system_model_iter_is_filtered_out (model, &iter);
5149             }
5150 
5151           g_value_set_boolean (value, sensitive);
5152         }
5153       else
5154         g_value_set_boolean (value, TRUE);
5155       break;
5156     case MODEL_COL_SURFACE:
5157       if (info)
5158         {
5159           if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_ICON))
5160             {
5161               g_value_take_boxed (value, _gtk_file_info_render_icon (info, GTK_WIDGET (impl), priv->icon_size));
5162             }
5163           else
5164             {
5165               GtkTreeModel *tree_model;
5166               GtkTreePath *start, *end;
5167               GtkTreeIter iter;
5168               gboolean visible;
5169 
5170               if (priv->browse_files_tree_view == NULL ||
5171                   g_file_info_has_attribute (info, "filechooser::queried"))
5172                 return FALSE;
5173 
5174               tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->browse_files_tree_view));
5175               if (tree_model != GTK_TREE_MODEL (model))
5176                 return FALSE;
5177 
5178               if (!_gtk_file_system_model_get_iter_for_file (model, &iter, file))
5179                 g_assert_not_reached ();
5180 
5181               if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (priv->browse_files_tree_view), &start, &end))
5182                 {
5183                   GtkTreePath *path;
5184 
5185                   gtk_tree_path_prev (start);
5186                   gtk_tree_path_next (end);
5187                   path = gtk_tree_model_get_path (tree_model, &iter);
5188                   visible = gtk_tree_path_compare (start, path) != 1 &&
5189                             gtk_tree_path_compare (path, end) != 1;
5190                   gtk_tree_path_free (path);
5191                   gtk_tree_path_free (start);
5192                   gtk_tree_path_free (end);
5193                 }
5194               else
5195                 visible = TRUE;
5196               if (visible)
5197                 {
5198                   g_file_info_set_attribute_boolean (info, "filechooser::queried", TRUE);
5199                   g_file_query_info_async (file,
5200                                            G_FILE_ATTRIBUTE_THUMBNAIL_PATH ","
5201                                            G_FILE_ATTRIBUTE_THUMBNAILING_FAILED ","
5202                                            G_FILE_ATTRIBUTE_STANDARD_ICON,
5203                                            G_FILE_QUERY_INFO_NONE,
5204                                            G_PRIORITY_DEFAULT,
5205                                            _gtk_file_system_model_get_cancellable (model),
5206                                            file_system_model_got_thumbnail,
5207                                            model);
5208                 }
5209               return FALSE;
5210             }
5211         }
5212       else
5213         g_value_set_boxed (value, NULL);
5214       break;
5215     case MODEL_COL_SIZE:
5216       g_value_set_int64 (value, info ? g_file_info_get_size (info) : 0);
5217       break;
5218     case MODEL_COL_SIZE_TEXT:
5219       if (info == NULL || _gtk_file_info_consider_as_directory (info))
5220         g_value_set_string (value, NULL);
5221       else
5222         g_value_take_string (value, g_format_size (g_file_info_get_size (info)));
5223       break;
5224     case MODEL_COL_TYPE:
5225       if (info == NULL || _gtk_file_info_consider_as_directory (info))
5226         g_value_set_string (value, NULL);
5227       else
5228         g_value_take_string (value, get_type_information (impl, info));
5229       break;
5230     case MODEL_COL_TIME:
5231     case MODEL_COL_DATE_TEXT:
5232     case MODEL_COL_TIME_TEXT:
5233       {
5234         glong time;
5235         if (info == NULL)
5236           break;
5237         if (priv->operation_mode == OPERATION_MODE_RECENT)
5238           time = (glong) g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS);
5239         else
5240           time = (glong) g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
5241         if (column == MODEL_COL_TIME)
5242           g_value_set_long (value, time);
5243         else if (time == 0)
5244           g_value_set_static_string (value, _("Unknown"));
5245         else if (column == MODEL_COL_DATE_TEXT)
5246           g_value_take_string (value, my_g_format_date_for_display (impl, time));
5247         else
5248           g_value_take_string (value, my_g_format_time_for_display (impl, time));
5249         break;
5250       }
5251     case MODEL_COL_ELLIPSIZE:
5252       g_value_set_enum (value, info ? PANGO_ELLIPSIZE_END : PANGO_ELLIPSIZE_NONE);
5253       break;
5254     case MODEL_COL_LOCATION_TEXT:
5255       {
5256         GFile *home_location;
5257         GFile *dir_location;
5258         gchar *location;
5259 
5260         home_location = g_file_new_for_path (g_get_home_dir ());
5261         if (file)
5262           dir_location = g_file_get_parent (file);
5263         else
5264           dir_location = NULL;
5265 
5266         if (dir_location && file_is_recent_uri (dir_location))
5267           {
5268             const char *target_uri;
5269             GFile *target;
5270 
5271             target_uri = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
5272             target = g_file_new_for_uri (target_uri);
5273             g_object_unref (dir_location);
5274             dir_location = g_file_get_parent (target);
5275             g_object_unref (target);
5276           }
5277 
5278         if (!dir_location)
5279           location = g_strdup ("/");
5280         else if (priv->current_folder && g_file_equal (priv->current_folder, dir_location))
5281           location = g_strdup ("");
5282         else if (g_file_equal (home_location, dir_location))
5283           location = g_strdup (_("Home"));
5284         else if (g_file_has_prefix (dir_location, home_location))
5285           {
5286             gchar *relative_path;
5287 
5288             relative_path = g_file_get_relative_path (home_location, dir_location);
5289             location = g_filename_display_name (relative_path);
5290 
5291             g_free (relative_path);
5292           }
5293         else
5294           location = g_file_get_path (dir_location);
5295 
5296         g_value_take_string (value, location);
5297 
5298         if (dir_location)
5299           g_object_unref (dir_location);
5300         g_object_unref (home_location);
5301       }
5302       break;
5303     default:
5304       g_assert_not_reached ();
5305       break;
5306     }
5307 
5308   return TRUE;
5309 }
5310 
5311 /* Gets rid of the old list model and creates a new one for the current folder */
5312 static gboolean
set_list_model(GtkFileChooserWidget * impl,GError ** error)5313 set_list_model (GtkFileChooserWidget  *impl,
5314                 GError               **error)
5315 {
5316   GtkFileChooserWidgetPrivate *priv = impl->priv;
5317 
5318   g_assert (priv->current_folder != NULL);
5319 
5320   if (priv->browse_files_model &&
5321       _gtk_file_system_model_get_directory (priv->browse_files_model) == priv->current_folder)
5322     return TRUE;
5323 
5324   profile_start ("start", NULL);
5325 
5326   stop_loading_and_clear_list_model (impl, TRUE);
5327 
5328   set_busy_cursor (impl, TRUE);
5329 
5330   priv->browse_files_model =
5331     _gtk_file_system_model_new_for_directory (priv->current_folder,
5332                                               MODEL_ATTRIBUTES,
5333                                               file_system_model_set,
5334                                               impl,
5335                                               MODEL_COLUMN_TYPES);
5336 
5337   _gtk_file_system_model_set_show_hidden (priv->browse_files_model, priv->show_hidden);
5338 
5339   profile_msg ("    set sort function", NULL);
5340   gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->browse_files_model), MODEL_COL_NAME, name_sort_func, impl, NULL);
5341   gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->browse_files_model), MODEL_COL_SIZE, size_sort_func, impl, NULL);
5342   gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->browse_files_model), MODEL_COL_TYPE, type_sort_func, impl, NULL);
5343   gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->browse_files_model), MODEL_COL_TIME, time_sort_func, impl, NULL);
5344   gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (priv->browse_files_model), NULL, NULL, NULL);
5345   set_sort_column (impl);
5346   priv->list_sort_ascending = TRUE;
5347   g_signal_connect (priv->browse_files_model, "sort-column-changed",
5348                     G_CALLBACK (list_sort_column_changed_cb), impl);
5349 
5350   load_setup_timer (impl); /* This changes the state to LOAD_PRELOAD */
5351 
5352   g_signal_connect (priv->browse_files_model, "finished-loading",
5353                     G_CALLBACK (browse_files_model_finished_loading_cb), impl);
5354 
5355   g_signal_connect (priv->browse_files_model, "row-changed",
5356                     G_CALLBACK (browse_files_model_row_changed_cb), impl);
5357 
5358   _gtk_file_system_model_set_filter (priv->browse_files_model, priv->current_filter);
5359 
5360   profile_end ("end", NULL);
5361 
5362   return TRUE;
5363 }
5364 
5365 struct update_chooser_entry_selected_foreach_closure {
5366   int num_selected;
5367   GtkTreeIter first_selected_iter;
5368 };
5369 
5370 static gint
compare_utf8_filenames(const gchar * a,const gchar * b)5371 compare_utf8_filenames (const gchar *a,
5372                         const gchar *b)
5373 {
5374   gchar *a_folded, *b_folded;
5375   gint retval;
5376 
5377   a_folded = g_utf8_strdown (a, -1);
5378   b_folded = g_utf8_strdown (b, -1);
5379 
5380   retval = strcmp (a_folded, b_folded);
5381 
5382   g_free (a_folded);
5383   g_free (b_folded);
5384 
5385   return retval;
5386 }
5387 
5388 static void
update_chooser_entry_selected_foreach(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)5389 update_chooser_entry_selected_foreach (GtkTreeModel *model,
5390                                        GtkTreePath  *path,
5391                                        GtkTreeIter  *iter,
5392                                        gpointer      data)
5393 {
5394   struct update_chooser_entry_selected_foreach_closure *closure;
5395 
5396   closure = data;
5397   closure->num_selected++;
5398 
5399   if (closure->num_selected == 1)
5400     closure->first_selected_iter = *iter;
5401 }
5402 
5403 static void
update_chooser_entry(GtkFileChooserWidget * impl)5404 update_chooser_entry (GtkFileChooserWidget *impl)
5405 {
5406   GtkFileChooserWidgetPrivate *priv = impl->priv;
5407   GtkTreeSelection *selection;
5408   struct update_chooser_entry_selected_foreach_closure closure;
5409 
5410   /* no need to update the file chooser's entry if there's no entry */
5411   if (priv->operation_mode == OPERATION_MODE_SEARCH ||
5412       !priv->location_entry)
5413     return;
5414 
5415   if (!(priv->action == GTK_FILE_CHOOSER_ACTION_SAVE
5416         || priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER
5417         || ((priv->action == GTK_FILE_CHOOSER_ACTION_OPEN
5418              || priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
5419             && priv->location_mode == LOCATION_MODE_FILENAME_ENTRY)))
5420     return;
5421 
5422   g_assert (priv->location_entry != NULL);
5423 
5424   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view));
5425   closure.num_selected = 0;
5426   gtk_tree_selection_selected_foreach (selection, update_chooser_entry_selected_foreach, &closure);
5427 
5428   if (closure.num_selected == 0)
5429     {
5430       if (priv->operation_mode == OPERATION_MODE_RECENT)
5431         _gtk_file_chooser_entry_set_base_folder (GTK_FILE_CHOOSER_ENTRY (priv->location_entry), NULL);
5432       else
5433         goto maybe_clear_entry;
5434     }
5435   else if (closure.num_selected == 1)
5436     {
5437       if (priv->operation_mode == OPERATION_MODE_BROWSE)
5438         {
5439           GFileInfo *info;
5440           gboolean change_entry;
5441 
5442           info = _gtk_file_system_model_get_info (priv->browse_files_model, &closure.first_selected_iter);
5443 
5444           /* If the cursor moved to the row of the newly created folder,
5445            * retrieving info will return NULL.
5446            */
5447           if (!info)
5448             return;
5449 
5450           g_free (priv->browse_files_last_selected_name);
5451           priv->browse_files_last_selected_name =
5452             g_strdup (g_file_info_get_display_name (info));
5453 
5454           if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
5455               priv->action == GTK_FILE_CHOOSER_ACTION_SAVE ||
5456               priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
5457             {
5458               /* Don't change the name when clicking on a folder... */
5459               change_entry = !_gtk_file_info_consider_as_directory (info);
5460             }
5461           else
5462             change_entry = TRUE; /* ... unless we are in SELECT_FOLDER mode */
5463 
5464           if (change_entry && !priv->auto_selecting_first_row)
5465             {
5466               g_signal_handlers_block_by_func (priv->location_entry, G_CALLBACK (location_entry_changed_cb), impl);
5467               gtk_entry_set_text (GTK_ENTRY (priv->location_entry), priv->browse_files_last_selected_name);
5468               g_signal_handlers_unblock_by_func (priv->location_entry, G_CALLBACK (location_entry_changed_cb), impl);
5469 
5470               if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE)
5471                 _gtk_file_chooser_entry_select_filename (GTK_FILE_CHOOSER_ENTRY (priv->location_entry));
5472             }
5473 
5474           return;
5475         }
5476       else if (priv->operation_mode == OPERATION_MODE_RECENT
5477                && priv->action == GTK_FILE_CHOOSER_ACTION_SAVE)
5478         {
5479           GFile *folder;
5480 
5481           /* Set the base folder on the name entry, so it will do completion relative to the correct recent-folder */
5482 
5483           gtk_tree_model_get (GTK_TREE_MODEL (priv->recent_model), &closure.first_selected_iter,
5484                               MODEL_COL_FILE, &folder,
5485                               -1);
5486           _gtk_file_chooser_entry_set_base_folder (GTK_FILE_CHOOSER_ENTRY (priv->location_entry), folder);
5487           g_object_unref (folder);
5488           return;
5489         }
5490     }
5491   else
5492     {
5493       g_assert (!(priv->action == GTK_FILE_CHOOSER_ACTION_SAVE ||
5494                   priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER));
5495 
5496       /* Multiple selection, so just clear the entry. */
5497       g_free (priv->browse_files_last_selected_name);
5498       priv->browse_files_last_selected_name = NULL;
5499 
5500       g_signal_handlers_block_by_func (priv->location_entry, G_CALLBACK (location_entry_changed_cb), impl);
5501       gtk_entry_set_text (GTK_ENTRY (priv->location_entry), "");
5502       g_signal_handlers_unblock_by_func (priv->location_entry, G_CALLBACK (location_entry_changed_cb), impl);
5503       return;
5504     }
5505 
5506  maybe_clear_entry:
5507 
5508   if ((priv->action == GTK_FILE_CHOOSER_ACTION_OPEN || priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
5509       && priv->browse_files_last_selected_name)
5510     {
5511       const char *entry_text;
5512       int len;
5513       gboolean clear_entry;
5514 
5515       entry_text = gtk_entry_get_text (GTK_ENTRY (priv->location_entry));
5516       len = strlen (entry_text);
5517       if (len != 0)
5518         {
5519           /* The file chooser entry may have appended a "/" to its text.
5520            * So take it out, and compare the result to the old selection.
5521            */
5522           if (entry_text[len - 1] == G_DIR_SEPARATOR)
5523             {
5524               gchar *tmp;
5525 
5526               tmp = g_strndup (entry_text, len - 1);
5527               clear_entry = (compare_utf8_filenames (priv->browse_files_last_selected_name, tmp) == 0);
5528               g_free (tmp);
5529             }
5530           else
5531             clear_entry = (compare_utf8_filenames (priv->browse_files_last_selected_name, entry_text) == 0);
5532         }
5533       else
5534         clear_entry = FALSE;
5535 
5536       if (clear_entry)
5537         {
5538           g_signal_handlers_block_by_func (priv->location_entry, G_CALLBACK (location_entry_changed_cb), impl);
5539           gtk_entry_set_text (GTK_ENTRY (priv->location_entry), "");
5540           g_signal_handlers_unblock_by_func (priv->location_entry, G_CALLBACK (location_entry_changed_cb), impl);
5541         }
5542     }
5543 }
5544 
5545 static gboolean
gtk_file_chooser_widget_set_current_folder(GtkFileChooser * chooser,GFile * file,GError ** error)5546 gtk_file_chooser_widget_set_current_folder (GtkFileChooser  *chooser,
5547                                              GFile          *file,
5548                                              GError        **error)
5549 {
5550   return gtk_file_chooser_widget_update_current_folder (chooser, file, FALSE, FALSE, error);
5551 }
5552 
5553 
5554 struct UpdateCurrentFolderData
5555 {
5556   GtkFileChooserWidget *impl;
5557   GFile *file;
5558   gboolean keep_trail;
5559   gboolean clear_entry;
5560   GFile *original_file;
5561   GError *original_error;
5562 };
5563 
5564 static void
update_current_folder_mount_enclosing_volume_cb(GCancellable * cancellable,GtkFileSystemVolume * volume,const GError * error,gpointer user_data)5565 update_current_folder_mount_enclosing_volume_cb (GCancellable        *cancellable,
5566                                                  GtkFileSystemVolume *volume,
5567                                                  const GError        *error,
5568                                                  gpointer             user_data)
5569 {
5570   struct UpdateCurrentFolderData *data = user_data;
5571   GtkFileChooserWidget *impl = data->impl;
5572   GtkFileChooserWidgetPrivate *priv = impl->priv;
5573   gboolean cancelled = g_cancellable_is_cancelled (cancellable);
5574 
5575   if (cancellable != priv->update_current_folder_cancellable)
5576     goto out;
5577 
5578   priv->update_current_folder_cancellable = NULL;
5579   set_busy_cursor (impl, FALSE);
5580 
5581   if (cancelled)
5582     goto out;
5583 
5584   if (error)
5585     {
5586       error_changing_folder_dialog (data->impl, data->file, g_error_copy (error));
5587       priv->reload_state = RELOAD_EMPTY;
5588       goto out;
5589     }
5590 
5591   change_folder_and_display_error (impl, data->file, data->clear_entry);
5592 
5593 out:
5594   g_object_unref (data->impl);
5595   g_object_unref (data->file);
5596   g_free (data);
5597 
5598   g_object_unref (cancellable);
5599 }
5600 
5601 static void
update_current_folder_get_info_cb(GCancellable * cancellable,GFileInfo * info,const GError * error,gpointer user_data)5602 update_current_folder_get_info_cb (GCancellable *cancellable,
5603                                    GFileInfo    *info,
5604                                    const GError *error,
5605                                    gpointer      user_data)
5606 {
5607   gboolean cancelled = g_cancellable_is_cancelled (cancellable);
5608   struct UpdateCurrentFolderData *data = user_data;
5609   GtkFileChooserWidget *impl = data->impl;
5610   GtkFileChooserWidgetPrivate *priv = impl->priv;
5611 
5612   if (cancellable != priv->update_current_folder_cancellable)
5613     goto out;
5614 
5615   priv->update_current_folder_cancellable = NULL;
5616   priv->reload_state = RELOAD_EMPTY;
5617 
5618   set_busy_cursor (impl, FALSE);
5619 
5620   if (cancelled)
5621     goto out;
5622 
5623   if (error)
5624     {
5625       GFile *parent_file;
5626 
5627       if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_MOUNTED))
5628         {
5629           GMountOperation *mount_operation;
5630           GtkWidget *toplevel;
5631 
5632           g_object_unref (cancellable);
5633           toplevel = gtk_widget_get_toplevel (GTK_WIDGET (impl));
5634 
5635           mount_operation = gtk_mount_operation_new (GTK_WINDOW (toplevel));
5636 
5637           set_busy_cursor (impl, TRUE);
5638 
5639           priv->update_current_folder_cancellable =
5640             _gtk_file_system_mount_enclosing_volume (priv->file_system, data->file,
5641                                                      mount_operation,
5642                                                      update_current_folder_mount_enclosing_volume_cb,
5643                                                      data);
5644 
5645           return;
5646         }
5647 
5648       if (!data->original_file)
5649         {
5650           data->original_file = g_object_ref (data->file);
5651           data->original_error = g_error_copy (error);
5652         }
5653 
5654       parent_file = g_file_get_parent (data->file);
5655 
5656       /* get parent path and try to change the folder to that */
5657       if (parent_file)
5658         {
5659           g_object_unref (data->file);
5660           data->file = parent_file;
5661 
5662           g_object_unref (cancellable);
5663 
5664           /* restart the update current folder operation */
5665           priv->reload_state = RELOAD_HAS_FOLDER;
5666 
5667           priv->update_current_folder_cancellable =
5668             _gtk_file_system_get_info (priv->file_system, data->file,
5669                                        "standard::type",
5670                                        update_current_folder_get_info_cb,
5671                                        data);
5672 
5673           set_busy_cursor (impl, TRUE);
5674 
5675           return;
5676         }
5677       else
5678         {
5679           /* Error and bail out, ignoring "not found" errors since they're useless:
5680            * they only happen when a program defaults to a folder that has been (re)moved.
5681            */
5682           if (!g_error_matches (data->original_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
5683             error_changing_folder_dialog (impl, data->original_file, data->original_error);
5684           else
5685             g_error_free (data->original_error);
5686 
5687           g_object_unref (data->original_file);
5688 
5689           goto out;
5690         }
5691     }
5692 
5693   if (data->original_file)
5694     {
5695       /* Error and bail out, ignoring "not found" errors since they're useless:
5696        * they only happen when a program defaults to a folder that has been (re)moved.
5697        */
5698       if (!g_error_matches (data->original_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
5699         error_changing_folder_dialog (impl, data->original_file, data->original_error);
5700       else
5701         g_error_free (data->original_error);
5702 
5703       g_object_unref (data->original_file);
5704     }
5705 
5706   if (! _gtk_file_info_consider_as_directory (info))
5707     goto out;
5708 
5709   _gtk_path_bar_set_file (GTK_PATH_BAR (priv->browse_path_bar), data->file, data->keep_trail);
5710 
5711   if (priv->current_folder != data->file)
5712     {
5713       if (priv->current_folder)
5714         g_object_unref (priv->current_folder);
5715 
5716       priv->current_folder = g_object_ref (data->file);
5717     }
5718 
5719   priv->reload_state = RELOAD_HAS_FOLDER;
5720 
5721   /* Set the folder on the save entry */
5722 
5723   if (priv->location_entry)
5724     {
5725       _gtk_file_chooser_entry_set_base_folder (GTK_FILE_CHOOSER_ENTRY (priv->location_entry),
5726                                                priv->current_folder);
5727 
5728       if (data->clear_entry)
5729         gtk_entry_set_text (GTK_ENTRY (priv->location_entry), "");
5730     }
5731 
5732   /* Create a new list model.  This is slightly evil; we store the result value
5733    * but perform more actions rather than returning immediately even if it
5734    * generates an error.
5735    */
5736   set_list_model (impl, NULL);
5737 
5738   /* Refresh controls */
5739 
5740   gtk_places_sidebar_set_location (GTK_PLACES_SIDEBAR (priv->places_sidebar), priv->current_folder);
5741 
5742   g_object_notify (G_OBJECT (impl), "subtitle");
5743 
5744   g_signal_emit_by_name (impl, "current-folder-changed", 0);
5745 
5746   check_preview_change (impl);
5747 
5748   g_signal_emit_by_name (impl, "selection-changed", 0);
5749 
5750 out:
5751   g_object_unref (data->impl);
5752   g_object_unref (data->file);
5753   g_free (data);
5754 
5755   g_object_unref (cancellable);
5756 }
5757 
5758 static gboolean
gtk_file_chooser_widget_update_current_folder(GtkFileChooser * chooser,GFile * file,gboolean keep_trail,gboolean clear_entry,GError ** error)5759 gtk_file_chooser_widget_update_current_folder (GtkFileChooser  *chooser,
5760                                                GFile          *file,
5761                                                gboolean        keep_trail,
5762                                                gboolean        clear_entry,
5763                                                GError        **error)
5764 {
5765   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser);
5766   GtkFileChooserWidgetPrivate *priv = impl->priv;
5767   struct UpdateCurrentFolderData *data;
5768 
5769   profile_start ("start", NULL);
5770 
5771   g_object_ref (file);
5772 
5773   operation_mode_set (impl, OPERATION_MODE_BROWSE);
5774 
5775   if (priv->local_only && !_gtk_file_has_native_path (file))
5776     {
5777       g_set_error_literal (error,
5778                            GTK_FILE_CHOOSER_ERROR,
5779                            GTK_FILE_CHOOSER_ERROR_BAD_FILENAME,
5780                            _("Cannot change to folder because it is not local"));
5781 
5782       g_object_unref (file);
5783       profile_end ("end - not local", NULL);
5784       return FALSE;
5785     }
5786 
5787   if (priv->update_current_folder_cancellable)
5788     g_cancellable_cancel (priv->update_current_folder_cancellable);
5789 
5790   /* Test validity of path here.  */
5791   data = g_new0 (struct UpdateCurrentFolderData, 1);
5792   data->impl = g_object_ref (impl);
5793   data->file = g_object_ref (file);
5794   data->keep_trail = keep_trail;
5795   data->clear_entry = clear_entry;
5796 
5797   priv->reload_state = RELOAD_HAS_FOLDER;
5798 
5799   priv->update_current_folder_cancellable =
5800     _gtk_file_system_get_info (priv->file_system, file,
5801                                "standard::type",
5802                                update_current_folder_get_info_cb,
5803                                data);
5804 
5805   set_busy_cursor (impl, TRUE);
5806   g_object_unref (file);
5807 
5808   profile_end ("end", NULL);
5809   return TRUE;
5810 }
5811 
5812 static GFile *
gtk_file_chooser_widget_get_current_folder(GtkFileChooser * chooser)5813 gtk_file_chooser_widget_get_current_folder (GtkFileChooser *chooser)
5814 {
5815   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser);
5816   GtkFileChooserWidgetPrivate *priv = impl->priv;
5817 
5818   if (priv->operation_mode == OPERATION_MODE_RECENT)
5819     return NULL;
5820 
5821   if (priv->current_folder)
5822     return g_object_ref (priv->current_folder);
5823 
5824   return NULL;
5825 }
5826 
5827 static void
gtk_file_chooser_widget_set_current_name(GtkFileChooser * chooser,const gchar * name)5828 gtk_file_chooser_widget_set_current_name (GtkFileChooser *chooser,
5829                                           const gchar    *name)
5830 {
5831   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser);
5832   GtkFileChooserWidgetPrivate *priv = impl->priv;
5833 
5834   g_return_if_fail (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE ||
5835                     priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER);
5836 
5837   pending_select_files_free (impl);
5838   gtk_entry_set_text (GTK_ENTRY (priv->location_entry), name);
5839 }
5840 
5841 static gchar *
gtk_file_chooser_widget_get_current_name(GtkFileChooser * chooser)5842 gtk_file_chooser_widget_get_current_name (GtkFileChooser *chooser)
5843 {
5844   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser);
5845   GtkFileChooserWidgetPrivate *priv = impl->priv;
5846 
5847   g_return_val_if_fail (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE ||
5848                         priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER,
5849                         NULL);
5850 
5851   return g_strdup (gtk_entry_get_text (GTK_ENTRY (priv->location_entry)));
5852 }
5853 
5854 static gboolean
gtk_file_chooser_widget_select_file(GtkFileChooser * chooser,GFile * file,GError ** error)5855 gtk_file_chooser_widget_select_file (GtkFileChooser  *chooser,
5856                                      GFile           *file,
5857                                      GError         **error)
5858 {
5859   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser);
5860   GtkFileChooserWidgetPrivate *priv = impl->priv;
5861   GFile *parent_file;
5862   gboolean same_path;
5863   GtkFileSystemModel *fsmodel;
5864 
5865   parent_file = g_file_get_parent (file);
5866 
5867   if (!parent_file)
5868     return gtk_file_chooser_set_current_folder_file (chooser, file, error);
5869 
5870   fsmodel = GTK_FILE_SYSTEM_MODEL (gtk_tree_view_get_model (GTK_TREE_VIEW (priv->browse_files_tree_view)));
5871 
5872   if (priv->operation_mode == OPERATION_MODE_SEARCH ||
5873       priv->operation_mode == OPERATION_MODE_RECENT ||
5874       priv->load_state == LOAD_EMPTY ||
5875       priv->browse_files_model != fsmodel)
5876     {
5877       same_path = FALSE;
5878     }
5879   else
5880     {
5881       g_assert (priv->current_folder != NULL);
5882 
5883       same_path = g_file_equal (parent_file, priv->current_folder);
5884     }
5885 
5886   if (same_path && priv->load_state == LOAD_FINISHED)
5887     {
5888       gboolean result;
5889       GSList files;
5890 
5891       files.data = (gpointer) file;
5892       files.next = NULL;
5893 
5894       /* Prevent the file chooser from loading a different folder when it is mapped */
5895       priv->reload_state = RELOAD_HAS_FOLDER;
5896 
5897       result = show_and_select_files (impl, &files);
5898       g_object_unref (parent_file);
5899       return result;
5900     }
5901 
5902   pending_select_files_add (impl, file);
5903 
5904   if (!same_path)
5905     {
5906       gboolean result;
5907 
5908       result = gtk_file_chooser_set_current_folder_file (chooser, parent_file, error);
5909       g_object_unref (parent_file);
5910       return result;
5911     }
5912 
5913   g_object_unref (parent_file);
5914   return TRUE;
5915 }
5916 
5917 static void
gtk_file_chooser_widget_unselect_file(GtkFileChooser * chooser,GFile * file)5918 gtk_file_chooser_widget_unselect_file (GtkFileChooser *chooser,
5919                                        GFile          *file)
5920 {
5921   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser);
5922   GtkFileChooserWidgetPrivate *priv = impl->priv;
5923   GtkTreeView *tree_view;
5924   GtkTreeModel *model;
5925   GtkTreeIter iter;
5926 
5927   tree_view = GTK_TREE_VIEW (priv->browse_files_tree_view);
5928   model = gtk_tree_view_get_model (tree_view);
5929   if (!model)
5930     return;
5931 
5932   if (!_gtk_file_system_model_get_iter_for_file (GTK_FILE_SYSTEM_MODEL (model), &iter, file))
5933     return;
5934 
5935   gtk_tree_selection_unselect_iter (gtk_tree_view_get_selection (tree_view), &iter);
5936 }
5937 
5938 static gboolean
maybe_select(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)5939 maybe_select (GtkTreeModel *model,
5940               GtkTreePath  *path,
5941               GtkTreeIter  *iter,
5942               gpointer      data)
5943 {
5944   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (data);
5945   GtkFileChooserWidgetPrivate *priv = impl->priv;
5946   GtkTreeSelection *selection;
5947   gboolean is_sensitive;
5948   gboolean is_folder;
5949 
5950   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view));
5951 
5952   gtk_tree_model_get (model, iter,
5953                       MODEL_COL_IS_FOLDER, &is_folder,
5954                       MODEL_COL_IS_SENSITIVE, &is_sensitive,
5955                       -1);
5956 
5957   if (is_sensitive &&
5958       ((is_folder && priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) ||
5959        (!is_folder && priv->action == GTK_FILE_CHOOSER_ACTION_OPEN)))
5960     gtk_tree_selection_select_iter (selection, iter);
5961   else
5962     gtk_tree_selection_unselect_iter (selection, iter);
5963 
5964   return FALSE;
5965 }
5966 
5967 static void
gtk_file_chooser_widget_select_all(GtkFileChooser * chooser)5968 gtk_file_chooser_widget_select_all (GtkFileChooser *chooser)
5969 {
5970   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser);
5971   GtkFileChooserWidgetPrivate *priv = impl->priv;
5972 
5973   if (priv->operation_mode == OPERATION_MODE_SEARCH ||
5974       priv->operation_mode == OPERATION_MODE_RECENT)
5975     {
5976       GtkTreeSelection *selection;
5977 
5978       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view));
5979       gtk_tree_selection_select_all (selection);
5980       return;
5981     }
5982 
5983   if (priv->select_multiple)
5984     gtk_tree_model_foreach (GTK_TREE_MODEL (priv->browse_files_model),
5985                             maybe_select, impl);
5986 }
5987 
5988 static void
gtk_file_chooser_widget_unselect_all(GtkFileChooser * chooser)5989 gtk_file_chooser_widget_unselect_all (GtkFileChooser *chooser)
5990 {
5991   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser);
5992   GtkFileChooserWidgetPrivate *priv = impl->priv;
5993   GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view));
5994 
5995   gtk_tree_selection_unselect_all (selection);
5996   pending_select_files_free (impl);
5997 }
5998 
5999 /* Checks whether the filename entry for Save modes contains a well-formed filename.
6000  *
6001  * is_well_formed_ret - whether what the user typed passes gkt_file_system_make_path()
6002  *
6003  * is_empty_ret - whether the file entry is totally empty
6004  *
6005  * is_file_part_empty_ret - whether the file part is empty (will be if user types
6006  *                          "foobar/", and the path will be “$cwd/foobar”)
6007  */
6008 static void
check_save_entry(GtkFileChooserWidget * impl,GFile ** file_ret,gboolean * is_well_formed_ret,gboolean * is_empty_ret,gboolean * is_file_part_empty_ret,gboolean * is_folder)6009 check_save_entry (GtkFileChooserWidget  *impl,
6010                   GFile                **file_ret,
6011                   gboolean              *is_well_formed_ret,
6012                   gboolean              *is_empty_ret,
6013                   gboolean              *is_file_part_empty_ret,
6014                   gboolean              *is_folder)
6015 {
6016   GtkFileChooserWidgetPrivate *priv = impl->priv;
6017   GtkFileChooserEntry *chooser_entry;
6018   GFile *current_folder;
6019   const char *file_part;
6020   char *file_part_stripped;
6021   GFile *file;
6022   GError *error;
6023 
6024   g_assert (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE ||
6025             priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER ||
6026             ((priv->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
6027               priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) &&
6028              priv->location_mode == LOCATION_MODE_FILENAME_ENTRY));
6029 
6030   chooser_entry = GTK_FILE_CHOOSER_ENTRY (priv->location_entry);
6031 
6032   if (strlen (gtk_entry_get_text (GTK_ENTRY (chooser_entry))) == 0)
6033     {
6034       *file_ret = NULL;
6035       *is_well_formed_ret = TRUE;
6036       *is_empty_ret = TRUE;
6037       *is_file_part_empty_ret = TRUE;
6038       *is_folder = FALSE;
6039 
6040       return;
6041     }
6042 
6043   *is_empty_ret = FALSE;
6044 
6045   current_folder = _gtk_file_chooser_entry_get_current_folder (chooser_entry);
6046   if (!current_folder)
6047     {
6048       *file_ret = NULL;
6049       *is_well_formed_ret = FALSE;
6050       *is_file_part_empty_ret = FALSE;
6051       *is_folder = FALSE;
6052 
6053       return;
6054     }
6055 
6056   file_part = _gtk_file_chooser_entry_get_file_part (chooser_entry);
6057 
6058   /* Copy and strip leading and trailing whitespace */
6059   file_part_stripped = g_strstrip (g_strdup (file_part));
6060 
6061   if (!file_part_stripped || file_part_stripped[0] == '\0')
6062     {
6063       *file_ret = current_folder;
6064       *is_well_formed_ret = TRUE;
6065       *is_file_part_empty_ret = TRUE;
6066       *is_folder = TRUE;
6067 
6068       g_free (file_part_stripped);
6069       return;
6070     }
6071 
6072   *is_file_part_empty_ret = FALSE;
6073 
6074   error = NULL;
6075   file = g_file_get_child_for_display_name (current_folder, file_part_stripped, &error);
6076   g_object_unref (current_folder);
6077   g_free (file_part_stripped);
6078 
6079   if (!file)
6080     {
6081       error_building_filename_dialog (impl, error);
6082       *file_ret = NULL;
6083       *is_well_formed_ret = FALSE;
6084       *is_folder = FALSE;
6085 
6086       return;
6087     }
6088 
6089   *file_ret = file;
6090   *is_well_formed_ret = TRUE;
6091   *is_folder = _gtk_file_chooser_entry_get_is_folder (chooser_entry, file);
6092 }
6093 
6094 struct get_files_closure {
6095   GtkFileChooserWidget *impl;
6096   GSList *result;
6097   GFile *file_from_entry;
6098 };
6099 
6100 static void
get_files_foreach(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)6101 get_files_foreach (GtkTreeModel *model,
6102                    GtkTreePath  *path,
6103                    GtkTreeIter  *iter,
6104                    gpointer      data)
6105 {
6106   GtkFileSystemModel *fs_model = GTK_FILE_SYSTEM_MODEL (model);
6107   struct get_files_closure *info = data;
6108   GFile *file;
6109 
6110   file = _gtk_file_system_model_get_file (fs_model, iter);
6111 
6112   if (!info->file_from_entry || !g_file_equal (info->file_from_entry, file))
6113     info->result = g_slist_prepend (info->result, g_object_ref (file));
6114 }
6115 
6116 static GSList *
gtk_file_chooser_widget_get_files(GtkFileChooser * chooser)6117 gtk_file_chooser_widget_get_files (GtkFileChooser *chooser)
6118 {
6119   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser);
6120   GtkFileChooserWidgetPrivate *priv = impl->priv;
6121   struct get_files_closure info;
6122   GtkWindow *toplevel;
6123   GtkWidget *current_focus;
6124   gboolean file_list_seen;
6125 
6126   info.impl = impl;
6127   info.result = NULL;
6128   info.file_from_entry = NULL;
6129 
6130   if (priv->operation_mode == OPERATION_MODE_SEARCH)
6131     return get_selected_files (impl);
6132 
6133   if (priv->operation_mode == OPERATION_MODE_RECENT)
6134     {
6135       if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE)
6136         {
6137           file_list_seen = TRUE;
6138           goto file_entry;
6139         }
6140       else
6141         return get_selected_files (impl);
6142     }
6143 
6144   toplevel = get_toplevel (GTK_WIDGET (impl));
6145   if (toplevel)
6146     current_focus = gtk_window_get_focus (toplevel);
6147   else
6148     current_focus = NULL;
6149 
6150   file_list_seen = FALSE;
6151   if (current_focus == priv->browse_files_tree_view)
6152     {
6153       GtkTreeSelection *selection;
6154 
6155     file_list:
6156 
6157       file_list_seen = TRUE;
6158       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view));
6159       gtk_tree_selection_selected_foreach (selection, get_files_foreach, &info);
6160 
6161       /* If there is no selection in the file list, we probably have this situation:
6162        *
6163        * 1. The user typed a filename in the SAVE filename entry ("foo.txt").
6164        * 2. He then double-clicked on a folder ("bar") in the file list
6165        *
6166        * So we want the selection to be "bar/foo.txt".  Jump to the case for the
6167        * filename entry to see if that is the case.
6168        */
6169       if (info.result == NULL && priv->location_entry)
6170         goto file_entry;
6171     }
6172   else if (priv->location_entry && current_focus == priv->location_entry)
6173     {
6174       gboolean is_well_formed, is_empty, is_file_part_empty, is_folder;
6175 
6176     file_entry:
6177 
6178       check_save_entry (impl, &info.file_from_entry, &is_well_formed, &is_empty, &is_file_part_empty, &is_folder);
6179 
6180       if (is_empty)
6181         goto out;
6182 
6183       if (!is_well_formed)
6184         return NULL;
6185 
6186       if (info.file_from_entry)
6187         info.result = g_slist_prepend (info.result, info.file_from_entry);
6188       else if (!file_list_seen)
6189         goto file_list;
6190       else
6191         return NULL;
6192     }
6193   else if (priv->toplevel_last_focus_widget == priv->browse_files_tree_view)
6194     goto file_list;
6195   else if (priv->location_entry && priv->toplevel_last_focus_widget == priv->location_entry)
6196     goto file_entry;
6197   else
6198     {
6199       /* The focus is on a dialog's action area button or something else */
6200       if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE ||
6201           priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
6202         goto file_entry;
6203       else
6204         goto file_list;
6205     }
6206 
6207  out:
6208 
6209   /* If there's no folder selected, and we're in SELECT_FOLDER mode,
6210    * then we fall back to the current directory
6211    */
6212   if (priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER &&
6213       info.result == NULL)
6214     {
6215       GFile *current_folder;
6216 
6217       current_folder = gtk_file_chooser_get_current_folder_file (chooser);
6218 
6219       if (current_folder)
6220         info.result = g_slist_prepend (info.result, current_folder);
6221     }
6222 
6223   return g_slist_reverse (info.result);
6224 }
6225 
6226 GFile *
gtk_file_chooser_widget_get_preview_file(GtkFileChooser * chooser)6227 gtk_file_chooser_widget_get_preview_file (GtkFileChooser *chooser)
6228 {
6229   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser);
6230   GtkFileChooserWidgetPrivate *priv = impl->priv;
6231 
6232   if (priv->preview_file)
6233     return g_object_ref (priv->preview_file);
6234   else
6235     return NULL;
6236 }
6237 
6238 static GtkFileSystem *
gtk_file_chooser_widget_get_file_system(GtkFileChooser * chooser)6239 gtk_file_chooser_widget_get_file_system (GtkFileChooser *chooser)
6240 {
6241   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser);
6242   GtkFileChooserWidgetPrivate *priv = impl->priv;
6243 
6244   return priv->file_system;
6245 }
6246 
6247 /* Shows or hides the filter widgets */
6248 static void
show_filters(GtkFileChooserWidget * impl,gboolean show)6249 show_filters (GtkFileChooserWidget *impl,
6250               gboolean              show)
6251 {
6252   GtkFileChooserWidgetPrivate *priv = impl->priv;
6253 
6254   gtk_widget_set_visible (priv->filter_combo_hbox, show);
6255   update_extra_and_filters (impl);
6256 }
6257 
6258 static void
gtk_file_chooser_widget_add_filter(GtkFileChooser * chooser,GtkFileFilter * filter)6259 gtk_file_chooser_widget_add_filter (GtkFileChooser *chooser,
6260                                     GtkFileFilter  *filter)
6261 {
6262   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser);
6263   GtkFileChooserWidgetPrivate *priv = impl->priv;
6264   const gchar *name;
6265 
6266   if (g_slist_find (priv->filters, filter))
6267     {
6268       g_warning ("gtk_file_chooser_add_filter() called on filter already in list");
6269       return;
6270     }
6271 
6272   g_object_ref_sink (filter);
6273   priv->filters = g_slist_append (priv->filters, filter);
6274 
6275   name = gtk_file_filter_get_name (filter);
6276   if (!name)
6277     name = "Untitled filter";   /* Place-holder, doesn't need to be marked for translation */
6278 
6279   gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (priv->filter_combo), name);
6280 
6281   if (!g_slist_find (priv->filters, priv->current_filter))
6282     set_current_filter (impl, filter);
6283 
6284   show_filters (impl, TRUE);
6285 }
6286 
6287 static void
gtk_file_chooser_widget_remove_filter(GtkFileChooser * chooser,GtkFileFilter * filter)6288 gtk_file_chooser_widget_remove_filter (GtkFileChooser *chooser,
6289                                        GtkFileFilter  *filter)
6290 {
6291   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser);
6292   GtkFileChooserWidgetPrivate *priv = impl->priv;
6293   GtkTreeModel *model;
6294   GtkTreeIter iter;
6295   gint filter_index;
6296 
6297   filter_index = g_slist_index (priv->filters, filter);
6298 
6299   if (filter_index < 0)
6300     {
6301       g_warning ("gtk_file_chooser_remove_filter() called on filter not in list");
6302       return;
6303     }
6304 
6305   priv->filters = g_slist_remove (priv->filters, filter);
6306 
6307   if (filter == priv->current_filter)
6308     {
6309       if (priv->filters)
6310         set_current_filter (impl, priv->filters->data);
6311       else
6312         set_current_filter (impl, NULL);
6313     }
6314 
6315   /* Remove row from the combo box */
6316   model = gtk_combo_box_get_model (GTK_COMBO_BOX (priv->filter_combo));
6317   if (!gtk_tree_model_iter_nth_child  (model, &iter, NULL, filter_index))
6318     g_assert_not_reached ();
6319 
6320   gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
6321 
6322   g_object_unref (filter);
6323 
6324   if (!priv->filters)
6325     show_filters (impl, FALSE);
6326 }
6327 
6328 static GSList *
gtk_file_chooser_widget_list_filters(GtkFileChooser * chooser)6329 gtk_file_chooser_widget_list_filters (GtkFileChooser *chooser)
6330 {
6331   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser);
6332   GtkFileChooserWidgetPrivate *priv = impl->priv;
6333 
6334   return g_slist_copy (priv->filters);
6335 }
6336 
6337 static gboolean
gtk_file_chooser_widget_add_shortcut_folder(GtkFileChooser * chooser,GFile * file,GError ** error)6338 gtk_file_chooser_widget_add_shortcut_folder (GtkFileChooser  *chooser,
6339                                              GFile           *file,
6340                                              GError         **error)
6341 {
6342   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser);
6343   GtkFileChooserWidgetPrivate *priv = impl->priv;
6344 
6345   gtk_places_sidebar_add_shortcut (GTK_PLACES_SIDEBAR (priv->places_sidebar), file);
6346   return TRUE;
6347 }
6348 
6349 static gboolean
gtk_file_chooser_widget_remove_shortcut_folder(GtkFileChooser * chooser,GFile * file,GError ** error)6350 gtk_file_chooser_widget_remove_shortcut_folder (GtkFileChooser  *chooser,
6351                                                 GFile           *file,
6352                                                 GError         **error)
6353 {
6354   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser);
6355   GtkFileChooserWidgetPrivate *priv = impl->priv;
6356 
6357   gtk_places_sidebar_remove_shortcut (GTK_PLACES_SIDEBAR (priv->places_sidebar), file);
6358   return TRUE;
6359 }
6360 
6361 static GSList *
gtk_file_chooser_widget_list_shortcut_folders(GtkFileChooser * chooser)6362 gtk_file_chooser_widget_list_shortcut_folders (GtkFileChooser *chooser)
6363 {
6364   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser);
6365   GtkFileChooserWidgetPrivate *priv = impl->priv;
6366 
6367   return gtk_places_sidebar_list_shortcuts (GTK_PLACES_SIDEBAR (priv->places_sidebar));
6368 }
6369 
6370 /* Guesses a size based upon font sizes */
6371 static void
find_good_size_from_style(GtkWidget * widget,gint * width,gint * height)6372 find_good_size_from_style (GtkWidget *widget,
6373                            gint      *width,
6374                            gint      *height)
6375 {
6376   GtkStyleContext *context;
6377   double font_size;
6378   GdkScreen *screen;
6379   double resolution;
6380 
6381   context = gtk_widget_get_style_context (widget);
6382 
6383   screen = gtk_widget_get_screen (widget);
6384   if (screen)
6385     {
6386       resolution = gdk_screen_get_resolution (screen);
6387       if (resolution < 0.0) /* will be -1 if the resolution is not defined in the GdkScreen */
6388         resolution = 96.0;
6389     }
6390   else
6391     resolution = 96.0; /* wheeee */
6392 
6393   gtk_style_context_get (context,
6394                          gtk_style_context_get_state (context),
6395                          "font-size", &font_size,
6396                          NULL);
6397   font_size = font_size * resolution / 72.0 + 0.5;
6398 
6399   *width = font_size * NUM_CHARS;
6400   *height = font_size * NUM_LINES;
6401 }
6402 
6403 static void
gtk_file_chooser_widget_get_default_size(GtkFileChooserEmbed * chooser_embed,gint * default_width,gint * default_height)6404 gtk_file_chooser_widget_get_default_size (GtkFileChooserEmbed *chooser_embed,
6405                                           gint                *default_width,
6406                                           gint                *default_height)
6407 {
6408   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser_embed);
6409   GtkFileChooserWidgetPrivate *priv = impl->priv;
6410   GtkRequisition req;
6411   int x, y, width, height;
6412   GSettings *settings;
6413 
6414   settings = _gtk_file_chooser_get_settings_for_widget (GTK_WIDGET (impl));
6415 
6416   g_settings_get (settings, SETTINGS_KEY_WINDOW_POSITION, "(ii)", &x, &y);
6417   g_settings_get (settings, SETTINGS_KEY_WINDOW_SIZE, "(ii)", &width, &height);
6418 
6419   if (x >= 0 && y >= 0 && width > 0 && height > 0)
6420     {
6421       *default_width = width;
6422       *default_height = height;
6423       return;
6424     }
6425 
6426   find_good_size_from_style (GTK_WIDGET (chooser_embed), default_width, default_height);
6427 
6428   if (priv->preview_widget_active &&
6429       priv->preview_widget &&
6430       gtk_widget_get_visible (priv->preview_widget))
6431     {
6432       gtk_widget_get_preferred_size (priv->preview_box,
6433                                      &req, NULL);
6434       *default_width += PREVIEW_HBOX_SPACING + req.width;
6435     }
6436 
6437   if (priv->extra_widget &&
6438       gtk_widget_get_visible (priv->extra_widget))
6439     {
6440       gtk_widget_get_preferred_size (priv->extra_align,
6441                                      &req, NULL);
6442       *default_height += gtk_box_get_spacing (GTK_BOX (chooser_embed)) + req.height;
6443     }
6444 }
6445 
6446 struct switch_folder_closure {
6447   GtkFileChooserWidget *impl;
6448   GFile *file;
6449   int num_selected;
6450 };
6451 
6452 /* Used from gtk_tree_selection_selected_foreach() in switch_to_selected_folder() */
6453 static void
switch_folder_foreach_cb(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)6454 switch_folder_foreach_cb (GtkTreeModel *model,
6455                           GtkTreePath  *path,
6456                           GtkTreeIter  *iter,
6457                           gpointer      data)
6458 {
6459   struct switch_folder_closure *closure;
6460 
6461   closure = data;
6462 
6463   closure->file = _gtk_file_system_model_get_file (GTK_FILE_SYSTEM_MODEL (model), iter);
6464   closure->num_selected++;
6465 }
6466 
6467 /* Changes to the selected folder in the list view */
6468 static void
switch_to_selected_folder(GtkFileChooserWidget * impl)6469 switch_to_selected_folder (GtkFileChooserWidget *impl)
6470 {
6471   GtkFileChooserWidgetPrivate *priv = impl->priv;
6472   GtkTreeSelection *selection;
6473   struct switch_folder_closure closure;
6474 
6475   /* We do this with foreach() rather than get_selected() as we may be in
6476    * multiple selection mode
6477    */
6478 
6479   closure.impl = impl;
6480   closure.file = NULL;
6481   closure.num_selected = 0;
6482 
6483   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view));
6484   gtk_tree_selection_selected_foreach (selection, switch_folder_foreach_cb, &closure);
6485 
6486   g_assert (closure.file && closure.num_selected == 1);
6487 
6488   change_folder_and_display_error (impl, closure.file, FALSE);
6489 }
6490 
6491 /* Gets the GFileInfo for the selected row in the file list; assumes single
6492  * selection mode.
6493  */
6494 static GFileInfo *
get_selected_file_info_from_file_list(GtkFileChooserWidget * impl,gboolean * had_selection)6495 get_selected_file_info_from_file_list (GtkFileChooserWidget *impl,
6496                                        gboolean              *had_selection)
6497 {
6498   GtkFileChooserWidgetPrivate *priv = impl->priv;
6499   GtkTreeSelection *selection;
6500   GtkTreeIter iter;
6501   GFileInfo *info;
6502   GtkTreeModel *model;
6503 
6504   g_assert (!priv->select_multiple);
6505   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view));
6506   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
6507     {
6508       *had_selection = FALSE;
6509       return NULL;
6510     }
6511 
6512   *had_selection = TRUE;
6513 
6514   info = _gtk_file_system_model_get_info (GTK_FILE_SYSTEM_MODEL (model), &iter);
6515   return info;
6516 }
6517 
6518 /* Gets the display name of the selected file in the file list; assumes single
6519  * selection mode and that something is selected.
6520  */
6521 static const gchar *
get_display_name_from_file_list(GtkFileChooserWidget * impl)6522 get_display_name_from_file_list (GtkFileChooserWidget *impl)
6523 {
6524   GFileInfo *info;
6525   gboolean had_selection;
6526 
6527   info = get_selected_file_info_from_file_list (impl, &had_selection);
6528   g_assert (had_selection);
6529   g_assert (info != NULL);
6530 
6531   return g_file_info_get_display_name (info);
6532 }
6533 
6534 static void
add_custom_button_to_dialog(GtkDialog * dialog,const gchar * mnemonic_label,gint response_id)6535 add_custom_button_to_dialog (GtkDialog   *dialog,
6536                              const gchar *mnemonic_label,
6537                              gint         response_id)
6538 {
6539   GtkWidget *button;
6540 
6541   button = gtk_button_new_with_mnemonic (mnemonic_label);
6542   gtk_widget_set_can_default (button, TRUE);
6543   gtk_widget_show (button);
6544 
6545   gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, response_id);
6546 }
6547 
6548 /* Presents an overwrite confirmation dialog; returns whether we should accept
6549  * the filename.
6550  */
6551 static gboolean
confirm_dialog_should_accept_filename(GtkFileChooserWidget * impl,const gchar * file_part,const gchar * folder_display_name)6552 confirm_dialog_should_accept_filename (GtkFileChooserWidget *impl,
6553                                        const gchar           *file_part,
6554                                        const gchar           *folder_display_name)
6555 {
6556   GtkWindow *toplevel;
6557   GtkWidget *dialog;
6558   int response;
6559 
6560   toplevel = get_toplevel (GTK_WIDGET (impl));
6561 
6562   dialog = gtk_message_dialog_new (toplevel,
6563                                    GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
6564                                    GTK_MESSAGE_QUESTION,
6565                                    GTK_BUTTONS_NONE,
6566                                    _("A file named “%s” already exists.  Do you want to replace it?"),
6567                                    file_part);
6568   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
6569                                             _("The file already exists in “%s”.  Replacing it will "
6570                                               "overwrite its contents."),
6571                                             folder_display_name);
6572 
6573   gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Cancel"), GTK_RESPONSE_CANCEL);
6574   add_custom_button_to_dialog (GTK_DIALOG (dialog), _("_Replace"), GTK_RESPONSE_ACCEPT);
6575 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
6576   gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
6577                                            GTK_RESPONSE_ACCEPT,
6578                                            GTK_RESPONSE_CANCEL,
6579                                            -1);
6580 G_GNUC_END_IGNORE_DEPRECATIONS
6581   gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
6582 
6583   if (gtk_window_has_group (toplevel))
6584     gtk_window_group_add_window (gtk_window_get_group (toplevel), GTK_WINDOW (dialog));
6585 
6586   response = gtk_dialog_run (GTK_DIALOG (dialog));
6587 
6588   if (response == GTK_RESPONSE_ACCEPT)
6589     /* Dialog is now going to be closed, so prevent any button/key presses to
6590      * file list (will be restablished on next map()). Fixes data loss bug #2288 */
6591     impl->priv->browse_files_interaction_frozen = TRUE;
6592 
6593   gtk_widget_destroy (dialog);
6594 
6595   return (response == GTK_RESPONSE_ACCEPT);
6596 }
6597 
6598 struct GetDisplayNameData
6599 {
6600   GtkFileChooserWidget *impl;
6601   gchar *file_part;
6602 };
6603 
6604 /* Every time we request a response explicitly, we need to save the selection to
6605  * the recently-used list, as requesting a response means, “the dialog is confirmed”.
6606  */
6607 static void
request_response_and_add_to_recent_list(GtkFileChooserWidget * impl)6608 request_response_and_add_to_recent_list (GtkFileChooserWidget *impl)
6609 {
6610   g_signal_emit_by_name (impl, "response-requested");
6611   add_selection_to_recent_list (impl);
6612 }
6613 
6614 static void
confirmation_confirm_get_info_cb(GCancellable * cancellable,GFileInfo * info,const GError * error,gpointer user_data)6615 confirmation_confirm_get_info_cb (GCancellable *cancellable,
6616                                   GFileInfo    *info,
6617                                   const GError *error,
6618                                   gpointer      user_data)
6619 {
6620   gboolean cancelled = g_cancellable_is_cancelled (cancellable);
6621   gboolean should_respond = FALSE;
6622   struct GetDisplayNameData *data = user_data;
6623   GtkFileChooserWidgetPrivate *priv = data->impl->priv;
6624 
6625   if (cancellable != priv->should_respond_get_info_cancellable)
6626     goto out;
6627 
6628   priv->should_respond_get_info_cancellable = NULL;
6629 
6630   if (cancelled)
6631     goto out;
6632 
6633   if (error)
6634     /* Huh?  Did the folder disappear?  Let the caller deal with it */
6635     should_respond = TRUE;
6636   else
6637     should_respond = confirm_dialog_should_accept_filename (data->impl, data->file_part, g_file_info_get_display_name (info));
6638 
6639   set_busy_cursor (data->impl, FALSE);
6640   if (should_respond)
6641     request_response_and_add_to_recent_list (data->impl);
6642 
6643 out:
6644   g_object_unref (data->impl);
6645   g_free (data->file_part);
6646   g_free (data);
6647 
6648   g_object_unref (cancellable);
6649 }
6650 
6651 /* Does overwrite confirmation if appropriate, and returns whether the dialog
6652  * should respond.  Can get the file part from the file list or the save entry.
6653  */
6654 static gboolean
should_respond_after_confirm_overwrite(GtkFileChooserWidget * impl,const gchar * file_part,GFile * parent_file)6655 should_respond_after_confirm_overwrite (GtkFileChooserWidget *impl,
6656                                         const gchar          *file_part,
6657                                         GFile                *parent_file)
6658 {
6659   GtkFileChooserWidgetPrivate *priv = impl->priv;
6660   GtkFileChooserConfirmation conf;
6661 
6662   if (!priv->do_overwrite_confirmation)
6663     return TRUE;
6664 
6665   conf = GTK_FILE_CHOOSER_CONFIRMATION_CONFIRM;
6666 
6667   g_signal_emit_by_name (impl, "confirm-overwrite", &conf);
6668 
6669   switch (conf)
6670     {
6671     case GTK_FILE_CHOOSER_CONFIRMATION_CONFIRM:
6672       {
6673         struct GetDisplayNameData *data;
6674 
6675         g_assert (file_part != NULL);
6676 
6677         data = g_new0 (struct GetDisplayNameData, 1);
6678         data->impl = g_object_ref (impl);
6679         data->file_part = g_strdup (file_part);
6680 
6681         if (priv->should_respond_get_info_cancellable)
6682           g_cancellable_cancel (priv->should_respond_get_info_cancellable);
6683 
6684         priv->should_respond_get_info_cancellable =
6685           _gtk_file_system_get_info (priv->file_system, parent_file,
6686                                      "standard::display-name",
6687                                      confirmation_confirm_get_info_cb,
6688                                      data);
6689         set_busy_cursor (data->impl, TRUE);
6690         return FALSE;
6691       }
6692 
6693     case GTK_FILE_CHOOSER_CONFIRMATION_ACCEPT_FILENAME:
6694       return TRUE;
6695 
6696     case GTK_FILE_CHOOSER_CONFIRMATION_SELECT_AGAIN:
6697       return FALSE;
6698 
6699     default:
6700       g_assert_not_reached ();
6701       return FALSE;
6702     }
6703 }
6704 
6705 static void
name_entry_get_parent_info_cb(GCancellable * cancellable,GFileInfo * info,const GError * error,gpointer user_data)6706 name_entry_get_parent_info_cb (GCancellable *cancellable,
6707                                GFileInfo    *info,
6708                                const GError *error,
6709                                gpointer      user_data)
6710 {
6711   gboolean parent_is_folder = FALSE;
6712   gboolean parent_is_accessible = FALSE;
6713   gboolean cancelled = g_cancellable_is_cancelled (cancellable);
6714   struct FileExistsData *data = user_data;
6715   GtkFileChooserWidget *impl = data->impl;
6716   GtkFileChooserWidgetPrivate *priv = impl->priv;
6717 
6718   if (cancellable != priv->should_respond_get_info_cancellable)
6719     goto out;
6720 
6721   priv->should_respond_get_info_cancellable = NULL;
6722 
6723   set_busy_cursor (impl, FALSE);
6724 
6725   if (cancelled)
6726     goto out;
6727 
6728   if (info)
6729     {
6730       parent_is_folder = _gtk_file_info_consider_as_directory (info);
6731 
6732       /* Some gvfs backends do not set executable attribute, let's assume that
6733        * the folder is accessible even if the attribute is not set.
6734        */
6735       parent_is_accessible = !g_file_info_has_attribute (info, "access::can-execute") ||
6736                              g_file_info_get_attribute_boolean (info, "access::can-execute");
6737     }
6738 
6739   if (parent_is_folder && parent_is_accessible)
6740     {
6741       if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN)
6742         {
6743           request_response_and_add_to_recent_list (impl); /* even if the file doesn't exist, apps can make good use of that (e.g. Emacs) */
6744         }
6745       else if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE)
6746         {
6747           if (data->file_exists_and_is_not_folder)
6748             {
6749               gboolean retval;
6750               char *file_part;
6751 
6752               /* Dup the string because the string may be modified
6753                * depending on what clients do in the confirm-overwrite
6754                * signal and this corrupts the pointer
6755                */
6756               file_part = g_strdup (_gtk_file_chooser_entry_get_file_part (GTK_FILE_CHOOSER_ENTRY (priv->location_entry)));
6757               retval = should_respond_after_confirm_overwrite (impl, file_part, data->parent_file);
6758               g_free (file_part);
6759 
6760               if (retval)
6761                 request_response_and_add_to_recent_list (impl);
6762             }
6763           else
6764             request_response_and_add_to_recent_list (impl);
6765         }
6766       else if (priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
6767                || priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
6768         {
6769           GError *mkdir_error = NULL;
6770 
6771           /* In both cases (SELECT_FOLDER and CREATE_FOLDER), if you type
6772            * "/blah/nonexistent" you *will* want a folder created.
6773            */
6774 
6775           set_busy_cursor (impl, TRUE);
6776           g_file_make_directory (data->file, NULL, &mkdir_error);
6777           set_busy_cursor (impl, FALSE);
6778 
6779           if (!mkdir_error)
6780             request_response_and_add_to_recent_list (impl);
6781           else
6782             error_creating_folder_dialog (impl, data->file, mkdir_error);
6783         }
6784       else
6785         g_assert_not_reached ();
6786     }
6787   else if (parent_is_folder)
6788     {
6789       GError *error;
6790 
6791       error = NULL;
6792       g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
6793                            _("You do not have access to the specified folder."));
6794 
6795       error_changing_folder_dialog (impl, data->parent_file, error);
6796     }
6797   else if (info)
6798     {
6799       /* The parent exists, but it's not a folder!
6800        * Someone probably typed existing_file.txt/subfile.txt
6801        */
6802       error_with_file_under_nonfolder (impl, data->parent_file);
6803     }
6804   else
6805     {
6806       GError *error_copy;
6807 
6808       /* The parent folder is not readable for some reason */
6809 
6810       error_copy = g_error_copy (error);
6811       error_changing_folder_dialog (impl, data->parent_file, error_copy);
6812     }
6813 
6814 out:
6815   g_object_unref (data->impl);
6816   g_object_unref (data->file);
6817   g_object_unref (data->parent_file);
6818   g_free (data);
6819 
6820   g_object_unref (cancellable);
6821 }
6822 
6823 static void
file_exists_get_info_cb(GCancellable * cancellable,GFileInfo * info,const GError * error,gpointer user_data)6824 file_exists_get_info_cb (GCancellable *cancellable,
6825                          GFileInfo    *info,
6826                          const GError *error,
6827                          gpointer      user_data)
6828 {
6829   gboolean data_ownership_taken = FALSE;
6830   gboolean cancelled = g_cancellable_is_cancelled (cancellable);
6831   gboolean file_exists;
6832   gboolean is_folder;
6833   gboolean needs_parent_check = FALSE;
6834   struct FileExistsData *data = user_data;
6835   GtkFileChooserWidget *impl = data->impl;
6836   GtkFileChooserWidgetPrivate *priv = impl->priv;
6837 
6838   if (cancellable != priv->file_exists_get_info_cancellable)
6839     goto out;
6840 
6841   priv->file_exists_get_info_cancellable = NULL;
6842 
6843   set_busy_cursor (impl, FALSE);
6844 
6845   if (cancelled)
6846     goto out;
6847 
6848   file_exists = (info != NULL);
6849   is_folder = (file_exists && _gtk_file_info_consider_as_directory (info));
6850 
6851   if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN)
6852     {
6853       if (is_folder)
6854         change_folder_and_display_error (impl, data->file, TRUE);
6855       else
6856         {
6857           if (file_exists)
6858             request_response_and_add_to_recent_list (impl); /* user typed an existing filename; we are done */
6859           else
6860             needs_parent_check = TRUE; /* file doesn't exist; see if its parent exists */
6861         }
6862     }
6863   else if (priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
6864     {
6865       if (file_exists && !is_folder)
6866         {
6867           /* Oops, the user typed the name of an existing path which is not
6868            * a folder
6869            */
6870           error_creating_folder_over_existing_file_dialog (impl, data->file);
6871         }
6872       else
6873         {
6874           needs_parent_check = TRUE;
6875         }
6876     }
6877   else if (priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
6878     {
6879       if (!file_exists)
6880         {
6881           needs_parent_check = TRUE;
6882         }
6883       else
6884         {
6885           if (is_folder)
6886             {
6887               /* User typed a folder; we are done */
6888               request_response_and_add_to_recent_list (impl);
6889             }
6890           else
6891             error_selecting_folder_over_existing_file_dialog (impl);
6892         }
6893     }
6894   else if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE)
6895     {
6896       if (is_folder)
6897         change_folder_and_display_error (impl, data->file, TRUE);
6898       else
6899         if (!file_exists && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_FILENAME_TOO_LONG))
6900           error_filename_to_long_dialog (data->impl);
6901         else
6902           needs_parent_check = TRUE;
6903     }
6904   else
6905     {
6906       g_assert_not_reached();
6907     }
6908 
6909   if (needs_parent_check)
6910     {
6911       /* check that everything up to the last path component exists (i.e. the parent) */
6912 
6913       data->file_exists_and_is_not_folder = file_exists && !is_folder;
6914       data_ownership_taken = TRUE;
6915 
6916       if (priv->should_respond_get_info_cancellable)
6917         g_cancellable_cancel (priv->should_respond_get_info_cancellable);
6918 
6919       priv->should_respond_get_info_cancellable =
6920         _gtk_file_system_get_info (priv->file_system,
6921                                    data->parent_file,
6922                                    "standard::type,access::can-execute",
6923                                    name_entry_get_parent_info_cb,
6924                                    data);
6925       set_busy_cursor (impl, TRUE);
6926     }
6927 
6928 out:
6929   if (!data_ownership_taken)
6930     {
6931       g_object_unref (impl);
6932       g_object_unref (data->file);
6933       g_object_unref (data->parent_file);
6934       g_free (data);
6935     }
6936 
6937   g_object_unref (cancellable);
6938 }
6939 
6940 static void
paste_text_received(GtkClipboard * clipboard,const gchar * text,GtkFileChooserWidget * impl)6941 paste_text_received (GtkClipboard         *clipboard,
6942                      const gchar          *text,
6943                      GtkFileChooserWidget *impl)
6944 {
6945   GFile *file;
6946 
6947   if (!text)
6948     return;
6949 
6950   file = g_file_new_for_uri (text);
6951 
6952   if (!gtk_file_chooser_widget_select_file (GTK_FILE_CHOOSER (impl), file, NULL))
6953     location_popup_handler (impl, text);
6954 
6955   g_object_unref (file);
6956 }
6957 
6958 /* Handler for the "location-popup-on-paste" keybinding signal */
6959 static void
location_popup_on_paste_handler(GtkFileChooserWidget * impl)6960 location_popup_on_paste_handler (GtkFileChooserWidget *impl)
6961 {
6962   GtkClipboard *clipboard = gtk_widget_get_clipboard (GTK_WIDGET (impl),
6963                                                       GDK_SELECTION_CLIPBOARD);
6964   gtk_clipboard_request_text (clipboard,
6965                               (GtkClipboardTextReceivedFunc) paste_text_received,
6966                               impl);
6967 }
6968 
6969 /* Implementation for GtkFileChooserEmbed::should_respond() */
6970 static void
add_selection_to_recent_list(GtkFileChooserWidget * impl)6971 add_selection_to_recent_list (GtkFileChooserWidget *impl)
6972 {
6973   GtkFileChooserWidgetPrivate *priv = impl->priv;
6974   GSList *files;
6975   GSList *l;
6976 
6977   files = gtk_file_chooser_widget_get_files (GTK_FILE_CHOOSER (impl));
6978 
6979   for (l = files; l; l = l->next)
6980     {
6981       GFile *file = l->data;
6982       char *uri;
6983 
6984       uri = g_file_get_uri (file);
6985       if (uri)
6986         {
6987           gtk_recent_manager_add_item (priv->recent_manager, uri);
6988           g_free (uri);
6989         }
6990     }
6991 
6992   g_slist_free_full (files, g_object_unref);
6993 }
6994 
6995 static gboolean
gtk_file_chooser_widget_should_respond(GtkFileChooserEmbed * chooser_embed)6996 gtk_file_chooser_widget_should_respond (GtkFileChooserEmbed *chooser_embed)
6997 {
6998   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser_embed);
6999   GtkFileChooserWidgetPrivate *priv = impl->priv;
7000   GtkWidget *toplevel;
7001   GtkWidget *current_focus;
7002   gboolean retval;
7003 
7004   toplevel = gtk_widget_get_toplevel (GTK_WIDGET (impl));
7005   g_assert (GTK_IS_WINDOW (toplevel));
7006 
7007   retval = FALSE;
7008 
7009   current_focus = gtk_window_get_focus (GTK_WINDOW (toplevel));
7010 
7011   if (current_focus == priv->browse_files_tree_view)
7012     {
7013       /* The following array encodes what we do based on the priv->action and the
7014        * number of files selected.
7015        */
7016       typedef enum {
7017         NOOP,                   /* Do nothing (don't respond) */
7018         RESPOND,                /* Respond immediately */
7019         RESPOND_OR_SWITCH,      /* Respond immediately if the selected item is a file; switch to it if it is a folder */
7020         ALL_FILES,              /* Respond only if everything selected is a file */
7021         ALL_FOLDERS,            /* Respond only if everything selected is a folder */
7022         SAVE_ENTRY,             /* Go to the code for handling the save entry */
7023         NOT_REACHED             /* Sanity check */
7024       } ActionToTake;
7025       static const ActionToTake what_to_do[4][3] = {
7026         /*                           0 selected  1 selected         many selected */
7027         /* ACTION_OPEN */          { NOOP,       RESPOND_OR_SWITCH, ALL_FILES   },
7028         /* ACTION_SAVE */          { SAVE_ENTRY, RESPOND_OR_SWITCH, NOT_REACHED },
7029         /* ACTION_SELECT_FOLDER */ { RESPOND,    ALL_FOLDERS,       ALL_FOLDERS },
7030         /* ACTION_CREATE_FOLDER */ { SAVE_ENTRY, ALL_FOLDERS,       NOT_REACHED }
7031       };
7032 
7033       int num_selected;
7034       gboolean all_files, all_folders;
7035       int k;
7036       ActionToTake action;
7037 
7038     file_list:
7039 
7040       g_assert (priv->action >= GTK_FILE_CHOOSER_ACTION_OPEN && priv->action <= GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER);
7041 
7042       if (priv->operation_mode == OPERATION_MODE_RECENT)
7043         {
7044           if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE)
7045             goto save_entry;
7046           else
7047             {
7048               retval = recent_should_respond (impl);
7049               goto out;
7050             }
7051         }
7052 
7053       selection_check (impl, &num_selected, &all_files, &all_folders);
7054 
7055       if (num_selected > 2)
7056         k = 2;
7057       else
7058         k = num_selected;
7059 
7060       action = what_to_do [priv->action] [k];
7061 
7062       switch (action)
7063         {
7064         case NOOP:
7065           return FALSE;
7066 
7067         case RESPOND:
7068           retval = TRUE;
7069           goto out;
7070 
7071         case RESPOND_OR_SWITCH:
7072           g_assert (num_selected == 1);
7073 
7074           if (all_folders)
7075             {
7076               switch_to_selected_folder (impl);
7077               return FALSE;
7078             }
7079           else if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE)
7080             {
7081               retval = should_respond_after_confirm_overwrite (impl,
7082                                                                get_display_name_from_file_list (impl),
7083                                                                priv->current_folder);
7084               goto out;
7085             }
7086           else
7087             {
7088               retval = TRUE;
7089               goto out;
7090             }
7091 
7092         case ALL_FILES:
7093           retval = all_files;
7094           goto out;
7095 
7096         case ALL_FOLDERS:
7097           retval = all_folders;
7098           goto out;
7099 
7100         case SAVE_ENTRY:
7101           goto save_entry;
7102 
7103         default:
7104           g_assert_not_reached ();
7105         }
7106     }
7107   else if ((priv->location_entry != NULL) && (current_focus == priv->location_entry))
7108     {
7109       GFile *file;
7110       gboolean is_well_formed, is_empty, is_file_part_empty;
7111       gboolean is_folder;
7112       GtkFileChooserEntry *entry;
7113 
7114     save_entry:
7115 
7116       g_assert (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE ||
7117                 priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER ||
7118                 ((priv->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
7119                   priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) &&
7120                  priv->location_mode == LOCATION_MODE_FILENAME_ENTRY));
7121 
7122       entry = GTK_FILE_CHOOSER_ENTRY (priv->location_entry);
7123       check_save_entry (impl, &file, &is_well_formed, &is_empty, &is_file_part_empty, &is_folder);
7124 
7125       if (!is_well_formed)
7126         {
7127           if (!is_empty &&
7128               priv->action == GTK_FILE_CHOOSER_ACTION_SAVE &&
7129               priv->operation_mode == OPERATION_MODE_RECENT)
7130             {
7131               /* FIXME: ERROR_NO_FOLDER */
7132 #if 0
7133               /* We'll #ifdef this out, as the fucking treeview selects its first row,
7134                * thus changing our assumption that no selection is present - setting
7135                * a selection causes the error message from path_bar_set_mode() to go away,
7136                * but we want the user to see that message!
7137                */
7138               gtk_widget_grab_focus (priv->browse_files_tree_view);
7139 #endif
7140             }
7141           /* FIXME: else show an "invalid filename" error as the pathbar mode? */
7142 
7143           return FALSE;
7144         }
7145 
7146       if (is_empty)
7147         {
7148           if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE ||
7149               priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
7150             {
7151               /* FIXME: ERROR_NO_FILENAME */
7152               gtk_widget_grab_focus (priv->location_entry);
7153               return FALSE;
7154             }
7155 
7156           goto file_list;
7157         }
7158 
7159       g_assert (file != NULL);
7160 
7161       if (is_folder)
7162         {
7163           if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
7164               priv->action == GTK_FILE_CHOOSER_ACTION_SAVE)
7165             {
7166               change_folder_and_display_error (impl, file, TRUE);
7167             }
7168           else if (priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER ||
7169                    priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
7170             {
7171               /* The folder already exists, so we do not need to create it.
7172                * Just respond to terminate the dialog.
7173                */
7174               retval = TRUE;
7175             }
7176           else
7177             {
7178               g_assert_not_reached ();
7179             }
7180         }
7181       else
7182         {
7183           struct FileExistsData *data;
7184 
7185           /* We need to check whether file exists and whether it is a folder -
7186            * the GtkFileChooserEntry *does* report is_folder==FALSE as a false
7187            * negative (it doesn't know yet if your last path component is a
7188            * folder).
7189            */
7190 
7191           data = g_new0 (struct FileExistsData, 1);
7192           data->impl = g_object_ref (impl);
7193           data->file = g_object_ref (file);
7194           data->parent_file = _gtk_file_chooser_entry_get_current_folder (entry);
7195 
7196           if (priv->file_exists_get_info_cancellable)
7197             g_cancellable_cancel (priv->file_exists_get_info_cancellable);
7198 
7199           priv->file_exists_get_info_cancellable =
7200             _gtk_file_system_get_info (priv->file_system, file,
7201                                        "standard::type",
7202                                        file_exists_get_info_cb,
7203                                        data);
7204 
7205           set_busy_cursor (impl, TRUE);
7206         }
7207 
7208       g_object_unref (file);
7209     }
7210   else if (priv->toplevel_last_focus_widget == priv->browse_files_tree_view)
7211     {
7212       /* The focus is on a dialog's action area button, *and* the widget that
7213        * was focused immediately before it is the file list.
7214        */
7215       goto file_list;
7216     }
7217   else if (priv->operation_mode == OPERATION_MODE_SEARCH && priv->toplevel_last_focus_widget == priv->search_entry)
7218     {
7219       search_entry_activate_cb (impl);
7220       return FALSE;
7221     }
7222   else if (priv->location_entry && priv->toplevel_last_focus_widget == priv->location_entry)
7223     {
7224       /* The focus is on a dialog's action area button, *and* the widget that
7225        * was focused immediately before it is the location entry.
7226        */
7227       goto save_entry;
7228     }
7229   else
7230     /* The focus is on a dialog's action area button or something else */
7231     if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE ||
7232         priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
7233       goto save_entry;
7234     else
7235       goto file_list;
7236 
7237  out:
7238 
7239   if (retval)
7240     add_selection_to_recent_list (impl);
7241 
7242   return retval;
7243 }
7244 
7245 /* Implementation for GtkFileChooserEmbed::initial_focus() */
7246 static void
gtk_file_chooser_widget_initial_focus(GtkFileChooserEmbed * chooser_embed)7247 gtk_file_chooser_widget_initial_focus (GtkFileChooserEmbed *chooser_embed)
7248 {
7249   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser_embed);
7250   GtkFileChooserWidgetPrivate *priv = impl->priv;
7251   GtkWidget *widget;
7252 
7253   if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
7254       priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
7255     {
7256       if (priv->location_mode == LOCATION_MODE_PATH_BAR
7257           || priv->operation_mode == OPERATION_MODE_RECENT)
7258         widget = priv->browse_files_tree_view;
7259       else
7260         widget = priv->location_entry;
7261     }
7262   else if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE ||
7263            priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
7264     widget = priv->location_entry;
7265   else
7266     {
7267       g_assert_not_reached ();
7268       widget = NULL;
7269     }
7270 
7271   g_assert (widget != NULL);
7272   gtk_widget_grab_focus (widget);
7273 }
7274 
7275 static void
selected_foreach_get_file_cb(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)7276 selected_foreach_get_file_cb (GtkTreeModel *model,
7277                               GtkTreePath  *path,
7278                               GtkTreeIter  *iter,
7279                               gpointer      data)
7280 {
7281   GSList **list;
7282   GFile *file;
7283 
7284   list = data;
7285 
7286   gtk_tree_model_get (model, iter, MODEL_COL_FILE, &file, -1);
7287   /* The file already has a new ref courtesy of gtk_tree_model_get();
7288    * this will be unreffed by the caller
7289    */
7290   *list = g_slist_prepend (*list, file);
7291 }
7292 
7293 static GSList *
get_selected_files(GtkFileChooserWidget * impl)7294 get_selected_files (GtkFileChooserWidget *impl)
7295 {
7296   GtkFileChooserWidgetPrivate *priv = impl->priv;
7297   GSList *result;
7298   GtkTreeSelection *selection;
7299 
7300   result = NULL;
7301 
7302   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view));
7303   gtk_tree_selection_selected_foreach (selection, selected_foreach_get_file_cb, &result);
7304   result = g_slist_reverse (result);
7305 
7306   return result;
7307 }
7308 
7309 static void
selected_foreach_get_info_cb(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)7310 selected_foreach_get_info_cb (GtkTreeModel *model,
7311                               GtkTreePath  *path,
7312                               GtkTreeIter  *iter,
7313                               gpointer      data)
7314 {
7315   GSList **list;
7316   GFileInfo *info;
7317 
7318   list = data;
7319 
7320   info = _gtk_file_system_model_get_info (GTK_FILE_SYSTEM_MODEL (model), iter);
7321   *list = g_slist_prepend (*list, g_object_ref (info));
7322 }
7323 
7324 static GSList *
get_selected_infos(GtkFileChooserWidget * impl)7325 get_selected_infos (GtkFileChooserWidget *impl)
7326 {
7327   GtkFileChooserWidgetPrivate *priv = impl->priv;
7328   GSList *result;
7329   GtkTreeSelection *selection;
7330 
7331   result = NULL;
7332 
7333   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view));
7334   gtk_tree_selection_selected_foreach (selection, selected_foreach_get_info_cb, &result);
7335   result = g_slist_reverse (result);
7336 
7337   return result;
7338 }
7339 
7340 /* Callback used from GtkSearchEngine when we get new hits */
7341 static void
search_engine_hits_added_cb(GtkSearchEngine * engine,GList * hits,GtkFileChooserWidget * impl)7342 search_engine_hits_added_cb (GtkSearchEngine      *engine,
7343                              GList                *hits,
7344                              GtkFileChooserWidget *impl)
7345 {
7346   GList *l, *files, *files_with_info, *infos;
7347   GFile *file;
7348   gboolean select = FALSE;
7349 
7350   if (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (impl->priv->search_model), NULL) == 0)
7351     select = TRUE;
7352 
7353   files = NULL;
7354   files_with_info = NULL;
7355   infos = NULL;
7356   for (l = hits; l; l = l->next)
7357     {
7358       GtkSearchHit *hit = (GtkSearchHit *)l->data;
7359       file = g_object_ref (hit->file);
7360       if (hit->info)
7361         {
7362           files_with_info = g_list_prepend (files_with_info, file);
7363           infos = g_list_prepend (infos, g_object_ref (hit->info));
7364         }
7365       else
7366         files = g_list_prepend (files, file);
7367     }
7368 
7369   _gtk_file_system_model_update_files (impl->priv->search_model,
7370                                        files_with_info, infos);
7371   _gtk_file_system_model_add_and_query_files (impl->priv->search_model,
7372                                               files, MODEL_ATTRIBUTES);
7373 
7374   g_list_free_full (files, g_object_unref);
7375   g_list_free_full (files_with_info, g_object_unref);
7376   g_list_free_full (infos, g_object_unref);
7377 
7378   gtk_stack_set_visible_child_name (GTK_STACK (impl->priv->browse_files_stack), "list");
7379   if (select)
7380     gtk_widget_grab_focus (impl->priv->browse_files_tree_view);
7381 }
7382 
7383 /* Callback used from GtkSearchEngine when the query is done running */
7384 static void
search_engine_finished_cb(GtkSearchEngine * engine,gboolean got_results,gpointer data)7385 search_engine_finished_cb (GtkSearchEngine *engine,
7386                            gboolean         got_results,
7387                            gpointer         data)
7388 {
7389   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (data);
7390   GtkFileChooserWidgetPrivate *priv = impl->priv;
7391 
7392   set_busy_cursor (impl, FALSE);
7393   gtk_widget_hide (priv->search_spinner);
7394 
7395   if (priv->show_progress_timeout)
7396     {
7397       g_source_remove (priv->show_progress_timeout);
7398       priv->show_progress_timeout = 0;
7399     }
7400 
7401   if (!got_results)
7402     {
7403       gtk_stack_set_visible_child_name (GTK_STACK (priv->browse_files_stack), "empty");
7404       gtk_entry_grab_focus_without_selecting (GTK_ENTRY (priv->search_entry));
7405     }
7406 }
7407 
7408 static void
search_engine_error_cb(GtkSearchEngine * engine,const gchar * message,gpointer data)7409 search_engine_error_cb (GtkSearchEngine *engine,
7410                         const gchar     *message,
7411                         gpointer         data)
7412 {
7413   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (data);
7414 
7415   search_stop_searching (impl, TRUE);
7416   error_message (impl, _("Could not send the search request"), message);
7417 }
7418 
7419 /* Frees the data in the search_model */
7420 static void
search_clear_model(GtkFileChooserWidget * impl,gboolean remove)7421 search_clear_model (GtkFileChooserWidget *impl,
7422                     gboolean              remove)
7423 {
7424   GtkFileChooserWidgetPrivate *priv = impl->priv;
7425 
7426   if (!priv->search_model)
7427     return;
7428 
7429   if (remove &&
7430       gtk_tree_view_get_model (GTK_TREE_VIEW (priv->browse_files_tree_view)) == GTK_TREE_MODEL (priv->search_model))
7431     gtk_tree_view_set_model (GTK_TREE_VIEW (priv->browse_files_tree_view), NULL);
7432 
7433   g_clear_object (&priv->search_model);
7434 }
7435 
7436 /* Stops any ongoing searches; does not touch the search_model */
7437 static void
search_stop_searching(GtkFileChooserWidget * impl,gboolean remove_query)7438 search_stop_searching (GtkFileChooserWidget *impl,
7439                        gboolean              remove_query)
7440 {
7441   GtkFileChooserWidgetPrivate *priv = impl->priv;
7442 
7443   if (remove_query && priv->search_entry)
7444     {
7445       gtk_entry_set_text (GTK_ENTRY (priv->search_entry), "");
7446     }
7447 
7448   if (priv->search_engine)
7449     {
7450       _gtk_search_engine_stop (priv->search_engine);
7451       g_signal_handlers_disconnect_by_data (priv->search_engine, impl);
7452       g_clear_object (&priv->search_engine);
7453 
7454       set_busy_cursor (impl, FALSE);
7455       gtk_widget_hide (priv->search_spinner);
7456     }
7457 
7458   if (priv->show_progress_timeout)
7459     {
7460       g_source_remove (priv->show_progress_timeout);
7461       priv->show_progress_timeout = 0;
7462     }
7463 }
7464 
7465 /* Creates the search_model and puts it in the tree view */
7466 static void
search_setup_model(GtkFileChooserWidget * impl)7467 search_setup_model (GtkFileChooserWidget *impl)
7468 {
7469   GtkFileChooserWidgetPrivate *priv = impl->priv;
7470 
7471   g_assert (priv->search_model == NULL);
7472 
7473   priv->search_model = _gtk_file_system_model_new (file_system_model_set,
7474                                                    impl,
7475                                                    MODEL_COLUMN_TYPES);
7476 
7477   gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (priv->search_model),
7478                                            search_sort_func,
7479                                            impl, NULL);
7480   gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (priv->search_model),
7481                                         GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
7482                                         GTK_SORT_ASCENDING);
7483 
7484   gtk_tree_view_set_model (GTK_TREE_VIEW (priv->browse_files_tree_view),
7485                            GTK_TREE_MODEL (priv->search_model));
7486 
7487   gtk_tree_view_column_set_sort_column_id (priv->list_name_column, -1);
7488   gtk_tree_view_column_set_sort_column_id (priv->list_time_column, -1);
7489   gtk_tree_view_column_set_sort_column_id (priv->list_size_column, -1);
7490   gtk_tree_view_column_set_sort_column_id (priv->list_type_column, -1);
7491   gtk_tree_view_column_set_sort_column_id (priv->list_location_column, -1);
7492 
7493   update_columns (impl, TRUE, _("Modified"));
7494 }
7495 
7496 static gboolean
show_spinner(gpointer data)7497 show_spinner (gpointer data)
7498 {
7499   GtkFileChooserWidget *impl = data;
7500   GtkFileChooserWidgetPrivate *priv = impl->priv;
7501 
7502   gtk_widget_show (priv->search_spinner);
7503   priv->show_progress_timeout = 0;
7504 
7505   return G_SOURCE_REMOVE;
7506 }
7507 
7508 /* Creates a new query with the specified text and launches it */
7509 static void
search_start_query(GtkFileChooserWidget * impl,const gchar * query_text)7510 search_start_query (GtkFileChooserWidget *impl,
7511                     const gchar          *query_text)
7512 {
7513   GtkFileChooserWidgetPrivate *priv = impl->priv;
7514   GFile *file;
7515 
7516   if (gtk_stack_get_visible_child (GTK_STACK (priv->browse_files_stack)) == priv->places_view)
7517     return;
7518 
7519   stop_loading_and_clear_list_model (impl, TRUE);
7520   recent_stop_loading (impl);
7521   recent_clear_model (impl, TRUE);
7522 
7523   search_stop_searching (impl, FALSE);
7524   search_clear_model (impl, TRUE);
7525   search_setup_model (impl);
7526 
7527   set_busy_cursor (impl, TRUE);
7528   priv->show_progress_timeout = g_timeout_add (1500, show_spinner, impl);
7529 
7530   gtk_stack_set_visible_child_name (GTK_STACK (priv->browse_files_stack), "list");
7531 
7532   if (priv->search_engine == NULL)
7533     priv->search_engine = _gtk_search_engine_new ();
7534 
7535   if (!priv->search_query)
7536     {
7537       priv->search_query = gtk_query_new ();
7538       gtk_query_set_text (priv->search_query, query_text);
7539     }
7540 
7541   file = gtk_places_sidebar_get_location (GTK_PLACES_SIDEBAR (priv->places_sidebar));
7542   if (file)
7543     {
7544       gtk_query_set_location (priv->search_query, file);
7545       g_object_unref (file);
7546     }
7547   else
7548     gtk_query_set_location (priv->search_query, priv->current_folder);
7549 
7550   _gtk_search_engine_set_model (priv->search_engine, priv->model_for_search);
7551   _gtk_search_engine_set_query (priv->search_engine, priv->search_query);
7552 
7553   g_signal_connect (priv->search_engine, "hits-added",
7554                     G_CALLBACK (search_engine_hits_added_cb), impl);
7555   g_signal_connect (priv->search_engine, "finished",
7556                     G_CALLBACK (search_engine_finished_cb), impl);
7557   g_signal_connect (priv->search_engine, "error",
7558                     G_CALLBACK (search_engine_error_cb), impl);
7559 
7560   _gtk_search_engine_start (priv->search_engine);
7561 
7562   if (gtk_query_get_location (priv->search_query) &&
7563       _gtk_file_consider_as_remote (gtk_query_get_location (priv->search_query)))
7564     gtk_widget_show (priv->remote_warning_bar);
7565 }
7566 
7567 /* Callback used when the user presses Enter while typing on the search
7568  * entry; starts the query
7569  */
7570 static void
search_entry_activate_cb(GtkFileChooserWidget * impl)7571 search_entry_activate_cb (GtkFileChooserWidget *impl)
7572 {
7573   GtkFileChooserWidgetPrivate *priv = impl->priv;
7574   const char *text;
7575 
7576   if (priv->operation_mode != OPERATION_MODE_SEARCH)
7577     return;
7578 
7579   text = gtk_entry_get_text (GTK_ENTRY (priv->search_entry));
7580 
7581   /* reset any existing query object */
7582   g_set_object (&priv->search_query, NULL);
7583 
7584   gtk_places_view_set_search_query (GTK_PLACES_VIEW (priv->places_view), text);
7585 
7586   if (text[0] != '\0')
7587     search_start_query (impl, text);
7588   else
7589     search_stop_searching (impl, FALSE);
7590 }
7591 
7592 static void
search_entry_stop_cb(GtkFileChooserWidget * impl)7593 search_entry_stop_cb (GtkFileChooserWidget *impl)
7594 {
7595   if (impl->priv->search_engine)
7596     search_stop_searching (impl, FALSE);
7597   else
7598     g_object_set (impl, "search-mode", FALSE, NULL);
7599 }
7600 
7601 /* Hides the path bar and creates the search entry */
7602 static void
search_setup_widgets(GtkFileChooserWidget * impl)7603 search_setup_widgets (GtkFileChooserWidget *impl)
7604 {
7605   GtkFileChooserWidgetPrivate *priv = impl->priv;
7606 
7607   /* if there already is a query, restart it */
7608   if (priv->search_query)
7609     {
7610       const gchar *query;
7611 
7612       query = gtk_query_get_text (priv->search_query);
7613       if (query)
7614         {
7615           gtk_entry_set_text (GTK_ENTRY (priv->search_entry), query);
7616           search_start_query (impl, query);
7617         }
7618       else
7619         {
7620           g_object_unref (priv->search_query);
7621           priv->search_query = NULL;
7622         }
7623     }
7624 }
7625 
7626 /*
7627  * Recent files support
7628  */
7629 
7630 /* Frees the data in the recent_model */
7631 static void
recent_clear_model(GtkFileChooserWidget * impl,gboolean remove)7632 recent_clear_model (GtkFileChooserWidget *impl,
7633                     gboolean              remove)
7634 {
7635   GtkFileChooserWidgetPrivate *priv = impl->priv;
7636 
7637   if (!priv->recent_model)
7638     return;
7639 
7640   if (remove)
7641     gtk_tree_view_set_model (GTK_TREE_VIEW (priv->browse_files_tree_view), NULL);
7642 
7643   g_set_object (&priv->recent_model, NULL);
7644 }
7645 
7646 /* Stops any ongoing loading of the recent files list; does
7647  * not touch the recent_model
7648  */
7649 static void
recent_stop_loading(GtkFileChooserWidget * impl)7650 recent_stop_loading (GtkFileChooserWidget *impl)
7651 {
7652   GtkFileChooserWidgetPrivate *priv = impl->priv;
7653 
7654   if (priv->load_recent_id)
7655     {
7656       g_source_remove (priv->load_recent_id);
7657       priv->load_recent_id = 0;
7658     }
7659 }
7660 
7661 static void
recent_setup_model(GtkFileChooserWidget * impl)7662 recent_setup_model (GtkFileChooserWidget *impl)
7663 {
7664   GtkFileChooserWidgetPrivate *priv = impl->priv;
7665 
7666   g_assert (priv->recent_model == NULL);
7667 
7668   priv->recent_model = _gtk_file_system_model_new (file_system_model_set,
7669                                                    impl,
7670                                                    MODEL_COLUMN_TYPES);
7671 
7672   _gtk_file_system_model_set_filter (priv->recent_model, priv->current_filter);
7673   gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (priv->recent_model),
7674                                            recent_sort_func,
7675                                            impl, NULL);
7676   gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (priv->recent_model),
7677                                         GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
7678                                         GTK_SORT_DESCENDING);
7679 }
7680 
7681 typedef struct
7682 {
7683   GtkFileChooserWidget *impl;
7684   GList *items;
7685 } RecentLoadData;
7686 
7687 static void
recent_idle_cleanup(gpointer data)7688 recent_idle_cleanup (gpointer data)
7689 {
7690   RecentLoadData *load_data = data;
7691   GtkFileChooserWidget *impl = load_data->impl;
7692   GtkFileChooserWidgetPrivate *priv = impl->priv;
7693 
7694   gtk_tree_view_set_model (GTK_TREE_VIEW (priv->browse_files_tree_view),
7695                            GTK_TREE_MODEL (priv->recent_model));
7696   gtk_tree_view_set_search_column (GTK_TREE_VIEW (priv->browse_files_tree_view), -1);
7697 
7698   gtk_tree_view_column_set_sort_column_id (priv->list_name_column, -1);
7699   gtk_tree_view_column_set_sort_column_id (priv->list_time_column, -1);
7700   gtk_tree_view_column_set_sort_column_id (priv->list_size_column, -1);
7701   gtk_tree_view_column_set_sort_column_id (priv->list_type_column, -1);
7702   gtk_tree_view_column_set_sort_column_id (priv->list_location_column, -1);
7703 
7704   update_columns (impl, TRUE, _("Accessed"));
7705 
7706   set_busy_cursor (impl, FALSE);
7707 
7708   priv->load_recent_id = 0;
7709 
7710   g_free (load_data);
7711 }
7712 
7713 static gboolean
recent_item_is_private(GtkRecentInfo * info)7714 recent_item_is_private (GtkRecentInfo *info)
7715 {
7716   gboolean is_private = FALSE;
7717 
7718   if (gtk_recent_info_get_private_hint (info))
7719     {
7720       const gchar *app_name = g_get_application_name ();
7721       gchar **recent_apps = gtk_recent_info_get_applications (info, NULL);
7722       is_private = !g_strv_contains ((const char *const*) recent_apps,
7723                                      app_name);
7724       g_strfreev (recent_apps);
7725     }
7726 
7727   return is_private;
7728 }
7729 
7730 /* Populates the file system model with the GtkRecentInfo* items
7731  * in the provided list; frees the items
7732  */
7733 static void
populate_model_with_recent_items(GtkFileChooserWidget * impl,GList * items)7734 populate_model_with_recent_items (GtkFileChooserWidget *impl,
7735                                   GList                *items)
7736 {
7737   GtkFileChooserWidgetPrivate *priv = impl->priv;
7738   gint limit;
7739   GList *l;
7740   gint n;
7741 
7742   limit = DEFAULT_RECENT_FILES_LIMIT;
7743 
7744   n = 0;
7745 
7746   for (l = items; l; l = l->next)
7747     {
7748       GtkRecentInfo *info = l->data;
7749       GFile *file;
7750 
7751       if (recent_item_is_private (info))
7752         continue;
7753 
7754       file = g_file_new_for_uri (gtk_recent_info_get_uri (info));
7755       _gtk_file_system_model_add_and_query_file (priv->recent_model,
7756                                                  file,
7757                                                  MODEL_ATTRIBUTES);
7758       g_object_unref (file);
7759 
7760       n++;
7761       if (limit != -1 && n >= limit)
7762         break;
7763     }
7764 
7765   g_set_object (&priv->model_for_search, priv->recent_model);
7766 }
7767 
7768 static void
populate_model_with_folders(GtkFileChooserWidget * impl,GList * items)7769 populate_model_with_folders (GtkFileChooserWidget *impl,
7770                              GList                *items)
7771 {
7772   GtkFileChooserWidgetPrivate *priv = impl->priv;
7773   GList *folders;
7774   GList *l;
7775 
7776   folders = _gtk_file_chooser_extract_recent_folders (items);
7777 
7778   for (l = folders; l; l = l->next)
7779     _gtk_file_system_model_add_and_query_file (priv->recent_model,
7780                                                G_FILE (l->data),
7781                                                MODEL_ATTRIBUTES);
7782 
7783   g_list_free_full (folders, g_object_unref);
7784 }
7785 
7786 static gboolean
recent_idle_load(gpointer data)7787 recent_idle_load (gpointer data)
7788 {
7789   RecentLoadData *load_data = data;
7790   GtkFileChooserWidget *impl = load_data->impl;
7791   GtkFileChooserWidgetPrivate *priv = impl->priv;
7792 
7793   if (!priv->recent_manager)
7794     return FALSE;
7795 
7796   load_data->items = gtk_recent_manager_get_items (priv->recent_manager);
7797   if (!load_data->items)
7798     return FALSE;
7799 
7800   if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN)
7801     populate_model_with_recent_items (impl, load_data->items);
7802   else
7803     populate_model_with_folders (impl, load_data->items);
7804 
7805   g_list_free_full (load_data->items, (GDestroyNotify) gtk_recent_info_unref);
7806   load_data->items = NULL;
7807 
7808   return FALSE;
7809 }
7810 
7811 static void
recent_start_loading(GtkFileChooserWidget * impl)7812 recent_start_loading (GtkFileChooserWidget *impl)
7813 {
7814   GtkFileChooserWidgetPrivate *priv = impl->priv;
7815   RecentLoadData *load_data;
7816 
7817   recent_stop_loading (impl);
7818   recent_clear_model (impl, TRUE);
7819   recent_setup_model (impl);
7820   set_busy_cursor (impl, TRUE);
7821 
7822   g_assert (priv->load_recent_id == 0);
7823 
7824   load_data = g_new (RecentLoadData, 1);
7825   load_data->impl = impl;
7826   load_data->items = NULL;
7827 
7828   /* begin lazy loading the recent files into the model */
7829   priv->load_recent_id = gdk_threads_add_idle_full (G_PRIORITY_DEFAULT,
7830                                                     recent_idle_load,
7831                                                     load_data,
7832                                                     recent_idle_cleanup);
7833   g_source_set_name_by_id (priv->load_recent_id, "[gtk+] recent_idle_load");
7834 }
7835 
7836 /* Called from ::should_respond(). We return whether there are selected
7837  * files in the recent files list.
7838  */
7839 static gboolean
recent_should_respond(GtkFileChooserWidget * impl)7840 recent_should_respond (GtkFileChooserWidget *impl)
7841 {
7842   GtkFileChooserWidgetPrivate *priv = impl->priv;
7843   GtkTreeSelection *selection;
7844 
7845   g_assert (priv->operation_mode == OPERATION_MODE_RECENT);
7846 
7847   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view));
7848   return (gtk_tree_selection_count_selected_rows (selection) != 0);
7849 }
7850 
7851 static void
set_current_filter(GtkFileChooserWidget * impl,GtkFileFilter * filter)7852 set_current_filter (GtkFileChooserWidget *impl,
7853                     GtkFileFilter        *filter)
7854 {
7855   GtkFileChooserWidgetPrivate *priv = impl->priv;
7856 
7857   if (priv->current_filter != filter)
7858     {
7859       int filter_index;
7860 
7861       /* NULL filters are allowed to reset to non-filtered status */
7862       filter_index = g_slist_index (priv->filters, filter);
7863       if (priv->filters && filter && filter_index < 0)
7864         return;
7865 
7866       if (priv->current_filter)
7867         g_object_unref (priv->current_filter);
7868       priv->current_filter = filter;
7869       if (priv->current_filter)
7870         g_object_ref_sink (priv->current_filter);
7871 
7872       if (priv->filters)
7873         gtk_combo_box_set_active (GTK_COMBO_BOX (priv->filter_combo), filter_index);
7874 
7875       clear_model_cache (impl, MODEL_COL_IS_SENSITIVE);
7876       set_model_filter (impl, priv->current_filter);
7877       g_object_notify (G_OBJECT (impl), "filter");
7878     }
7879 }
7880 
7881 static void
filter_combo_changed(GtkComboBox * combo_box,GtkFileChooserWidget * impl)7882 filter_combo_changed (GtkComboBox          *combo_box,
7883                       GtkFileChooserWidget *impl)
7884 {
7885   GtkFileChooserWidgetPrivate *priv = impl->priv;
7886   gint new_index;
7887   GtkFileFilter *new_filter;
7888 
7889   new_index = gtk_combo_box_get_active (combo_box);
7890   new_filter = g_slist_nth_data (priv->filters, new_index);
7891   set_current_filter (impl, new_filter);
7892 
7893   if (priv->location_entry != NULL)
7894     _gtk_file_chooser_entry_set_file_filter (GTK_FILE_CHOOSER_ENTRY (priv->location_entry),
7895                                              new_filter);
7896 }
7897 
7898 static void
check_preview_change(GtkFileChooserWidget * impl)7899 check_preview_change (GtkFileChooserWidget *impl)
7900 {
7901   GtkFileChooserWidgetPrivate *priv = impl->priv;
7902   GtkTreePath *path;
7903   GFile *new_file;
7904   char *new_display_name;
7905   GtkTreeModel *model;
7906   GtkTreeSelection *selection;
7907 
7908   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->browse_files_tree_view));
7909   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view));
7910   if (gtk_tree_selection_get_mode (selection) == GTK_SELECTION_SINGLE ||
7911       gtk_tree_selection_get_mode (selection) == GTK_SELECTION_BROWSE)
7912     {
7913       GtkTreeIter iter;
7914 
7915       if (gtk_tree_selection_get_selected (selection, NULL, &iter))
7916         path = gtk_tree_model_get_path (model, &iter);
7917       else
7918         path = NULL;
7919     }
7920   else
7921     {
7922       gtk_tree_view_get_cursor (GTK_TREE_VIEW (priv->browse_files_tree_view), &path, NULL);
7923       if (path && !gtk_tree_selection_path_is_selected (selection, path))
7924         {
7925           gtk_tree_path_free (path);
7926           path = NULL;
7927         }
7928     }
7929 
7930   if (path)
7931     {
7932       GtkTreeIter iter;
7933 
7934       gtk_tree_model_get_iter (model, &iter, path);
7935       gtk_tree_model_get (model, &iter,
7936                           MODEL_COL_FILE, &new_file,
7937                           MODEL_COL_NAME, &new_display_name,
7938                           -1);
7939 
7940       gtk_tree_path_free (path);
7941     }
7942   else
7943     {
7944       new_file = NULL;
7945       new_display_name = NULL;
7946     }
7947 
7948   if (new_file != priv->preview_file &&
7949       !(new_file && priv->preview_file &&
7950         g_file_equal (new_file, priv->preview_file)))
7951     {
7952       if (priv->preview_file)
7953         {
7954           g_object_unref (priv->preview_file);
7955           g_free (priv->preview_display_name);
7956         }
7957 
7958       if (new_file)
7959         {
7960           priv->preview_file = new_file;
7961           priv->preview_display_name = new_display_name;
7962         }
7963       else
7964         {
7965           priv->preview_file = NULL;
7966           priv->preview_display_name = NULL;
7967           g_free (new_display_name);
7968         }
7969 
7970       if (priv->use_preview_label && priv->preview_label)
7971         gtk_label_set_text (GTK_LABEL (priv->preview_label), priv->preview_display_name);
7972 
7973       g_signal_emit_by_name (impl, "update-preview");
7974     }
7975   else
7976     {
7977       if (new_file)
7978         g_object_unref (new_file);
7979 
7980       g_free (new_display_name);
7981     }
7982 }
7983 
7984 static gboolean
list_select_func(GtkTreeSelection * selection,GtkTreeModel * model,GtkTreePath * path,gboolean path_currently_selected,gpointer data)7985 list_select_func (GtkTreeSelection *selection,
7986                   GtkTreeModel     *model,
7987                   GtkTreePath      *path,
7988                   gboolean          path_currently_selected,
7989                   gpointer          data)
7990 {
7991   GtkFileChooserWidget *impl = data;
7992   GtkFileChooserWidgetPrivate *priv = impl->priv;
7993 
7994   if (priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER ||
7995       priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
7996     {
7997       GtkTreeIter iter;
7998       gboolean is_sensitive;
7999       gboolean is_folder;
8000 
8001       if (!gtk_tree_model_get_iter (model, &iter, path))
8002         return FALSE;
8003       gtk_tree_model_get (model, &iter,
8004                           MODEL_COL_IS_SENSITIVE, &is_sensitive,
8005                           MODEL_COL_IS_FOLDER, &is_folder,
8006                           -1);
8007       if (!is_sensitive || !is_folder)
8008         return FALSE;
8009     }
8010 
8011   return TRUE;
8012 }
8013 
8014 static void
list_selection_changed(GtkTreeSelection * selection,GtkFileChooserWidget * impl)8015 list_selection_changed (GtkTreeSelection     *selection,
8016                         GtkFileChooserWidget *impl)
8017 {
8018   GtkFileChooserWidgetPrivate *priv = impl->priv;
8019 
8020   if (gtk_tree_view_get_model (GTK_TREE_VIEW (priv->browse_files_tree_view)) == NULL)
8021     return;
8022 
8023   if (priv->location_entry)
8024     update_chooser_entry (impl);
8025 
8026   location_bar_update (impl);
8027 
8028   check_preview_change (impl);
8029 
8030   g_signal_emit_by_name (impl, "selection-changed", 0);
8031 }
8032 
8033 static void
list_cursor_changed(GtkTreeView * list,GtkFileChooserWidget * impl)8034 list_cursor_changed (GtkTreeView          *list,
8035                      GtkFileChooserWidget *impl)
8036 {
8037   check_preview_change (impl);
8038 }
8039 
8040 /* Callback used when a row in the file list is activated */
8041 static void
list_row_activated(GtkTreeView * tree_view,GtkTreePath * path,GtkTreeViewColumn * column,GtkFileChooserWidget * impl)8042 list_row_activated (GtkTreeView          *tree_view,
8043                     GtkTreePath          *path,
8044                     GtkTreeViewColumn    *column,
8045                     GtkFileChooserWidget *impl)
8046 {
8047   GtkFileChooserWidgetPrivate *priv = impl->priv;
8048   GFile *file;
8049   GtkTreeIter iter;
8050   GtkTreeModel *model;
8051   gboolean is_folder;
8052   gboolean is_sensitive;
8053 
8054   model = gtk_tree_view_get_model (tree_view);
8055 
8056   if (!gtk_tree_model_get_iter (model, &iter, path))
8057     return;
8058 
8059   gtk_tree_model_get (model, &iter,
8060                       MODEL_COL_FILE, &file,
8061                       MODEL_COL_IS_FOLDER, &is_folder,
8062                       MODEL_COL_IS_SENSITIVE, &is_sensitive,
8063                       -1);
8064 
8065   if (is_sensitive && is_folder && file)
8066     {
8067       change_folder_and_display_error (impl, file, FALSE);
8068       goto out;
8069     }
8070 
8071   if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
8072       priv->action == GTK_FILE_CHOOSER_ACTION_SAVE)
8073     g_signal_emit_by_name (impl, "file-activated");
8074 
8075  out:
8076 
8077   if (file)
8078     g_object_unref (file);
8079 }
8080 
8081 static void
path_bar_clicked(GtkPathBar * path_bar,GFile * file,GFile * child_file,gboolean child_is_hidden,GtkFileChooserWidget * impl)8082 path_bar_clicked (GtkPathBar           *path_bar,
8083                   GFile                *file,
8084                   GFile                *child_file,
8085                   gboolean              child_is_hidden,
8086                   GtkFileChooserWidget *impl)
8087 {
8088   if (child_file)
8089     pending_select_files_add (impl, child_file);
8090 
8091   if (!change_folder_and_display_error (impl, file, FALSE))
8092     return;
8093 
8094   /* Say we have "/foo/bar/[.baz]" and the user clicks on "bar".  We should then
8095    * show hidden files so that ".baz" appears in the file list, as it will still
8096    * be shown in the path bar: "/foo/[bar]/.baz"
8097    */
8098   if (child_is_hidden)
8099     g_object_set (impl, "show-hidden", TRUE, NULL);
8100 }
8101 
8102 static void
update_cell_renderer_attributes(GtkFileChooserWidget * impl)8103 update_cell_renderer_attributes (GtkFileChooserWidget *impl)
8104 {
8105   GtkFileChooserWidgetPrivate *priv = impl->priv;
8106 
8107   gtk_tree_view_column_set_attributes (priv->list_name_column,
8108                                        priv->list_pixbuf_renderer,
8109                                        "surface", MODEL_COL_SURFACE,
8110                                        "sensitive", MODEL_COL_IS_SENSITIVE,
8111                                        NULL);
8112   gtk_tree_view_column_set_attributes (priv->list_name_column,
8113                                        priv->list_name_renderer,
8114                                        "text", MODEL_COL_NAME,
8115                                        "ellipsize", MODEL_COL_ELLIPSIZE,
8116                                        "sensitive", MODEL_COL_IS_SENSITIVE,
8117                                        NULL);
8118 
8119   gtk_tree_view_column_set_attributes (priv->list_size_column,
8120                                        priv->list_size_renderer,
8121                                        "text", MODEL_COL_SIZE_TEXT,
8122                                        "sensitive", MODEL_COL_IS_SENSITIVE,
8123                                        NULL);
8124 
8125   gtk_tree_view_column_set_attributes (priv->list_type_column,
8126                                        priv->list_type_renderer,
8127                                        "text", MODEL_COL_TYPE,
8128                                        "sensitive", MODEL_COL_IS_SENSITIVE,
8129                                        NULL);
8130 
8131   gtk_tree_view_column_set_attributes (priv->list_time_column,
8132                                        priv->list_date_renderer,
8133                                        "text", MODEL_COL_DATE_TEXT,
8134                                        "sensitive", MODEL_COL_IS_SENSITIVE,
8135                                        NULL);
8136 
8137   gtk_tree_view_column_set_attributes (priv->list_time_column,
8138                                        priv->list_time_renderer,
8139                                        "text", MODEL_COL_TIME_TEXT,
8140                                        "sensitive", MODEL_COL_IS_SENSITIVE,
8141                                        NULL);
8142 
8143   gtk_tree_view_column_set_attributes (priv->list_location_column,
8144                                        priv->list_location_renderer,
8145                                        "text", MODEL_COL_LOCATION_TEXT,
8146                                        "sensitive", MODEL_COL_IS_SENSITIVE,
8147                                        NULL);
8148 
8149   update_time_renderer_visible (impl);
8150 }
8151 
8152 static void
location_set_user_text(GtkFileChooserWidget * impl,const gchar * path)8153 location_set_user_text (GtkFileChooserWidget *impl,
8154                         const gchar          *path)
8155 {
8156   GtkFileChooserWidgetPrivate *priv = impl->priv;
8157 
8158   gtk_entry_set_text (GTK_ENTRY (priv->location_entry), path);
8159   gtk_editable_set_position (GTK_EDITABLE (priv->location_entry), -1);
8160 }
8161 
8162 static void
location_popup_handler(GtkFileChooserWidget * impl,const gchar * path)8163 location_popup_handler (GtkFileChooserWidget *impl,
8164                         const gchar          *path)
8165 {
8166   GtkFileChooserWidgetPrivate *priv = impl->priv;
8167 
8168   if (priv->operation_mode != OPERATION_MODE_BROWSE)
8169     {
8170       operation_mode_set (impl, OPERATION_MODE_BROWSE);
8171       if (priv->current_folder)
8172         change_folder_and_display_error (impl, priv->current_folder, FALSE);
8173       else
8174         switch_to_home_dir (impl);
8175     }
8176 
8177   if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
8178       priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
8179     {
8180       if (!path)
8181         return;
8182 
8183       location_mode_set (impl, LOCATION_MODE_FILENAME_ENTRY);
8184       location_set_user_text (impl, path);
8185     }
8186   else if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE ||
8187            priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
8188     {
8189       gtk_widget_grab_focus (priv->location_entry);
8190       if (path != NULL)
8191         location_set_user_text (impl, path);
8192     }
8193   else
8194     g_assert_not_reached ();
8195 }
8196 
8197 /* Handler for the "up-folder" keybinding signal */
8198 static void
up_folder_handler(GtkFileChooserWidget * impl)8199 up_folder_handler (GtkFileChooserWidget *impl)
8200 {
8201   GtkFileChooserWidgetPrivate *priv = impl->priv;
8202 
8203   _gtk_path_bar_up (GTK_PATH_BAR (priv->browse_path_bar));
8204 }
8205 
8206 /* Handler for the "down-folder" keybinding signal */
8207 static void
down_folder_handler(GtkFileChooserWidget * impl)8208 down_folder_handler (GtkFileChooserWidget *impl)
8209 {
8210   GtkFileChooserWidgetPrivate *priv = impl->priv;
8211 
8212   _gtk_path_bar_down (GTK_PATH_BAR (priv->browse_path_bar));
8213 }
8214 
8215 /* Handler for the "home-folder" keybinding signal */
8216 static void
home_folder_handler(GtkFileChooserWidget * impl)8217 home_folder_handler (GtkFileChooserWidget *impl)
8218 {
8219   switch_to_home_dir (impl);
8220 }
8221 
8222 /* Handler for the "desktop-folder" keybinding signal */
8223 static void
desktop_folder_handler(GtkFileChooserWidget * impl)8224 desktop_folder_handler (GtkFileChooserWidget *impl)
8225 {
8226   const char *name;
8227 
8228   /* "To disable a directory, point it to the homedir."
8229    * See http://freedesktop.org/wiki/Software/xdg-user-dirs
8230    */
8231   name = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP);
8232   if (!g_strcmp0 (name, g_get_home_dir ()))
8233     return;
8234 
8235   gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (impl), name);
8236 }
8237 
8238 /* Handler for the "search-shortcut" keybinding signal */
8239 static void
search_shortcut_handler(GtkFileChooserWidget * impl)8240 search_shortcut_handler (GtkFileChooserWidget *impl)
8241 {
8242   GtkFileChooserWidgetPrivate *priv = impl->priv;
8243 
8244   if (priv->operation_mode == OPERATION_MODE_SEARCH)
8245     {
8246       operation_mode_set (impl, OPERATION_MODE_BROWSE);
8247       if (priv->current_folder)
8248         change_folder_and_display_error (impl, priv->current_folder, FALSE);
8249       else
8250         switch_to_home_dir (impl);
8251     }
8252   else
8253     operation_mode_set (impl, OPERATION_MODE_SEARCH);
8254 }
8255 
8256 /* Handler for the "recent-shortcut" keybinding signal */
8257 static void
recent_shortcut_handler(GtkFileChooserWidget * impl)8258 recent_shortcut_handler (GtkFileChooserWidget *impl)
8259 {
8260   operation_mode_set (impl, OPERATION_MODE_RECENT);
8261 }
8262 
8263 /* Handler for the "places-shortcut" keybinding signal */
8264 static void
places_shortcut_handler(GtkFileChooserWidget * impl)8265 places_shortcut_handler (GtkFileChooserWidget *impl)
8266 {
8267   gtk_widget_child_focus (impl->priv->places_sidebar, GTK_DIR_LEFT);
8268 }
8269 
8270 static void
quick_bookmark_handler(GtkFileChooserWidget * impl,gint bookmark_index)8271 quick_bookmark_handler (GtkFileChooserWidget *impl,
8272                         gint                  bookmark_index)
8273 {
8274   GtkFileChooserWidgetPrivate *priv = impl->priv;
8275   GFile *file;
8276 
8277   file = gtk_places_sidebar_get_nth_bookmark (GTK_PLACES_SIDEBAR (priv->places_sidebar), bookmark_index);
8278 
8279   if (file)
8280     {
8281       change_folder_and_display_error (impl, file, FALSE);
8282       g_object_unref (file);
8283     }
8284 }
8285 
8286 static void
show_hidden_handler(GtkFileChooserWidget * impl)8287 show_hidden_handler (GtkFileChooserWidget *impl)
8288 {
8289   GtkFileChooserWidgetPrivate *priv = impl->priv;
8290 
8291   g_object_set (impl, "show-hidden", !priv->show_hidden, NULL);
8292 }
8293 
8294 static void
add_normal_and_shifted_binding(GtkBindingSet * binding_set,guint keyval,GdkModifierType modifiers,const gchar * signal_name)8295 add_normal_and_shifted_binding (GtkBindingSet   *binding_set,
8296                                 guint            keyval,
8297                                 GdkModifierType  modifiers,
8298                                 const gchar     *signal_name)
8299 {
8300   gtk_binding_entry_add_signal (binding_set,
8301                                 keyval, modifiers,
8302                                 signal_name, 0);
8303 
8304   gtk_binding_entry_add_signal (binding_set,
8305                                 keyval, modifiers | GDK_SHIFT_MASK,
8306                                 signal_name, 0);
8307 }
8308 
8309 static void
gtk_file_chooser_widget_class_init(GtkFileChooserWidgetClass * class)8310 gtk_file_chooser_widget_class_init (GtkFileChooserWidgetClass *class)
8311 {
8312   static const guint quick_bookmark_keyvals[10] = {
8313     GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5, GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
8314   };
8315   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
8316   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
8317   GtkBindingSet *binding_set;
8318   gint i;
8319 
8320   gobject_class->finalize = gtk_file_chooser_widget_finalize;
8321   gobject_class->constructed = gtk_file_chooser_widget_constructed;
8322   gobject_class->set_property = gtk_file_chooser_widget_set_property;
8323   gobject_class->get_property = gtk_file_chooser_widget_get_property;
8324   gobject_class->dispose = gtk_file_chooser_widget_dispose;
8325 
8326   widget_class->show_all = gtk_file_chooser_widget_show_all;
8327   widget_class->realize = gtk_file_chooser_widget_realize;
8328   widget_class->map = gtk_file_chooser_widget_map;
8329   widget_class->unmap = gtk_file_chooser_widget_unmap;
8330   widget_class->hierarchy_changed = gtk_file_chooser_widget_hierarchy_changed;
8331   widget_class->style_updated = gtk_file_chooser_widget_style_updated;
8332   widget_class->screen_changed = gtk_file_chooser_widget_screen_changed;
8333   widget_class->key_press_event = gtk_file_chooser_widget_key_press_event;
8334 
8335   /*
8336    * Signals
8337    */
8338 
8339   /**
8340    * GtkFileChooserWidget::location-popup:
8341    * @widget: the object which received the signal
8342    * @path: a string that gets put in the text entry for the file name
8343    *
8344    * The ::location-popup signal is a [keybinding signal][GtkBindingSignal]
8345    * which gets emitted when the user asks for it.
8346    *
8347    * This is used to make the file chooser show a "Location" prompt which
8348    * the user can use to manually type the name of the file he wishes to select.
8349    *
8350    * The default bindings for this signal are `Control + L` with a @path string
8351    * of "" (the empty string).  It is also bound to `/` with a @path string of
8352    * "`/`" (a slash):  this lets you type `/` and immediately type a path name.
8353    * On Unix systems, this is bound to `~` (tilde) with a @path string of "~"
8354    * itself for access to home directories.
8355    */
8356   signals[LOCATION_POPUP] =
8357     g_signal_new_class_handler (I_("location-popup"),
8358                                 G_OBJECT_CLASS_TYPE (class),
8359                                 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
8360                                 G_CALLBACK (location_popup_handler),
8361                                 NULL, NULL,
8362                                 NULL,
8363                                 G_TYPE_NONE, 1, G_TYPE_STRING);
8364 
8365   /**
8366    * GtkFileChooserWidget::location-popup-on-paste:
8367    * @widget: the object which received the signal
8368    *
8369    * The ::location-popup-on-paste signal is a [keybinding signal][GtkBindingSignal]
8370    * which gets emitted when the user asks for it.
8371    *
8372    * This is used to make the file chooser show a "Location" prompt when the user
8373    * pastes into a #GtkFileChooserWidget.
8374    *
8375    * The default binding for this signal is `Control + V`.
8376    */
8377   signals[LOCATION_POPUP_ON_PASTE] =
8378     g_signal_new_class_handler (I_("location-popup-on-paste"),
8379                                 G_OBJECT_CLASS_TYPE (class),
8380                                 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
8381                                 G_CALLBACK (location_popup_on_paste_handler),
8382                                 NULL, NULL,
8383                                 NULL,
8384                                 G_TYPE_NONE, 0);
8385 
8386   /**
8387    * GtkFileChooserWidget::location-toggle-popup:
8388    * @widget: the object which received the signal
8389    *
8390    * The ::location-toggle-popup signal is a [keybinding signal][GtkBindingSignal]
8391    * which gets emitted when the user asks for it.
8392    *
8393    * This is used to toggle the visibility of a "Location" prompt which the user
8394    * can use to manually type the name of the file he wishes to select.
8395    *
8396    * The default binding for this signal is `Control + L`.
8397    */
8398   signals[LOCATION_TOGGLE_POPUP] =
8399     g_signal_new_class_handler (I_("location-toggle-popup"),
8400                                 G_OBJECT_CLASS_TYPE (class),
8401                                 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
8402                                 G_CALLBACK (location_toggle_popup_handler),
8403                                 NULL, NULL,
8404                                 NULL,
8405                                 G_TYPE_NONE, 0);
8406 
8407   /**
8408    * GtkFileChooserWidget::up-folder:
8409    * @widget: the object which received the signal
8410    *
8411    * The ::up-folder signal is a [keybinding signal][GtkBindingSignal]
8412    * which gets emitted when the user asks for it.
8413    *
8414    * This is used to make the file chooser go to the parent of the current folder
8415    * in the file hierarchy.
8416    *
8417    * The default binding for this signal is `Alt + Up`.
8418    */
8419   signals[UP_FOLDER] =
8420     g_signal_new_class_handler (I_("up-folder"),
8421                                 G_OBJECT_CLASS_TYPE (class),
8422                                 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
8423                                 G_CALLBACK (up_folder_handler),
8424                                 NULL, NULL,
8425                                 NULL,
8426                                 G_TYPE_NONE, 0);
8427 
8428   /**
8429    * GtkFileChooserWidget::down-folder:
8430    * @widget: the object which received the signal
8431    *
8432    * The ::down-folder signal is a [keybinding signal][GtkBindingSignal]
8433    * which gets emitted when the user asks for it.
8434    *
8435    * This is used to make the file chooser go to a child of the current folder
8436    * in the file hierarchy. The subfolder that will be used is displayed in the
8437    * path bar widget of the file chooser. For example, if the path bar is showing
8438    * "/foo/bar/baz", with bar currently displayed, then this will cause the file
8439    * chooser to switch to the "baz" subfolder.
8440    *
8441    * The default binding for this signal is `Alt + Down`.
8442    */
8443   signals[DOWN_FOLDER] =
8444     g_signal_new_class_handler (I_("down-folder"),
8445                                 G_OBJECT_CLASS_TYPE (class),
8446                                 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
8447                                 G_CALLBACK (down_folder_handler),
8448                                 NULL, NULL,
8449                                 NULL,
8450                                 G_TYPE_NONE, 0);
8451 
8452   /**
8453    * GtkFileChooserWidget::home-folder:
8454    * @widget: the object which received the signal
8455    *
8456    * The ::home-folder signal is a [keybinding signal][GtkBindingSignal]
8457    * which gets emitted when the user asks for it.
8458    *
8459    * This is used to make the file chooser show the user's home
8460    * folder in the file list.
8461    *
8462    * The default binding for this signal is `Alt + Home`.
8463    */
8464   signals[HOME_FOLDER] =
8465     g_signal_new_class_handler (I_("home-folder"),
8466                                 G_OBJECT_CLASS_TYPE (class),
8467                                 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
8468                                 G_CALLBACK (home_folder_handler),
8469                                 NULL, NULL,
8470                                 NULL,
8471                                 G_TYPE_NONE, 0);
8472 
8473   /**
8474    * GtkFileChooserWidget::desktop-folder:
8475    * @widget: the object which received the signal
8476    *
8477    * The ::desktop-folder signal is a [keybinding signal][GtkBindingSignal]
8478    * which gets emitted when the user asks for it.
8479    *
8480    * This is used to make the file chooser show the user's Desktop
8481    * folder in the file list.
8482    *
8483    * The default binding for this signal is `Alt + D`.
8484    */
8485   signals[DESKTOP_FOLDER] =
8486     g_signal_new_class_handler (I_("desktop-folder"),
8487                                 G_OBJECT_CLASS_TYPE (class),
8488                                 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
8489                                 G_CALLBACK (desktop_folder_handler),
8490                                 NULL, NULL,
8491                                 NULL,
8492                                 G_TYPE_NONE, 0);
8493 
8494   /**
8495    * GtkFileChooserWidget::quick-bookmark:
8496    * @widget: the object which received the signal
8497    * @bookmark_index: the number of the bookmark to switch to
8498    *
8499    * The ::quick-bookmark signal is a [keybinding signal][GtkBindingSignal]
8500    * which gets emitted when the user asks for it.
8501    *
8502    * This is used to make the file chooser switch to the bookmark specified
8503    * in the @bookmark_index parameter. For example, if you have three bookmarks,
8504    * you can pass 0, 1, 2 to this signal to switch to each of them, respectively.
8505    *
8506    * The default binding for this signal is `Alt + 1`, `Alt + 2`,
8507    * etc. until `Alt + 0`.  Note that in the default binding, that
8508    * `Alt + 1` is actually defined to switch to the bookmark at index
8509    * 0, and so on successively; `Alt + 0` is defined to switch to the
8510    * bookmark at index 10.
8511    */
8512   signals[QUICK_BOOKMARK] =
8513     g_signal_new_class_handler (I_("quick-bookmark"),
8514                                 G_OBJECT_CLASS_TYPE (class),
8515                                 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
8516                                 G_CALLBACK (quick_bookmark_handler),
8517                                 NULL, NULL,
8518                                 NULL,
8519                                 G_TYPE_NONE, 1, G_TYPE_INT);
8520 
8521   /**
8522    * GtkFileChooserWidget::show-hidden:
8523    * @widget: the object which received the signal
8524    *
8525    * The ::show-hidden signal is a [keybinding signal][GtkBindingSignal]
8526    * which gets emitted when the user asks for it.
8527    *
8528    * This is used to make the file chooser display hidden files.
8529    *
8530    * The default binding for this signal is `Control + H`.
8531    */
8532   signals[SHOW_HIDDEN] =
8533     g_signal_new_class_handler (I_("show-hidden"),
8534                                 G_OBJECT_CLASS_TYPE (class),
8535                                 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
8536                                 G_CALLBACK (show_hidden_handler),
8537                                 NULL, NULL,
8538                                 NULL,
8539                                 G_TYPE_NONE, 0);
8540 
8541   /**
8542    * GtkFileChooserWidget::search-shortcut:
8543    * @widget: the object which received the signal
8544    *
8545    * The ::search-shortcut signal is a [keybinding signal][GtkBindingSignal]
8546    * which gets emitted when the user asks for it.
8547    *
8548    * This is used to make the file chooser show the search entry.
8549    *
8550    * The default binding for this signal is `Alt + S`.
8551    */
8552   signals[SEARCH_SHORTCUT] =
8553     g_signal_new_class_handler (I_("search-shortcut"),
8554                                 G_OBJECT_CLASS_TYPE (class),
8555                                 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
8556                                 G_CALLBACK (search_shortcut_handler),
8557                                 NULL, NULL,
8558                                 NULL,
8559                                 G_TYPE_NONE, 0);
8560 
8561   /**
8562    * GtkFileChooserWidget::recent-shortcut:
8563    * @widget: the object which received the signal
8564    *
8565    * The ::recent-shortcut signal is a [keybinding signal][GtkBindingSignal]
8566    * which gets emitted when the user asks for it.
8567    *
8568    * This is used to make the file chooser show the Recent location.
8569    *
8570    * The default binding for this signal is `Alt + R`.
8571    */
8572   signals[RECENT_SHORTCUT] =
8573     g_signal_new_class_handler (I_("recent-shortcut"),
8574                                 G_OBJECT_CLASS_TYPE (class),
8575                                 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
8576                                 G_CALLBACK (recent_shortcut_handler),
8577                                 NULL, NULL,
8578                                 NULL,
8579                                 G_TYPE_NONE, 0);
8580 
8581   /**
8582    * GtkFileChooserWidget::places-shortcut:
8583    * @widget: the object which received the signal
8584    *
8585    * The ::places-shortcut signal is a [keybinding signal][GtkBindingSignal]
8586    * which gets emitted when the user asks for it.
8587    *
8588    * This is used to move the focus to the places sidebar.
8589    *
8590    * The default binding for this signal is `Alt + P`.
8591    */
8592   signals[PLACES_SHORTCUT] =
8593     g_signal_new_class_handler (I_("places-shortcut"),
8594                                 G_OBJECT_CLASS_TYPE (class),
8595                                 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
8596                                 G_CALLBACK (places_shortcut_handler),
8597                                 NULL, NULL,
8598                                 NULL,
8599                                 G_TYPE_NONE, 0);
8600 
8601   binding_set = gtk_binding_set_by_class (class);
8602 
8603   gtk_binding_entry_add_signal (binding_set,
8604                                 GDK_KEY_l, GDK_CONTROL_MASK,
8605                                 "location-toggle-popup",
8606                                 0);
8607 
8608   gtk_binding_entry_add_signal (binding_set,
8609                                 GDK_KEY_v, GDK_CONTROL_MASK,
8610                                 "location-popup-on-paste",
8611                                 0);
8612 
8613   add_normal_and_shifted_binding (binding_set,
8614                                   GDK_KEY_Up, GDK_MOD1_MASK,
8615                                   "up-folder");
8616 
8617   add_normal_and_shifted_binding (binding_set,
8618                                   GDK_KEY_KP_Up, GDK_MOD1_MASK,
8619                                   "up-folder");
8620 
8621   add_normal_and_shifted_binding (binding_set,
8622                                   GDK_KEY_Down, GDK_MOD1_MASK,
8623                                   "down-folder");
8624   add_normal_and_shifted_binding (binding_set,
8625                                   GDK_KEY_KP_Down, GDK_MOD1_MASK,
8626                                   "down-folder");
8627 
8628   gtk_binding_entry_add_signal (binding_set,
8629                                 GDK_KEY_Home, GDK_MOD1_MASK,
8630                                 "home-folder",
8631                                 0);
8632   gtk_binding_entry_add_signal (binding_set,
8633                                 GDK_KEY_KP_Home, GDK_MOD1_MASK,
8634                                 "home-folder",
8635                                 0);
8636   gtk_binding_entry_add_signal (binding_set,
8637                                 GDK_KEY_d, GDK_MOD1_MASK,
8638                                 "desktop-folder",
8639                                 0);
8640   gtk_binding_entry_add_signal (binding_set,
8641                                 GDK_KEY_h, GDK_CONTROL_MASK,
8642                                 "show-hidden",
8643                                 0);
8644   gtk_binding_entry_add_signal (binding_set,
8645                                 GDK_KEY_s, GDK_MOD1_MASK,
8646                                 "search-shortcut",
8647                                 0);
8648   gtk_binding_entry_add_signal (binding_set,
8649                                 GDK_KEY_f, GDK_CONTROL_MASK,
8650                                 "search-shortcut",
8651                                 0);
8652   gtk_binding_entry_add_signal (binding_set,
8653                                 GDK_KEY_r, GDK_MOD1_MASK,
8654                                 "recent-shortcut",
8655                                 0);
8656   gtk_binding_entry_add_signal (binding_set,
8657                                 GDK_KEY_p, GDK_MOD1_MASK,
8658                                 "places-shortcut",
8659                                 0);
8660 
8661   for (i = 0; i < 10; i++)
8662     gtk_binding_entry_add_signal (binding_set,
8663                                   quick_bookmark_keyvals[i], GDK_MOD1_MASK,
8664                                   "quick-bookmark",
8665                                   1, G_TYPE_INT, i);
8666 
8667   g_object_class_install_property (gobject_class, PROP_SEARCH_MODE,
8668                                    g_param_spec_boolean ("search-mode",
8669                                                          P_("Search mode"),
8670                                                          P_("Search mode"),
8671                                                          FALSE,
8672                                                          G_PARAM_READWRITE));
8673 
8674   g_object_class_install_property (gobject_class, PROP_SUBTITLE,
8675                                    g_param_spec_string ("subtitle",
8676                                                         P_("Subtitle"),
8677                                                         P_("Subtitle"),
8678                                                         "",
8679                                                         G_PARAM_READABLE));
8680 
8681   _gtk_file_chooser_install_properties (gobject_class);
8682 
8683   /* Bind class to template */
8684   gtk_widget_class_set_template_from_resource (widget_class,
8685                                                "/org/gtk/libgtk/ui/gtkfilechooserwidget.ui");
8686 
8687   /* A *lot* of widgets that we need to handle .... */
8688   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, browse_widgets_hpaned);
8689   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, browse_files_stack);
8690   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, places_sidebar);
8691   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, places_view);
8692   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, browse_files_tree_view);
8693   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, browse_files_swin);
8694   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, browse_header_revealer);
8695   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, browse_header_stack);
8696   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, browse_new_folder_button);
8697   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, browse_path_bar_size_group);
8698   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, browse_path_bar);
8699   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, filter_combo_hbox);
8700   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, filter_combo);
8701   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, preview_box);
8702   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, extra_align);
8703   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, extra_and_filters);
8704   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, location_entry_box);
8705   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, search_entry);
8706   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, search_spinner);
8707   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, list_name_column);
8708   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, list_pixbuf_renderer);
8709   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, list_name_renderer);
8710   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, list_time_column);
8711   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, list_date_renderer);
8712   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, list_time_renderer);
8713   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, list_size_column);
8714   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, list_size_renderer);
8715   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, list_type_column);
8716   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, list_type_renderer);
8717   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, list_location_column);
8718   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, list_location_renderer);
8719   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, new_folder_name_entry);
8720   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, new_folder_create_button);
8721   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, new_folder_error_label);
8722   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, new_folder_popover);
8723   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, rename_file_name_entry);
8724   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, rename_file_rename_button);
8725   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, rename_file_error_label);
8726   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, rename_file_popover);
8727   gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, remote_warning_bar);
8728 
8729   /* And a *lot* of callbacks to bind ... */
8730   gtk_widget_class_bind_template_callback (widget_class, browse_files_key_press_event_cb);
8731   gtk_widget_class_bind_template_callback (widget_class, file_list_drag_drop_cb);
8732   gtk_widget_class_bind_template_callback (widget_class, file_list_drag_data_received_cb);
8733   gtk_widget_class_bind_template_callback (widget_class, list_popup_menu_cb);
8734   gtk_widget_class_bind_template_callback (widget_class, file_list_query_tooltip_cb);
8735   gtk_widget_class_bind_template_callback (widget_class, list_button_press_event_cb);
8736   gtk_widget_class_bind_template_callback (widget_class, list_row_activated);
8737   gtk_widget_class_bind_template_callback (widget_class, file_list_drag_begin_cb);
8738   gtk_widget_class_bind_template_callback (widget_class, file_list_drag_motion_cb);
8739   gtk_widget_class_bind_template_callback (widget_class, file_list_drag_end_cb);
8740   gtk_widget_class_bind_template_callback (widget_class, list_selection_changed);
8741   gtk_widget_class_bind_template_callback (widget_class, list_cursor_changed);
8742   gtk_widget_class_bind_template_callback (widget_class, filter_combo_changed);
8743   gtk_widget_class_bind_template_callback (widget_class, path_bar_clicked);
8744   gtk_widget_class_bind_template_callback (widget_class, places_sidebar_open_location_cb);
8745   gtk_widget_class_bind_template_callback (widget_class, places_sidebar_show_error_message_cb);
8746   gtk_widget_class_bind_template_callback (widget_class, places_sidebar_show_other_locations_with_flags_cb);
8747   gtk_widget_class_bind_template_callback (widget_class, search_entry_activate_cb);
8748   gtk_widget_class_bind_template_callback (widget_class, search_entry_stop_cb);
8749   gtk_widget_class_bind_template_callback (widget_class, new_folder_popover_active);
8750   gtk_widget_class_bind_template_callback (widget_class, new_folder_name_changed);
8751   gtk_widget_class_bind_template_callback (widget_class, new_folder_create_clicked);
8752   gtk_widget_class_bind_template_callback (widget_class, rename_file_name_changed);
8753   gtk_widget_class_bind_template_callback (widget_class, rename_file_rename_clicked);
8754   gtk_widget_class_bind_template_callback (widget_class, rename_file_end);
8755 
8756   gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_FILE_CHOOSER_WIDGET_ACCESSIBLE);
8757   gtk_widget_class_set_css_name (widget_class, "filechooser");
8758 }
8759 
8760 static void
post_process_ui(GtkFileChooserWidget * impl)8761 post_process_ui (GtkFileChooserWidget *impl)
8762 {
8763   GtkTreeSelection *selection;
8764   GtkCellRenderer *cell;
8765   AtkObject *atk_obj;
8766   GList *cells;
8767   GFile *file;
8768 
8769   /* Some qdata, qdata can't be set with GtkBuilder */
8770   g_object_set_data (G_OBJECT (impl->priv->browse_files_tree_view), "fmq-name", "file_list");
8771   g_object_set_data (G_OBJECT (impl->priv->browse_files_tree_view), I_("GtkFileChooserWidget"), impl);
8772 
8773   /* Setup file list treeview */
8774   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->priv->browse_files_tree_view));
8775   gtk_tree_selection_set_select_function (selection,
8776                                           list_select_func,
8777                                           impl, NULL);
8778   gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (impl->priv->browse_files_tree_view),
8779                                           GDK_BUTTON1_MASK,
8780                                           NULL, 0,
8781                                           GDK_ACTION_COPY | GDK_ACTION_MOVE);
8782   gtk_drag_source_add_uri_targets (impl->priv->browse_files_tree_view);
8783 
8784   gtk_drag_dest_set (impl->priv->browse_files_tree_view,
8785                      GTK_DEST_DEFAULT_ALL,
8786                      NULL, 0,
8787                      GDK_ACTION_COPY | GDK_ACTION_MOVE);
8788   gtk_drag_dest_add_uri_targets (impl->priv->browse_files_tree_view);
8789 
8790   /* File browser treemodel columns are shared between GtkFileChooser implementations,
8791    * so we don't set cell renderer attributes in GtkBuilder, but rather keep that
8792    * in code.
8793    */
8794   file_list_set_sort_column_ids (impl);
8795   update_cell_renderer_attributes (impl);
8796 
8797   /* Get the combo's text renderer and set ellipsize parameters,
8798    * perhaps GtkComboBoxText should declare the cell renderer
8799    * as an 'internal-child', then we could configure it in GtkBuilder
8800    * instead of hard coding it here.
8801    */
8802   cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (impl->priv->filter_combo));
8803   g_assert (cells);
8804   cell = cells->data;
8805   g_object_set (G_OBJECT (cell),
8806                 "ellipsize", PANGO_ELLIPSIZE_END,
8807                 NULL);
8808 
8809   g_list_free (cells);
8810 
8811   /* Set the GtkPathBar file system backend */
8812   _gtk_path_bar_set_file_system (GTK_PATH_BAR (impl->priv->browse_path_bar), impl->priv->file_system);
8813   file = g_file_new_for_path ("/");
8814   _gtk_path_bar_set_file (GTK_PATH_BAR (impl->priv->browse_path_bar), file, FALSE);
8815   g_object_unref (file);
8816 
8817   /* Set the fixed size icon renderer, this requires
8818    * that priv->icon_size be already setup.
8819    */
8820   set_icon_cell_renderer_fixed_size (impl);
8821 
8822   atk_obj = gtk_widget_get_accessible (impl->priv->browse_new_folder_button);
8823   if (GTK_IS_ACCESSIBLE (atk_obj))
8824     atk_object_set_name (atk_obj, _("Create Folder"));
8825 
8826   gtk_popover_set_default_widget (GTK_POPOVER (impl->priv->new_folder_popover), impl->priv->new_folder_create_button);
8827   gtk_popover_set_default_widget (GTK_POPOVER (impl->priv->rename_file_popover), impl->priv->rename_file_rename_button);
8828   gtk_popover_set_relative_to (GTK_POPOVER (impl->priv->rename_file_popover), impl->priv->browse_files_tree_view);
8829 
8830   add_actions (impl);
8831 }
8832 
8833 void
gtk_file_chooser_widget_set_save_entry(GtkFileChooserWidget * impl,GtkWidget * entry)8834 gtk_file_chooser_widget_set_save_entry (GtkFileChooserWidget *impl,
8835                                         GtkWidget            *entry)
8836 {
8837   GtkFileChooserWidgetPrivate *priv = impl->priv;
8838 
8839   g_return_if_fail (GTK_IS_FILE_CHOOSER_WIDGET (impl));
8840   g_return_if_fail (entry == NULL || GTK_IS_FILE_CHOOSER_ENTRY (entry));
8841 
8842   priv->external_entry = entry;
8843 
8844   if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE ||
8845       priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
8846     {
8847       save_widgets_destroy (impl);
8848       save_widgets_create (impl);
8849     }
8850 }
8851 
8852 static void
gtk_file_chooser_widget_init(GtkFileChooserWidget * impl)8853 gtk_file_chooser_widget_init (GtkFileChooserWidget *impl)
8854 {
8855   GtkFileChooserWidgetPrivate *priv;
8856 
8857   profile_start ("start", NULL);
8858 #ifdef PROFILE_FILE_CHOOSER
8859   access ("MARK: *** CREATE FILE CHOOSER", F_OK);
8860 #endif
8861   impl->priv = gtk_file_chooser_widget_get_instance_private (impl);
8862   priv = impl->priv;
8863 
8864   priv->local_only = TRUE;
8865   priv->preview_widget_active = TRUE;
8866   priv->use_preview_label = TRUE;
8867   priv->select_multiple = FALSE;
8868   priv->show_hidden = FALSE;
8869   priv->show_size_column = TRUE;
8870   priv->show_type_column = TRUE;
8871   priv->type_format = TYPE_FORMAT_MIME;
8872   priv->icon_size = FALLBACK_ICON_SIZE;
8873   priv->load_state = LOAD_EMPTY;
8874   priv->reload_state = RELOAD_EMPTY;
8875   priv->pending_select_files = NULL;
8876   priv->location_mode = LOCATION_MODE_PATH_BAR;
8877   priv->operation_mode = OPERATION_MODE_BROWSE;
8878   priv->sort_column = MODEL_COL_NAME;
8879   priv->sort_order = GTK_SORT_ASCENDING;
8880   priv->recent_manager = gtk_recent_manager_get_default ();
8881   priv->create_folders = TRUE;
8882   priv->auto_selecting_first_row = FALSE;
8883   priv->renamed_file = NULL;
8884 
8885   /* Ensure GTK+ private types used by the template
8886    * definition before calling gtk_widget_init_template()
8887    */
8888   g_type_ensure (GTK_TYPE_PATH_BAR);
8889   g_type_ensure (GTK_TYPE_PLACES_VIEW);
8890 
8891   gtk_widget_init_template (GTK_WIDGET (impl));
8892   gtk_widget_set_size_request (priv->browse_files_tree_view, 280, -1);
8893 
8894   set_file_system_backend (impl);
8895 
8896   priv->bookmarks_manager = _gtk_bookmarks_manager_new (NULL, NULL);
8897 
8898   priv->long_press_gesture = gtk_gesture_long_press_new (priv->browse_files_tree_view);
8899   gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (priv->long_press_gesture), TRUE);
8900   g_signal_connect (priv->long_press_gesture, "pressed",
8901                     G_CALLBACK (long_press_cb), impl);
8902 
8903   /* Setup various attributes and callbacks in the UI
8904    * which cannot be done with GtkBuilder.
8905    */
8906   post_process_ui (impl);
8907 
8908   profile_end ("end", NULL);
8909 }
8910 
8911 /**
8912  * gtk_file_chooser_widget_new:
8913  * @action: Open or save mode for the widget
8914  *
8915  * Creates a new #GtkFileChooserWidget. This is a file chooser widget that can
8916  * be embedded in custom windows, and it is the same widget that is used by
8917  * #GtkFileChooserDialog.
8918  *
8919  * Returns: a new #GtkFileChooserWidget
8920  *
8921  * Since: 2.4
8922  **/
8923 GtkWidget *
gtk_file_chooser_widget_new(GtkFileChooserAction action)8924 gtk_file_chooser_widget_new (GtkFileChooserAction action)
8925 {
8926   return g_object_new (GTK_TYPE_FILE_CHOOSER_WIDGET,
8927                        "action", action,
8928                        NULL);
8929 }
8930 
8931 static void
gtk_file_chooser_widget_add_choice(GtkFileChooser * chooser,const char * id,const char * label,const char ** options,const char ** option_labels)8932 gtk_file_chooser_widget_add_choice (GtkFileChooser  *chooser,
8933                                     const char      *id,
8934                                     const char      *label,
8935                                     const char     **options,
8936                                     const char     **option_labels)
8937 {
8938   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser);
8939   GtkFileChooserWidgetPrivate *priv = impl->priv;
8940   GtkWidget *widget;
8941 
8942   if (priv->choices == NULL)
8943     {
8944       priv->choices = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
8945       priv->choice_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
8946       set_extra_widget (impl, priv->choice_box);
8947     }
8948   else if (g_hash_table_lookup (priv->choices, id))
8949     {
8950       g_warning ("Duplicate choice %s", id);
8951       return;
8952     }
8953 
8954   if (options)
8955     {
8956       GtkWidget *box;
8957       GtkWidget *combo;
8958       int i;
8959 
8960       box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
8961       gtk_container_add (GTK_CONTAINER (box), gtk_label_new (label));
8962 
8963       combo = gtk_combo_box_text_new ();
8964       g_hash_table_insert (priv->choices, g_strdup (id), combo);
8965       gtk_container_add (GTK_CONTAINER (box), combo);
8966 
8967       for (i = 0; options[i]; i++)
8968         gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo),
8969                                    options[i], option_labels[i]);
8970 
8971       widget = box;
8972     }
8973   else
8974     {
8975       GtkWidget *check;
8976 
8977       check = gtk_check_button_new_with_label (label);
8978       g_hash_table_insert (priv->choices, g_strdup (id), check);
8979 
8980       widget = check;
8981     }
8982 
8983   gtk_widget_show_all (widget);
8984   gtk_container_add (GTK_CONTAINER (priv->choice_box), widget);
8985 }
8986 
8987 static void
gtk_file_chooser_widget_remove_choice(GtkFileChooser * chooser,const char * id)8988 gtk_file_chooser_widget_remove_choice (GtkFileChooser  *chooser,
8989                                        const char      *id)
8990 {
8991   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser);
8992   GtkFileChooserWidgetPrivate *priv = impl->priv;
8993   GtkWidget *widget;
8994 
8995   if (priv->choices == NULL)
8996     return;
8997 
8998   widget = (GtkWidget *)g_hash_table_lookup (priv->choices, id);
8999   g_hash_table_remove (priv->choices, id);
9000   gtk_container_remove (GTK_CONTAINER (priv->choice_box), widget);
9001 
9002   if (g_hash_table_size (priv->choices) == 0)
9003     {
9004       set_extra_widget (impl, NULL);
9005       g_hash_table_unref (priv->choices);
9006       priv->choices = NULL;
9007       priv->choice_box = NULL;
9008     }
9009 }
9010 
9011 static void
gtk_file_chooser_widget_set_choice(GtkFileChooser * chooser,const char * id,const char * option)9012 gtk_file_chooser_widget_set_choice (GtkFileChooser  *chooser,
9013                                     const char      *id,
9014                                     const char      *option)
9015 {
9016   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser);
9017   GtkFileChooserWidgetPrivate *priv = impl->priv;
9018   GtkWidget *widget;
9019 
9020   if (priv->choices == NULL)
9021     return;
9022 
9023   widget = (GtkWidget *)g_hash_table_lookup (priv->choices, id);
9024 
9025   if (GTK_IS_COMBO_BOX (widget))
9026     gtk_combo_box_set_active_id (GTK_COMBO_BOX (widget), option);
9027   else if (GTK_IS_TOGGLE_BUTTON (widget))
9028     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), g_str_equal (option, "true"));
9029 }
9030 
9031 static const char *
gtk_file_chooser_widget_get_choice(GtkFileChooser * chooser,const char * id)9032 gtk_file_chooser_widget_get_choice (GtkFileChooser  *chooser,
9033                                     const char      *id)
9034 {
9035   GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser);
9036   GtkFileChooserWidgetPrivate *priv = impl->priv;
9037   GtkWidget *widget;
9038 
9039   if (priv->choices == NULL)
9040     return NULL;
9041 
9042   widget = (GtkWidget *)g_hash_table_lookup (priv->choices, id);
9043   if (GTK_IS_COMBO_BOX (widget))
9044     return gtk_combo_box_get_active_id (GTK_COMBO_BOX (widget));
9045   else if (GTK_IS_TOGGLE_BUTTON (widget))
9046     return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)) ? "true" : "false";
9047 
9048   return NULL;
9049 }
9050 
9051