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