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

source code of gtk/gtk/gtkfilechooserwidget.c