diff options
-rw-r--r-- | gtk/Makefile.am | 4 | ||||
-rw-r--r-- | gtk/gtk-keys.css.mac | 2 | ||||
-rw-r--r-- | gtk/gtk.gresource.xml | 2 | ||||
-rw-r--r-- | gtk/gtkfilechooserbutton.c | 2 | ||||
-rw-r--r-- | gtk/gtkfilechooserdefault.c | 7520 | ||||
-rw-r--r-- | gtk/gtkfilechooserdefault.h | 38 | ||||
-rw-r--r-- | gtk/gtkfilechooserwidget.c | 7493 | ||||
-rw-r--r-- | gtk/gtkrc.key.mac | 2 | ||||
-rw-r--r-- | gtk/resources/ui/gtkfilechooserwidget.ui (renamed from gtk/resources/ui/gtkfilechooserdefault.ui) | 2 | ||||
-rw-r--r-- | testsuite/gtk/filechooser.c | 136 | ||||
-rw-r--r-- | testsuite/gtk/templates.c | 2 |
11 files changed, 7493 insertions, 7710 deletions
diff --git a/gtk/Makefile.am b/gtk/Makefile.am index d0ceb1c51f..67fc47efb1 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -469,7 +469,6 @@ gtk_private_h_sources = \ gtkcustompaperunixdialog.h \ gtkdialogprivate.h \ gtkentryprivate.h \ - gtkfilechooserdefault.h \ gtkfilechooserembed.h \ gtkfilechooserentry.h \ gtkfilechooserprivate.h \ @@ -722,7 +721,6 @@ gtk_base_c_sources = \ gtkexpander.c \ gtkfilechooser.c \ gtkfilechooserbutton.c \ - gtkfilechooserdefault.c \ gtkfilechooserdialog.c \ gtkfilechooserembed.c \ gtkfilechooserentry.c \ @@ -1119,7 +1117,7 @@ COMPOSITE_TEMPLATES = \ resources/ui/gtkcoloreditor.ui \ resources/ui/gtkdialog.ui \ resources/ui/gtkfilechooserbutton.ui \ - resources/ui/gtkfilechooserdefault.ui \ + resources/ui/gtkfilechooserwidget.ui \ resources/ui/gtkfilechooserdialog.ui \ resources/ui/gtkfontbutton.ui \ resources/ui/gtkfontchooserdialog.ui \ diff --git a/gtk/gtk-keys.css.mac b/gtk/gtk-keys.css.mac index 7d51403a96..15ba6e8f65 100644 --- a/gtk/gtk-keys.css.mac +++ b/gtk/gtk-keys.css.mac @@ -196,7 +196,7 @@ GtkLabel { gtk-key-bindings: gtk-mac-alt-arrows, gtk-mac-label; } -GtkFileChooserDefault { +GtkFileChooserWidget { gtk-key-bindings: gtk-mac-file-chooser; } diff --git a/gtk/gtk.gresource.xml b/gtk/gtk.gresource.xml index 7c430d9b1a..df3fa41f6d 100644 --- a/gtk/gtk.gresource.xml +++ b/gtk/gtk.gresource.xml @@ -19,7 +19,7 @@ <file compressed="true">ui/gtkcoloreditor.ui</file> <file compressed="true">ui/gtkdialog.ui</file> <file compressed="true">ui/gtkfilechooserbutton.ui</file> - <file compressed="true">ui/gtkfilechooserdefault.ui</file> + <file compressed="true">ui/gtkfilechooserwidget.ui</file> <file compressed="true">ui/gtkfilechooserdialog.ui</file> <file compressed="true">ui/gtkfontbutton.ui</file> <file compressed="true">ui/gtkfontchooserdialog.ui</file> diff --git a/gtk/gtkfilechooserbutton.c b/gtk/gtkfilechooserbutton.c index d33d34787b..2fadbdd48b 100644 --- a/gtk/gtkfilechooserbutton.c +++ b/gtk/gtkfilechooserbutton.c @@ -662,7 +662,7 @@ get_selected_file (GtkFileChooserButton *button) else if (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog)) == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) { /* If there is no "real" selection in SELECT_FOLDER mode, then we'll just return - * the current folder, since that is what GtkFileChooserDefault would do. + * the current folder, since that is what GtkFileChooserWidget would do. */ if (priv->current_folder_while_inactive) retval = priv->current_folder_while_inactive; diff --git a/gtk/gtkfilechooserdefault.c b/gtk/gtkfilechooserdefault.c deleted file mode 100644 index 447170be96..0000000000 --- a/gtk/gtkfilechooserdefault.c +++ /dev/null @@ -1,7520 +0,0 @@ -/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */ -/* GTK - The GIMP Toolkit - * gtkfilechooserdefault.c: Default implementation of GtkFileChooser - * Copyright (C) 2003, Red Hat, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see <http://www.gnu.org/licenses/>. - */ - -/* TODO: - * - * * Fix FIXME-places-sidebar - */ - -#include "config.h" - -#include "gtkfilechooserdefault.h" - -#include "gtkbindings.h" -#include "gtkcelllayout.h" -#include "gtkcellrendererpixbuf.h" -#include "gtkcellrenderertext.h" -#include "gtkcheckmenuitem.h" -#include "gtkclipboard.h" -#include "gtkcomboboxtext.h" -#include "gtkentry.h" -#include "gtkexpander.h" -#include "gtkfilechooserprivate.h" -#include "gtkfilechooserdialog.h" -#include "gtkfilechooserembed.h" -#include "gtkfilechooserentry.h" -#include "gtkfilechooserutils.h" -#include "gtkfilechooser.h" -#include "gtkfilesystem.h" -#include "gtkfilesystemmodel.h" -#include "gtkframe.h" -#include "gtkgrid.h" -#include "deprecated/gtkiconfactory.h" -#include "gtkicontheme.h" -#include "gtkimage.h" -#include "deprecated/gtkimagemenuitem.h" -#include "gtkinfobar.h" -#include "gtklabel.h" -#include "gtkmarshalers.h" -#include "gtkmessagedialog.h" -#include "gtkmountoperation.h" -#include "gtkpaned.h" -#include "gtkpathbar.h" -#include "gtkplacessidebar.h" -#include "gtkprivate.h" -#include "gtkradiobutton.h" -#include "gtkrecentfilter.h" -#include "gtkrecentmanager.h" -#include "gtkscrolledwindow.h" -#include "gtkseparatormenuitem.h" -#include "gtksettings.h" -#include "gtksizegroup.h" -#include "gtksizerequest.h" -#include "gtktoolbar.h" -#include "gtktoolbutton.h" -#include "gtktooltip.h" -#include "gtktreednd.h" -#include "gtktreeprivate.h" -#include "gtktreeselection.h" -#include "gtkbox.h" -#include "gtkorientable.h" -#include "gtkintl.h" - -#include <cairo-gobject.h> -#include <errno.h> -#include <string.h> -#include <time.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <locale.h> - -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif -#ifdef G_OS_WIN32 -#include <io.h> -#endif - -/* Values for GtkSelection-related "info" fields */ -#define SELECTION_TEXT 0 -#define SELECTION_URI 1 - -/* Profiling stuff */ -#undef PROFILE_FILE_CHOOSER -#ifdef PROFILE_FILE_CHOOSER - - -#ifndef F_OK -#define F_OK 0 -#endif - -#define PROFILE_INDENT 4 - -static int profile_indent; - -static void -profile_add_indent (int indent) -{ - profile_indent += indent; - if (profile_indent < 0) - g_error ("You screwed up your indentation"); -} - -void -_gtk_file_chooser_profile_log (const char *func, int indent, const char *msg1, const char *msg2) -{ - char *str; - - if (indent < 0) - profile_add_indent (indent); - - if (profile_indent == 0) - str = g_strdup_printf ("MARK: %s %s %s", func ? func : "", msg1 ? msg1 : "", msg2 ? msg2 : ""); - else - str = g_strdup_printf ("MARK: %*c %s %s %s", profile_indent - 1, ' ', func ? func : "", msg1 ? msg1 : "", msg2 ? msg2 : ""); - - access (str, F_OK); - g_free (str); - - if (indent > 0) - profile_add_indent (indent); -} - -#define profile_start(x, y) _gtk_file_chooser_profile_log (G_STRFUNC, PROFILE_INDENT, x, y) -#define profile_end(x, y) _gtk_file_chooser_profile_log (G_STRFUNC, -PROFILE_INDENT, x, y) -#define profile_msg(x, y) _gtk_file_chooser_profile_log (NULL, 0, x, y) -#else -#define profile_start(x, y) -#define profile_end(x, y) -#define profile_msg(x, y) -#endif - - -typedef enum { - LOAD_EMPTY, /* There is no model */ - LOAD_PRELOAD, /* Model is loading and a timer is running; model isn't inserted into the tree yet */ - LOAD_LOADING, /* Timeout expired, model is inserted into the tree, but not fully loaded yet */ - LOAD_FINISHED /* Model is fully loaded and inserted into the tree */ -} LoadState; - -typedef enum { - RELOAD_EMPTY, /* No folder has been set */ - RELOAD_HAS_FOLDER /* We have a folder, although it may not be completely loaded yet; no need to reload */ -} ReloadState; - -typedef enum { - LOCATION_MODE_PATH_BAR, - LOCATION_MODE_FILENAME_ENTRY -} LocationMode; - -typedef enum { - OPERATION_MODE_BROWSE, - OPERATION_MODE_SEARCH, - OPERATION_MODE_RECENT -} OperationMode; - -typedef enum { - STARTUP_MODE_RECENT, - STARTUP_MODE_CWD -} StartupMode; - -typedef struct { - GtkFileChooserAction action; - - GtkFileSystem *file_system; - - /* Save mode widgets */ - GtkWidget *save_widgets; - GtkWidget *save_widgets_table; - - GtkWidget *save_folder_label; - - /* The file browsing widgets */ - GtkWidget *browse_widgets_box; - GtkWidget *browse_widgets_hpaned; - GtkWidget *browse_header_box; - GtkWidget *browse_files_tree_view; - GtkWidget *browse_files_popup_menu; - GtkWidget *browse_files_popup_menu_add_shortcut_item; - GtkWidget *browse_files_popup_menu_hidden_files_item; - GtkWidget *browse_files_popup_menu_size_column_item; - GtkWidget *browse_files_popup_menu_copy_file_location_item; - GtkWidget *browse_files_popup_menu_visit_file_item; - GtkWidget *browse_new_folder_button; - GtkWidget *browse_path_bar_hbox; - GtkSizeGroup *browse_path_bar_size_group; - GtkWidget *browse_path_bar; - GtkWidget *browse_special_mode_icon; - GtkWidget *browse_special_mode_label; - GtkWidget *browse_select_a_folder_info_bar; - GtkWidget *browse_select_a_folder_label; - GtkWidget *browse_select_a_folder_icon; - - GtkFileSystemModel *browse_files_model; - char *browse_files_last_selected_name; - - GtkWidget *places_sidebar; - StartupMode startup_mode; - - /* OPERATION_MODE_SEARCH */ - GtkWidget *search_hbox; - GtkWidget *search_entry; - GtkSearchEngine *search_engine; - GtkQuery *search_query; - GtkFileSystemModel *search_model; - - /* OPERATION_MODE_RECENT */ - GtkRecentManager *recent_manager; - GtkFileSystemModel *recent_model; - guint load_recent_id; - - GtkWidget *filter_combo_hbox; - GtkWidget *filter_combo; - GtkWidget *preview_box; - GtkWidget *preview_label; - GtkWidget *preview_widget; - GtkWidget *extra_align; - GtkWidget *extra_widget; - - GtkWidget *location_button; - GtkWidget *location_entry_box; - GtkWidget *location_label; - GtkWidget *location_entry; - LocationMode location_mode; - - /* Handles */ - GSList *loading_shortcuts; - GSList *reload_icon_cancellables; - GCancellable *file_list_drag_data_received_cancellable; - GCancellable *update_current_folder_cancellable; - GCancellable *should_respond_get_info_cancellable; - GCancellable *file_exists_get_info_cancellable; - GCancellable *update_from_entry_cancellable; - GCancellable *shortcuts_activate_iter_cancellable; - - LoadState load_state; - ReloadState reload_state; - guint load_timeout_id; - - OperationMode operation_mode; - - GSList *pending_select_files; - - GtkFileFilter *current_filter; - GSList *filters; - - GtkBookmarksManager *bookmarks_manager; - - int num_volumes; - int num_shortcuts; - int num_bookmarks; - - gulong volumes_changed_id; - gulong bookmarks_changed_id; - - GFile *current_volume_file; - GFile *current_folder; - GFile *preview_file; - char *preview_display_name; - - GtkTreeViewColumn *list_name_column; - GtkCellRenderer *list_name_renderer; - GtkCellRenderer *list_pixbuf_renderer; - GtkTreeViewColumn *list_mtime_column; - GtkTreeViewColumn *list_size_column; - - GSource *edited_idle; - char *edited_new_text; - - gulong settings_signal_id; - int icon_size; - - GSource *focus_entry_idle; - - gulong toplevel_set_focus_id; - GtkWidget *toplevel_last_focus_widget; - - gint sort_column; - GtkSortType sort_order; - - /* Flags */ - - guint local_only : 1; - guint preview_widget_active : 1; - guint use_preview_label : 1; - guint select_multiple : 1; - guint show_hidden : 1; - guint sort_directories_first : 1; - guint do_overwrite_confirmation : 1; - guint list_sort_ascending : 1; - guint shortcuts_current_folder_active : 1; - guint show_size_column : 1; - guint create_folders : 1; - guint auto_selecting_first_row : 1; -} GtkFileChooserDefaultPrivate; - -#define GTK_FILE_CHOOSER_DEFAULT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_CHOOSER_DEFAULT, GtkFileChooserDefaultClass)) -#define GTK_IS_FILE_CHOOSER_DEFAULT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FILE_CHOOSER_DEFAULT)) -#define GTK_FILE_CHOOSER_DEFAULT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FILE_CHOOSER_DEFAULT, GtkFileChooserDefaultClass)) - -#define MAX_LOADING_TIME 500 - -#define DEFAULT_NEW_FOLDER_NAME _("Type name of new folder") - -typedef struct _GtkFileChooserDefaultClass GtkFileChooserDefaultClass; - -struct _GtkFileChooserDefault -{ - GtkBox parent_instance; - - GtkFileChooserDefaultPrivate *priv; -}; - -struct _GtkFileChooserDefaultClass -{ - GtkBoxClass parent_class; -}; - -/* Signal IDs */ -enum { - LOCATION_POPUP, - LOCATION_POPUP_ON_PASTE, - UP_FOLDER, - DOWN_FOLDER, - HOME_FOLDER, - DESKTOP_FOLDER, - QUICK_BOOKMARK, - LOCATION_TOGGLE_POPUP, - SHOW_HIDDEN, - SEARCH_SHORTCUT, - RECENT_SHORTCUT, - - LAST_SIGNAL -}; - -static guint signals[LAST_SIGNAL] = { 0 }; - -#define MODEL_ATTRIBUTES "standard::name,standard::type,standard::display-name," \ - "standard::is-hidden,standard::is-backup,standard::size," \ - "standard::content-type,time::modified" -enum { - /* the first 3 must be these due to settings caching sort column */ - MODEL_COL_NAME, - MODEL_COL_SIZE, - MODEL_COL_MTIME, - MODEL_COL_FILE, - MODEL_COL_NAME_COLLATED, - MODEL_COL_IS_FOLDER, - MODEL_COL_IS_SENSITIVE, - MODEL_COL_SURFACE, - MODEL_COL_SIZE_TEXT, - MODEL_COL_MTIME_TEXT, - MODEL_COL_ELLIPSIZE, - MODEL_COL_NUM_COLUMNS -}; - -/* This list of types is passed to _gtk_file_system_model_new*() */ -#define MODEL_COLUMN_TYPES \ - MODEL_COL_NUM_COLUMNS, \ - G_TYPE_STRING, /* MODEL_COL_NAME */ \ - G_TYPE_INT64, /* MODEL_COL_SIZE */ \ - G_TYPE_LONG, /* MODEL_COL_MTIME */ \ - G_TYPE_FILE, /* MODEL_COL_FILE */ \ - G_TYPE_STRING, /* MODEL_COL_NAME_COLLATED */ \ - G_TYPE_BOOLEAN, /* MODEL_COL_IS_FOLDER */ \ - G_TYPE_BOOLEAN, /* MODEL_COL_IS_SENSITIVE */ \ - CAIRO_GOBJECT_TYPE_SURFACE, /* MODEL_COL_SURFACE */ \ - G_TYPE_STRING, /* MODEL_COL_SIZE_TEXT */ \ - G_TYPE_STRING, /* MODEL_COL_MTIME_TEXT */ \ - PANGO_TYPE_ELLIPSIZE_MODE /* MODEL_COL_ELLIPSIZE */ - -/* Identifiers for target types */ -enum { - GTK_TREE_MODEL_ROW, -}; - -#define DEFAULT_RECENT_FILES_LIMIT 50 - -/* Icon size for if we can't get it from the theme */ -#define FALLBACK_ICON_SIZE 16 - -#define PREVIEW_HBOX_SPACING 12 -#define NUM_LINES 45 -#define NUM_CHARS 60 - -static void gtk_file_chooser_default_iface_init (GtkFileChooserIface *iface); -static void gtk_file_chooser_embed_default_iface_init (GtkFileChooserEmbedIface *iface); - -static GObject* gtk_file_chooser_default_constructor (GType type, - guint n_construct_properties, - GObjectConstructParam *construct_params); -static void gtk_file_chooser_default_finalize (GObject *object); -static void gtk_file_chooser_default_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec); -static void gtk_file_chooser_default_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec); -static void gtk_file_chooser_default_dispose (GObject *object); -static void gtk_file_chooser_default_show_all (GtkWidget *widget); -static void gtk_file_chooser_default_realize (GtkWidget *widget); -static void gtk_file_chooser_default_map (GtkWidget *widget); -static void gtk_file_chooser_default_unmap (GtkWidget *widget); -static void gtk_file_chooser_default_hierarchy_changed (GtkWidget *widget, - GtkWidget *previous_toplevel); -static void gtk_file_chooser_default_style_updated (GtkWidget *widget); -static void gtk_file_chooser_default_screen_changed (GtkWidget *widget, - GdkScreen *previous_screen); - -static gboolean gtk_file_chooser_default_set_current_folder (GtkFileChooser *chooser, - GFile *folder, - GError **error); -static gboolean gtk_file_chooser_default_update_current_folder (GtkFileChooser *chooser, - GFile *folder, - gboolean keep_trail, - gboolean clear_entry, - GError **error); -static GFile * gtk_file_chooser_default_get_current_folder (GtkFileChooser *chooser); -static void gtk_file_chooser_default_set_current_name (GtkFileChooser *chooser, - const gchar *name); -static gchar * gtk_file_chooser_default_get_current_name (GtkFileChooser *chooser); -static gboolean gtk_file_chooser_default_select_file (GtkFileChooser *chooser, - GFile *file, - GError **error); -static void gtk_file_chooser_default_unselect_file (GtkFileChooser *chooser, - GFile *file); -static void gtk_file_chooser_default_select_all (GtkFileChooser *chooser); -static void gtk_file_chooser_default_unselect_all (GtkFileChooser *chooser); -static GSList * gtk_file_chooser_default_get_files (GtkFileChooser *chooser); -static GFile * gtk_file_chooser_default_get_preview_file (GtkFileChooser *chooser); -static GtkFileSystem *gtk_file_chooser_default_get_file_system (GtkFileChooser *chooser); -static void gtk_file_chooser_default_add_filter (GtkFileChooser *chooser, - GtkFileFilter *filter); -static void gtk_file_chooser_default_remove_filter (GtkFileChooser *chooser, - GtkFileFilter *filter); -static GSList * gtk_file_chooser_default_list_filters (GtkFileChooser *chooser); -static gboolean gtk_file_chooser_default_add_shortcut_folder (GtkFileChooser *chooser, - GFile *file, - GError **error); -static gboolean gtk_file_chooser_default_remove_shortcut_folder (GtkFileChooser *chooser, - GFile *file, - GError **error); -static GSList * gtk_file_chooser_default_list_shortcut_folders (GtkFileChooser *chooser); - -static void gtk_file_chooser_default_get_default_size (GtkFileChooserEmbed *chooser_embed, - gint *default_width, - gint *default_height); -static gboolean gtk_file_chooser_default_should_respond (GtkFileChooserEmbed *chooser_embed); -static void gtk_file_chooser_default_initial_focus (GtkFileChooserEmbed *chooser_embed); - -static void add_selection_to_recent_list (GtkFileChooserDefault *impl); - -static void location_popup_handler (GtkFileChooserDefault *impl, - const gchar *path); -static void location_popup_on_paste_handler (GtkFileChooserDefault *impl); -static void location_toggle_popup_handler (GtkFileChooserDefault *impl); -static void up_folder_handler (GtkFileChooserDefault *impl); -static void down_folder_handler (GtkFileChooserDefault *impl); -static void home_folder_handler (GtkFileChooserDefault *impl); -static void desktop_folder_handler (GtkFileChooserDefault *impl); -static void quick_bookmark_handler (GtkFileChooserDefault *impl, - gint bookmark_index); -static void show_hidden_handler (GtkFileChooserDefault *impl); -static void search_shortcut_handler (GtkFileChooserDefault *impl); -static void recent_shortcut_handler (GtkFileChooserDefault *impl); -static void update_appearance (GtkFileChooserDefault *impl); - -static void operation_mode_set (GtkFileChooserDefault *impl, OperationMode mode); - -static void set_current_filter (GtkFileChooserDefault *impl, - GtkFileFilter *filter); -static void check_preview_change (GtkFileChooserDefault *impl); - -static void filter_combo_changed (GtkComboBox *combo_box, - GtkFileChooserDefault *impl); - -static gboolean list_select_func (GtkTreeSelection *selection, - GtkTreeModel *model, - GtkTreePath *path, - gboolean path_currently_selected, - gpointer data); - -static void list_selection_changed (GtkTreeSelection *tree_selection, - GtkFileChooserDefault *impl); -static void list_row_activated (GtkTreeView *tree_view, - GtkTreePath *path, - GtkTreeViewColumn *column, - GtkFileChooserDefault *impl); - -static void path_bar_clicked (GtkPathBar *path_bar, - GFile *file, - GFile *child, - gboolean child_is_hidden, - GtkFileChooserDefault *impl); - -static void update_cell_renderer_attributes (GtkFileChooserDefault *impl); - -static void load_remove_timer (GtkFileChooserDefault *impl, LoadState new_load_state); -static void browse_files_center_selected_row (GtkFileChooserDefault *impl); - -static void location_button_toggled_cb (GtkToggleButton *toggle, - GtkFileChooserDefault *impl); -static void location_switch_to_path_bar (GtkFileChooserDefault *impl); - -static void stop_loading_and_clear_list_model (GtkFileChooserDefault *impl, - gboolean remove_from_treeview); - -static void search_setup_widgets (GtkFileChooserDefault *impl); -static void search_stop_searching (GtkFileChooserDefault *impl, - gboolean remove_query); -static void search_clear_model (GtkFileChooserDefault *impl, - gboolean remove_from_treeview); -static gboolean search_should_respond (GtkFileChooserDefault *impl); -static GSList *search_get_selected_files (GtkFileChooserDefault *impl); -static void search_entry_activate_cb (GtkEntry *entry, - gpointer data); -static void settings_load (GtkFileChooserDefault *impl); - -static void recent_start_loading (GtkFileChooserDefault *impl); -static void recent_stop_loading (GtkFileChooserDefault *impl); -static void recent_clear_model (GtkFileChooserDefault *impl, - gboolean remove_from_treeview); -static gboolean recent_should_respond (GtkFileChooserDefault *impl); -static GSList * recent_get_selected_files (GtkFileChooserDefault *impl); -static void set_file_system_backend (GtkFileChooserDefault *impl); -static void unset_file_system_backend (GtkFileChooserDefault *impl); - - - -G_DEFINE_TYPE_WITH_CODE (GtkFileChooserDefault, _gtk_file_chooser_default, GTK_TYPE_BOX, - G_ADD_PRIVATE (GtkFileChooserDefault) - G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_CHOOSER, - gtk_file_chooser_default_iface_init) - G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_CHOOSER_EMBED, - gtk_file_chooser_embed_default_iface_init)); - -static void -gtk_file_chooser_default_iface_init (GtkFileChooserIface *iface) -{ - iface->select_file = gtk_file_chooser_default_select_file; - iface->unselect_file = gtk_file_chooser_default_unselect_file; - iface->select_all = gtk_file_chooser_default_select_all; - iface->unselect_all = gtk_file_chooser_default_unselect_all; - iface->get_files = gtk_file_chooser_default_get_files; - iface->get_preview_file = gtk_file_chooser_default_get_preview_file; - iface->get_file_system = gtk_file_chooser_default_get_file_system; - iface->set_current_folder = gtk_file_chooser_default_set_current_folder; - iface->get_current_folder = gtk_file_chooser_default_get_current_folder; - iface->set_current_name = gtk_file_chooser_default_set_current_name; - iface->get_current_name = gtk_file_chooser_default_get_current_name; - iface->add_filter = gtk_file_chooser_default_add_filter; - iface->remove_filter = gtk_file_chooser_default_remove_filter; - iface->list_filters = gtk_file_chooser_default_list_filters; - iface->add_shortcut_folder = gtk_file_chooser_default_add_shortcut_folder; - iface->remove_shortcut_folder = gtk_file_chooser_default_remove_shortcut_folder; - iface->list_shortcut_folders = gtk_file_chooser_default_list_shortcut_folders; -} - -static void -gtk_file_chooser_embed_default_iface_init (GtkFileChooserEmbedIface *iface) -{ - iface->get_default_size = gtk_file_chooser_default_get_default_size; - iface->should_respond = gtk_file_chooser_default_should_respond; - iface->initial_focus = gtk_file_chooser_default_initial_focus; -} - -static void -pending_select_files_free (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - g_slist_free_full (priv->pending_select_files, g_object_unref); - priv->pending_select_files = NULL; -} - -static void -pending_select_files_add (GtkFileChooserDefault *impl, - GFile *file) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - priv->pending_select_files = - g_slist_prepend (priv->pending_select_files, g_object_ref (file)); -} - -static void -gtk_file_chooser_default_finalize (GObject *object) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (object); - GtkFileChooserDefaultPrivate *priv = impl->priv; - GSList *l; - - unset_file_system_backend (impl); - - g_free (priv->browse_files_last_selected_name); - - for (l = priv->filters; l; l = l->next) - { - GtkFileFilter *filter; - - filter = GTK_FILE_FILTER (l->data); - g_object_unref (filter); - } - g_slist_free (priv->filters); - - if (priv->current_filter) - g_object_unref (priv->current_filter); - - if (priv->current_volume_file) - g_object_unref (priv->current_volume_file); - - if (priv->current_folder) - g_object_unref (priv->current_folder); - - if (priv->preview_file) - g_object_unref (priv->preview_file); - - if (priv->browse_path_bar_size_group) - g_object_unref (priv->browse_path_bar_size_group); - - /* Free all the Models we have */ - stop_loading_and_clear_list_model (impl, FALSE); - search_clear_model (impl, FALSE); - recent_clear_model (impl, FALSE); - - /* stopping the load above should have cleared this */ - g_assert (priv->load_timeout_id == 0); - - g_free (priv->preview_display_name); - - g_free (priv->edited_new_text); - - impl->priv = NULL; - - G_OBJECT_CLASS (_gtk_file_chooser_default_parent_class)->finalize (object); -} - -/* Shows an error dialog set as transient for the specified window */ -static void -error_message_with_parent (GtkWindow *parent, - const char *msg, - const char *detail) -{ - GtkWidget *dialog; - - dialog = gtk_message_dialog_new (parent, - GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_ERROR, - GTK_BUTTONS_OK, - "%s", - msg); - gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), - "%s", detail); - - if (parent && gtk_window_has_group (parent)) - gtk_window_group_add_window (gtk_window_get_group (parent), - GTK_WINDOW (dialog)); - - gtk_dialog_run (GTK_DIALOG (dialog)); - gtk_widget_destroy (dialog); -} - -/* Returns a toplevel GtkWindow, or NULL if none */ -static GtkWindow * -get_toplevel (GtkWidget *widget) -{ - GtkWidget *toplevel; - - toplevel = gtk_widget_get_toplevel (widget); - if (!gtk_widget_is_toplevel (toplevel)) - return NULL; - else - return GTK_WINDOW (toplevel); -} - -/* Shows an error dialog for the file chooser */ -static void -error_message (GtkFileChooserDefault *impl, - const char *msg, - const char *detail) -{ - error_message_with_parent (get_toplevel (GTK_WIDGET (impl)), msg, detail); -} - -/* Shows a simple error dialog relative to a path. Frees the GError as well. */ -static void -error_dialog (GtkFileChooserDefault *impl, - const char *msg, - GFile *file, - GError *error) -{ - if (error) - { - char *uri = NULL; - char *text; - - if (file) - uri = g_file_get_uri (file); - text = g_strdup_printf (msg, uri); - error_message (impl, text, error->message); - g_free (text); - g_free (uri); - g_error_free (error); - } -} - -/* Shows an error dialog about not being able to create a folder */ -static void -error_creating_folder_dialog (GtkFileChooserDefault *impl, - GFile *file, - GError *error) -{ - error_dialog (impl, - _("The folder could not be created"), - file, error); -} - -/* Shows an error about not being able to create a folder because a file with - * the same name is already there. - */ -static void -error_creating_folder_over_existing_file_dialog (GtkFileChooserDefault *impl, - GFile *file, - GError *error) -{ - error_dialog (impl, - _("The folder could not be created, as a file with the same " - "name already exists. Try using a different name for the " - "folder, or rename the file first."), - file, error); -} - -static void -error_with_file_under_nonfolder (GtkFileChooserDefault *impl, - GFile *parent_file) -{ - GError *error; - - error = NULL; - g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY, - _("You need to choose a valid filename.")); - - error_dialog (impl, - _("Cannot create a file under %s as it is not a folder"), - parent_file, error); -} - -static void -error_filename_to_long_dialog (GtkFileChooserDefault *impl) -{ - error_message (impl, - _("Cannot create file as the filename is too long"), - _("Try using a shorter name.")); -} - -/* Shows an error about not being able to select a folder because a file with - * the same name is already there. - */ -static void -error_selecting_folder_over_existing_file_dialog (GtkFileChooserDefault *impl) -{ - error_message (impl, - _("You may only select folders"), - _("The item that you selected is not a folder try using a different item.")); -} - -/* Shows an error dialog about not being able to create a filename */ -static void -error_building_filename_dialog (GtkFileChooserDefault *impl, - GError *error) -{ - error_dialog (impl, _("Invalid file name"), - NULL, error); -} - -/* Shows an error dialog when we cannot switch to a folder */ -static void -error_changing_folder_dialog (GtkFileChooserDefault *impl, - GFile *file, - GError *error) -{ - error_dialog (impl, _("The folder contents could not be displayed"), - file, error); -} - -/* Changes folders, displaying an error dialog if this fails */ -static gboolean -change_folder_and_display_error (GtkFileChooserDefault *impl, - GFile *file, - gboolean clear_entry) -{ - GError *error; - gboolean result; - - g_return_val_if_fail (G_IS_FILE (file), FALSE); - - /* We copy the path because of this case: - * - * list_row_activated() - * fetches path from model; path belongs to the model (*) - * calls change_folder_and_display_error() - * calls gtk_file_chooser_set_current_folder_file() - * changing folders fails, sets model to NULL, thus freeing the path in (*) - */ - - error = NULL; - result = gtk_file_chooser_default_update_current_folder (GTK_FILE_CHOOSER (impl), file, TRUE, clear_entry, &error); - - if (!result) - error_changing_folder_dialog (impl, file, error); - - return result; -} - -static void -emit_default_size_changed (GtkFileChooserDefault *impl) -{ - profile_msg (" emit default-size-changed start", NULL); - g_signal_emit_by_name (impl, "default-size-changed"); - profile_msg (" emit default-size-changed end", NULL); -} - -static void -update_preview_widget_visibility (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (priv->use_preview_label) - { - if (!priv->preview_label) - { - priv->preview_label = gtk_label_new (priv->preview_display_name); - gtk_box_pack_start (GTK_BOX (priv->preview_box), priv->preview_label, FALSE, FALSE, 0); - gtk_box_reorder_child (GTK_BOX (priv->preview_box), priv->preview_label, 0); - gtk_label_set_ellipsize (GTK_LABEL (priv->preview_label), PANGO_ELLIPSIZE_MIDDLE); - gtk_widget_show (priv->preview_label); - } - } - else - { - if (priv->preview_label) - { - gtk_widget_destroy (priv->preview_label); - priv->preview_label = NULL; - } - } - - if (priv->preview_widget_active && priv->preview_widget) - gtk_widget_show (priv->preview_box); - else - gtk_widget_hide (priv->preview_box); - - if (!gtk_widget_get_mapped (GTK_WIDGET (impl))) - emit_default_size_changed (impl); -} - -static void -set_preview_widget (GtkFileChooserDefault *impl, - GtkWidget *preview_widget) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (preview_widget == priv->preview_widget) - return; - - if (priv->preview_widget) - gtk_container_remove (GTK_CONTAINER (priv->preview_box), - priv->preview_widget); - - priv->preview_widget = preview_widget; - if (priv->preview_widget) - { - gtk_widget_show (priv->preview_widget); - gtk_box_pack_start (GTK_BOX (priv->preview_box), priv->preview_widget, TRUE, TRUE, 0); - gtk_box_reorder_child (GTK_BOX (priv->preview_box), - priv->preview_widget, - (priv->use_preview_label && priv->preview_label) ? 1 : 0); - } - - update_preview_widget_visibility (impl); -} - -/* Callback used when the "New Folder" button is clicked */ -static void -new_folder_button_clicked (GtkButton *button, - GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkTreeIter iter; - GtkTreePath *path; - - if (!priv->browse_files_model) - return; /* FIXME: this sucks. Disable the New Folder button or something. */ - - /* Prevent button from being clicked twice */ - gtk_widget_set_sensitive (priv->browse_new_folder_button, FALSE); - - _gtk_file_system_model_add_editable (priv->browse_files_model, &iter); - - path = gtk_tree_model_get_path (GTK_TREE_MODEL (priv->browse_files_model), &iter); - gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (priv->browse_files_tree_view), - path, priv->list_name_column, - FALSE, 0.0, 0.0); - - g_object_set (priv->list_name_renderer, "editable", TRUE, NULL); - gtk_tree_view_set_cursor (GTK_TREE_VIEW (priv->browse_files_tree_view), - path, - priv->list_name_column, - TRUE); - - gtk_tree_path_free (path); -} - -static GSource * -add_idle_while_impl_is_alive (GtkFileChooserDefault *impl, GCallback callback) -{ - GSource *source; - - source = g_idle_source_new (); - g_source_set_closure (source, - g_cclosure_new_object (callback, G_OBJECT (impl))); - g_source_attach (source, NULL); - - return source; -} - -/* Idle handler for creating a new folder after editing its name cell, or for - * canceling the editing. - */ -static gboolean -edited_idle_cb (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - gdk_threads_enter (); - - g_source_destroy (priv->edited_idle); - priv->edited_idle = NULL; - - _gtk_file_system_model_remove_editable (priv->browse_files_model); - g_object_set (priv->list_name_renderer, "editable", FALSE, NULL); - - gtk_widget_set_sensitive (priv->browse_new_folder_button, TRUE); - - if (priv->edited_new_text /* not cancelled? */ - && (strlen (priv->edited_new_text) != 0) - && (strcmp (priv->edited_new_text, DEFAULT_NEW_FOLDER_NAME) != 0)) /* Don't create folder if name is empty or has not been edited */ - { - GError *error = NULL; - GFile *file; - - file = g_file_get_child_for_display_name (priv->current_folder, - priv->edited_new_text, - &error); - if (file) - { - GError *error = NULL; - - if (g_file_make_directory (file, NULL, &error)) - change_folder_and_display_error (impl, file, FALSE); - else - error_creating_folder_dialog (impl, file, error); - - g_object_unref (file); - } - else - error_creating_folder_dialog (impl, file, error); - - g_free (priv->edited_new_text); - priv->edited_new_text = NULL; - } - - gdk_threads_leave (); - - return FALSE; -} - -static void -queue_edited_idle (GtkFileChooserDefault *impl, - const gchar *new_text) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - /* We create the folder in an idle handler so that we don't modify the tree - * just now. - */ - - if (!priv->edited_idle) - priv->edited_idle = add_idle_while_impl_is_alive (impl, G_CALLBACK (edited_idle_cb)); - - g_free (priv->edited_new_text); - priv->edited_new_text = g_strdup (new_text); -} - -/* Callback used from the text cell renderer when the new folder is named */ -static void -renderer_edited_cb (GtkCellRendererText *cell_renderer_text, - const gchar *path, - const gchar *new_text, - GtkFileChooserDefault *impl) -{ - /* work around bug #154921 */ - g_object_set (cell_renderer_text, - "mode", GTK_CELL_RENDERER_MODE_INERT, NULL); - queue_edited_idle (impl, new_text); -} - -/* Callback used from the text cell renderer when the new folder edition gets - * canceled. - */ -static void -renderer_editing_canceled_cb (GtkCellRendererText *cell_renderer_text, - GtkFileChooserDefault *impl) -{ - /* work around bug #154921 */ - g_object_set (cell_renderer_text, - "mode", GTK_CELL_RENDERER_MODE_INERT, NULL); - queue_edited_idle (impl, NULL); -} - - -struct selection_check_closure { - GtkFileChooserDefault *impl; - int num_selected; - gboolean all_files; - gboolean all_folders; -}; - -/* Used from gtk_tree_selection_selected_foreach() */ -static void -selection_check_foreach_cb (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) -{ - struct selection_check_closure *closure; - gboolean is_folder; - GFile *file; - - gtk_tree_model_get (model, iter, - MODEL_COL_FILE, &file, - MODEL_COL_IS_FOLDER, &is_folder, - -1); - - if (file == NULL) - return; - - g_object_unref (file); - - closure = data; - closure->num_selected++; - - closure->all_folders = closure->all_folders && is_folder; - closure->all_files = closure->all_files && !is_folder; -} - -/* Checks whether the selected items in the file list are all files or all folders */ -static void -selection_check (GtkFileChooserDefault *impl, - gint *num_selected, - gboolean *all_files, - gboolean *all_folders) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - struct selection_check_closure closure; - GtkTreeSelection *selection; - - closure.impl = impl; - closure.num_selected = 0; - closure.all_files = TRUE; - closure.all_folders = TRUE; - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); - gtk_tree_selection_selected_foreach (selection, - selection_check_foreach_cb, - &closure); - - g_assert (closure.num_selected == 0 || !(closure.all_files && closure.all_folders)); - - if (num_selected) - *num_selected = closure.num_selected; - - if (all_files) - *all_files = closure.all_files; - - if (all_folders) - *all_folders = closure.all_folders; -} - -static gboolean -file_is_recent_uri (GFile *file) -{ - GFile *recent; - gboolean same; - - recent = g_file_new_for_uri ("recent:///"); - same = g_file_equal (file, recent); - g_object_unref (recent); - - return same; -} - -static void -places_sidebar_open_location_cb (GtkPlacesSidebar *sidebar, GFile *location, GtkPlacesOpenFlags open_flags, GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - gboolean clear_entry; - - /* In the Save modes, we want to preserve what the uesr typed in the filename - * entry, so that he may choose another folder without erasing his typed name. - */ - if (priv->location_entry - && !(priv->action == GTK_FILE_CHOOSER_ACTION_SAVE - || priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)) - clear_entry = TRUE; - else - clear_entry = FALSE; - - /* FIXME-places-sidebar: - * - * GtkPlacesSidebar doesn't have a Search item anymore. We should put that function in a toolbar-like button, like - * in Nautilus, and do operation_mode_set (impl, OPERATION_MODE_SEARCH); - */ - - if (file_is_recent_uri (location)) - operation_mode_set (impl, OPERATION_MODE_RECENT); - else - change_folder_and_display_error (impl, location, clear_entry); -} - -/* Callback used when the places sidebar needs us to display an error message */ -static void -places_sidebar_show_error_message_cb (GtkPlacesSidebar *sidebar, - const char *primary, - const char *secondary, - GtkFileChooserDefault *impl) -{ - error_message (impl, primary, secondary); -} - -static gboolean -key_is_left_or_right (GdkEventKey *event) -{ - guint modifiers; - - modifiers = gtk_accelerator_get_default_mod_mask (); - - return ((event->keyval == GDK_KEY_Right - || event->keyval == GDK_KEY_KP_Right - || event->keyval == GDK_KEY_Left - || event->keyval == GDK_KEY_KP_Left) - && (event->state & modifiers) == 0); -} - -/* Handles key press events on the file list, so that we can trap Enter to - * activate the default button on our own. Also, checks to see if '/' has been - * pressed. - */ -static gboolean -browse_files_key_press_event_cb (GtkWidget *widget, - GdkEventKey *event, - gpointer data) -{ - GtkFileChooserDefault *impl = (GtkFileChooserDefault *) data; - GtkFileChooserDefaultPrivate *priv = impl->priv; - GdkModifierType no_text_input_mask; - - no_text_input_mask = - gtk_widget_get_modifier_mask (widget, GDK_MODIFIER_INTENT_NO_TEXT_INPUT); - - if ((event->keyval == GDK_KEY_slash - || event->keyval == GDK_KEY_KP_Divide -#ifdef G_OS_UNIX - || event->keyval == GDK_KEY_asciitilde -#endif - ) && !(event->state & no_text_input_mask)) - { - location_popup_handler (impl, event->string); - return TRUE; - } - - if (key_is_left_or_right (event)) - { - gtk_widget_grab_focus (priv->places_sidebar); - return TRUE; - } - - if ((event->keyval == GDK_KEY_Return - || event->keyval == GDK_KEY_ISO_Enter - || event->keyval == GDK_KEY_KP_Enter - || event->keyval == GDK_KEY_space - || event->keyval == GDK_KEY_KP_Space) - && !(event->state & gtk_accelerator_get_default_mod_mask ()) - && !(priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER || - priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)) - { - GtkWindow *window; - - window = get_toplevel (widget); - if (window) - { - GtkWidget *default_widget, *focus_widget; - - default_widget = gtk_window_get_default_widget (window); - focus_widget = gtk_window_get_focus (window); - - if (widget != default_widget && - !(widget == focus_widget && (!default_widget || !gtk_widget_get_sensitive (default_widget)))) - { - gtk_window_activate_default (window); - - return TRUE; - } - } - } - - return FALSE; -} - -/* Callback used when the file list's popup menu is detached */ -static void -popup_menu_detach_cb (GtkWidget *attach_widget, - GtkMenu *menu) -{ - GtkFileChooserDefault *impl = g_object_get_data (G_OBJECT (attach_widget), "GtkFileChooserDefault"); - GtkFileChooserDefaultPrivate *priv; - - g_assert (GTK_IS_FILE_CHOOSER_DEFAULT (impl)); - - priv = impl->priv; - - priv->browse_files_popup_menu = NULL; - priv->browse_files_popup_menu_add_shortcut_item = NULL; - priv->browse_files_popup_menu_hidden_files_item = NULL; - priv->browse_files_popup_menu_copy_file_location_item = NULL; -} - -/* Callback used from gtk_tree_selection_selected_foreach(); adds a bookmark for - * each selected item in the file list. - */ -static void -add_bookmark_foreach_cb (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) -{ - GtkFileChooserDefault *impl = (GtkFileChooserDefault *) data; - GtkFileChooserDefaultPrivate *priv = impl->priv; - GFile *file; - - gtk_tree_model_get (model, iter, - MODEL_COL_FILE, &file, - -1); - - _gtk_bookmarks_manager_insert_bookmark (priv->bookmarks_manager, file, 0, NULL); /* NULL-GError */ - - g_object_unref (file); -} - -/* Callback used when the "Add to Bookmarks" menu item is activated */ -static void -add_to_shortcuts_cb (GtkMenuItem *item, - GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkTreeSelection *selection; - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); - - gtk_tree_selection_selected_foreach (selection, - add_bookmark_foreach_cb, - impl); -} - -/* callback used to set data to clipboard */ -static void -copy_file_get_cb (GtkClipboard *clipboard, - GtkSelectionData *selection_data, - guint info, - gpointer data) -{ - GSList *selected_files = data; - - if (selected_files) - { - gint num_files = g_slist_length (selected_files); - gchar **uris; - gint i; - GSList *l; - - uris = g_new (gchar *, num_files + 1); - uris[num_files] = NULL; /* null terminator */ - - i = 0; - - for (l = selected_files; l; l = l->next) - { - GFile *file = (GFile *) l->data; - - if (info == SELECTION_URI) - uris[i] = g_file_get_uri (file); - else /* if (info == SELECTION_TEXT) - let this be the fallback */ - uris[i] = g_file_get_parse_name (file); - - i++; - } - - if (info == SELECTION_URI) - gtk_selection_data_set_uris (selection_data, uris); - else /* if (info == SELECTION_TEXT) - let this be the fallback */ - { - char *str = g_strjoinv (" ", uris); - gtk_selection_data_set_text (selection_data, str, -1); - g_free (str); - } - - g_strfreev (uris); - } -} - -/* callback used to clear the clipboard data */ -static void -copy_file_clear_cb (GtkClipboard *clipboard, - gpointer data) -{ - GSList *selected_files = data; - - g_slist_foreach (selected_files, (GFunc) g_object_unref, NULL); - g_slist_free (selected_files); -} - -/* Callback used when the "Copy file’s location" menu item is activated */ -static void -copy_file_location_cb (GtkMenuItem *item, - GtkFileChooserDefault *impl) -{ - GSList *selected_files = NULL; - - selected_files = search_get_selected_files (impl); - - if (selected_files) - { - GtkClipboard *clipboard; - GtkTargetList *target_list; - GtkTargetEntry *targets; - int n_targets; - - clipboard = gtk_widget_get_clipboard (GTK_WIDGET (impl), GDK_SELECTION_CLIPBOARD); - - target_list = gtk_target_list_new (NULL, 0); - gtk_target_list_add_text_targets (target_list, SELECTION_TEXT); - gtk_target_list_add_uri_targets (target_list, SELECTION_URI); - - targets = gtk_target_table_new_from_list (target_list, &n_targets); - gtk_target_list_unref (target_list); - - gtk_clipboard_set_with_data (clipboard, targets, n_targets, - copy_file_get_cb, - copy_file_clear_cb, - selected_files); - - gtk_target_table_free (targets, n_targets); - } -} - -/* Callback used when the "Visit this file" menu item is activated */ -static void -visit_file_cb (GtkMenuItem *item, - GtkFileChooserDefault *impl) -{ - GSList *files; - - files = search_get_selected_files (impl); - - /* Sigh, just use the first one */ - if (files) - { - GFile *file = files->data; - - gtk_file_chooser_default_select_file (GTK_FILE_CHOOSER (impl), file, NULL); /* NULL-GError */ - } - - g_slist_foreach (files, (GFunc) g_object_unref, NULL); - g_slist_free (files); -} - -/* callback used when the "Show Hidden Files" menu item is toggled */ -static void -show_hidden_toggled_cb (GtkCheckMenuItem *item, - GtkFileChooserDefault *impl) -{ - g_object_set (impl, - "show-hidden", gtk_check_menu_item_get_active (item), - NULL); -} - -/* Callback used when the "Show Size Column" menu item is toggled */ -static void -show_size_column_toggled_cb (GtkCheckMenuItem *item, - GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - priv->show_size_column = gtk_check_menu_item_get_active (item); - - gtk_tree_view_column_set_visible (priv->list_size_column, - priv->show_size_column); -} - -/* Shows an error dialog about not being able to select a dragged file */ -static void -error_selecting_dragged_file_dialog (GtkFileChooserDefault *impl, - GFile *file, - GError *error) -{ - error_dialog (impl, - _("Could not select file"), - file, error); -} - -static void -file_list_drag_data_select_uris (GtkFileChooserDefault *impl, - gchar **uris) -{ - int i; - char *uri; - GtkFileChooser *chooser = GTK_FILE_CHOOSER (impl); - - for (i = 1; uris[i]; i++) - { - GFile *file; - GError *error = NULL; - - uri = uris[i]; - file = g_file_new_for_uri (uri); - - gtk_file_chooser_default_select_file (chooser, file, &error); - if (error) - error_selecting_dragged_file_dialog (impl, file, error); - - g_object_unref (file); - } -} - -struct FileListDragData -{ - GtkFileChooserDefault *impl; - gchar **uris; - GFile *file; -}; - -static void -file_list_drag_data_received_get_info_cb (GCancellable *cancellable, - GFileInfo *info, - const GError *error, - gpointer user_data) -{ - gboolean cancelled = g_cancellable_is_cancelled (cancellable); - struct FileListDragData *data = user_data; - GtkFileChooser *chooser = GTK_FILE_CHOOSER (data->impl); - GtkFileChooserDefaultPrivate *priv = data->impl->priv; - - if (cancellable != priv->file_list_drag_data_received_cancellable) - goto out; - - priv->file_list_drag_data_received_cancellable = NULL; - - if (cancelled || error) - goto out; - - if ((priv->action == GTK_FILE_CHOOSER_ACTION_OPEN || - priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) && - data->uris[1] == 0 && !error && _gtk_file_info_consider_as_directory (info)) - change_folder_and_display_error (data->impl, data->file, FALSE); - else - { - GError *error = NULL; - - gtk_file_chooser_default_unselect_all (chooser); - gtk_file_chooser_default_select_file (chooser, data->file, &error); - if (error) - error_selecting_dragged_file_dialog (data->impl, data->file, error); - else - browse_files_center_selected_row (data->impl); - } - - if (priv->select_multiple) - file_list_drag_data_select_uris (data->impl, data->uris); - -out: - g_object_unref (data->impl); - g_strfreev (data->uris); - g_object_unref (data->file); - g_free (data); - - g_object_unref (cancellable); -} - -static void -file_list_drag_data_received_cb (GtkWidget *widget, - GdkDragContext *context, - gint x, - gint y, - GtkSelectionData *selection_data, - guint info, - guint time_, - gpointer data) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (data); - GtkFileChooserDefaultPrivate *priv = impl->priv; - gchar **uris; - char *uri; - GFile *file; - - /* Allow only drags from other widgets; see bug #533891. */ - if (gtk_drag_get_source_widget (context) == widget) - { - g_signal_stop_emission_by_name (widget, "drag-data-received"); - return; - } - - /* Parse the text/uri-list string, navigate to the first one */ - uris = gtk_selection_data_get_uris (selection_data); - if (uris && uris[0]) - { - struct FileListDragData *data; - - uri = uris[0]; - file = g_file_new_for_uri (uri); - - data = g_new0 (struct FileListDragData, 1); - data->impl = g_object_ref (impl); - data->uris = uris; - data->file = file; - - if (priv->file_list_drag_data_received_cancellable) - g_cancellable_cancel (priv->file_list_drag_data_received_cancellable); - - priv->file_list_drag_data_received_cancellable = - _gtk_file_system_get_info (priv->file_system, file, - "standard::type", - file_list_drag_data_received_get_info_cb, - data); - } - - g_signal_stop_emission_by_name (widget, "drag-data-received"); -} - -/* Don't do anything with the drag_drop signal */ -static gboolean -file_list_drag_drop_cb (GtkWidget *widget, - GdkDragContext *context, - gint x, - gint y, - guint time_, - GtkFileChooserDefault *impl) -{ - g_signal_stop_emission_by_name (widget, "drag-drop"); - return TRUE; -} - -/* Disable the normal tree drag motion handler, it makes it look like you're - dropping the dragged item onto a tree item */ -static gboolean -file_list_drag_motion_cb (GtkWidget *widget, - GdkDragContext *context, - gint x, - gint y, - guint time_, - GtkFileChooserDefault *impl) -{ - g_signal_stop_emission_by_name (widget, "drag-motion"); - return TRUE; -} - -/* Sensitizes the "Copy file’s location" and other context menu items if there is actually - * a selection active. - */ -static void -check_file_list_menu_sensitivity (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - gint num_selected; - gboolean all_files; - gboolean all_folders; - gboolean active; - - selection_check (impl, &num_selected, &all_files, &all_folders); - - active = (num_selected != 0); - - if (priv->browse_files_popup_menu_copy_file_location_item) - gtk_widget_set_sensitive (priv->browse_files_popup_menu_copy_file_location_item, active); - if (priv->browse_files_popup_menu_add_shortcut_item) - gtk_widget_set_sensitive (priv->browse_files_popup_menu_add_shortcut_item, active && all_folders); - if (priv->browse_files_popup_menu_visit_file_item) - gtk_widget_set_sensitive (priv->browse_files_popup_menu_visit_file_item, active); -} - -static GtkWidget * -file_list_add_menu_item (GtkFileChooserDefault *impl, - const char *mnemonic_label, - GCallback callback) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkWidget *item; - - item = gtk_menu_item_new_with_mnemonic (mnemonic_label); - g_signal_connect (item, "activate", callback, impl); - gtk_widget_show (item); - gtk_menu_shell_append (GTK_MENU_SHELL (priv->browse_files_popup_menu), item); - - return item; -} - -static GtkWidget * -file_list_add_check_menu_item (GtkFileChooserDefault *impl, - const char *mnemonic_label, - GCallback callback) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkWidget *item; - - item = gtk_check_menu_item_new_with_mnemonic (mnemonic_label); - g_signal_connect (item, "toggled", callback, impl); - gtk_widget_show (item); - gtk_menu_shell_append (GTK_MENU_SHELL (priv->browse_files_popup_menu), item); - - return item; -} - -/* Constructs the popup menu for the file list if needed */ -static void -file_list_build_popup_menu (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkWidget *item; - - if (priv->browse_files_popup_menu) - return; - - priv->browse_files_popup_menu = gtk_menu_new (); - gtk_menu_attach_to_widget (GTK_MENU (priv->browse_files_popup_menu), - priv->browse_files_tree_view, - popup_menu_detach_cb); - - priv->browse_files_popup_menu_visit_file_item = file_list_add_menu_item (impl, _("_Visit File"), - G_CALLBACK (visit_file_cb)); - - priv->browse_files_popup_menu_copy_file_location_item = file_list_add_menu_item (impl, _("_Copy Location"), - G_CALLBACK (copy_file_location_cb)); - - priv->browse_files_popup_menu_add_shortcut_item = file_list_add_menu_item (impl, _("_Add to Bookmarks"), - G_CALLBACK (add_to_shortcuts_cb)); - - item = gtk_separator_menu_item_new (); - gtk_widget_show (item); - gtk_menu_shell_append (GTK_MENU_SHELL (priv->browse_files_popup_menu), item); - - priv->browse_files_popup_menu_hidden_files_item = file_list_add_check_menu_item (impl, _("Show _Hidden Files"), - G_CALLBACK (show_hidden_toggled_cb)); - - priv->browse_files_popup_menu_size_column_item = file_list_add_check_menu_item (impl, _("Show _Size Column"), - G_CALLBACK (show_size_column_toggled_cb)); - - check_file_list_menu_sensitivity (impl); -} - -/* Updates the popup menu for the file list, creating it if necessary */ -static void -file_list_update_popup_menu (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - file_list_build_popup_menu (impl); - - /* The sensitivity of the Add to Bookmarks item is set in - * bookmarks_check_add_sensitivity() - */ - - /* 'Visit this file' */ - gtk_widget_set_visible (priv->browse_files_popup_menu_visit_file_item, (priv->operation_mode != OPERATION_MODE_BROWSE)); - - /* 'Show Hidden Files' */ - g_signal_handlers_block_by_func (priv->browse_files_popup_menu_hidden_files_item, - G_CALLBACK (show_hidden_toggled_cb), impl); - gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (priv->browse_files_popup_menu_hidden_files_item), - priv->show_hidden); - g_signal_handlers_unblock_by_func (priv->browse_files_popup_menu_hidden_files_item, - G_CALLBACK (show_hidden_toggled_cb), impl); - - /* 'Show Size Column' */ - g_signal_handlers_block_by_func (priv->browse_files_popup_menu_size_column_item, - G_CALLBACK (show_size_column_toggled_cb), impl); - gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (priv->browse_files_popup_menu_size_column_item), - priv->show_size_column); - g_signal_handlers_unblock_by_func (priv->browse_files_popup_menu_size_column_item, - G_CALLBACK (show_size_column_toggled_cb), impl); -} - -static void -popup_position_func (GtkMenu *menu, - gint *x, - gint *y, - gboolean *push_in, - gpointer user_data) -{ - GtkAllocation allocation; - GtkWidget *widget = GTK_WIDGET (user_data); - GdkScreen *screen = gtk_widget_get_screen (widget); - GtkRequisition req; - gint monitor_num; - GdkRectangle monitor; - - g_return_if_fail (gtk_widget_get_realized (widget)); - - gdk_window_get_origin (gtk_widget_get_window (widget), x, y); - - gtk_widget_get_preferred_size (GTK_WIDGET (menu), - &req, NULL); - - gtk_widget_get_allocation (widget, &allocation); - *x += (allocation.width - req.width) / 2; - *y += (allocation.height - req.height) / 2; - - monitor_num = gdk_screen_get_monitor_at_point (screen, *x, *y); - gtk_menu_set_monitor (menu, monitor_num); - gdk_screen_get_monitor_workarea (screen, monitor_num, &monitor); - - *x = CLAMP (*x, monitor.x, monitor.x + MAX (0, monitor.width - req.width)); - *y = CLAMP (*y, monitor.y, monitor.y + MAX (0, monitor.height - req.height)); - - *push_in = FALSE; -} - -static void -file_list_popup_menu (GtkFileChooserDefault *impl, - GdkEventButton *event) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - file_list_update_popup_menu (impl); - if (event) - gtk_menu_popup (GTK_MENU (priv->browse_files_popup_menu), - NULL, NULL, NULL, NULL, - event->button, event->time); - else - { - gtk_menu_popup (GTK_MENU (priv->browse_files_popup_menu), - NULL, NULL, - popup_position_func, priv->browse_files_tree_view, - 0, GDK_CURRENT_TIME); - gtk_menu_shell_select_first (GTK_MENU_SHELL (priv->browse_files_popup_menu), - FALSE); - } - -} - -/* Callback used for the GtkWidget::popup-menu signal of the file list */ -static gboolean -list_popup_menu_cb (GtkWidget *widget, - GtkFileChooserDefault *impl) -{ - file_list_popup_menu (impl, NULL); - return TRUE; -} - -/* Callback used when a button is pressed on the file list. We trap button 3 to - * bring up a popup menu. - */ -static gboolean -list_button_press_event_cb (GtkWidget *widget, - GdkEventButton *event, - GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - static gboolean in_press = FALSE; - - if (in_press) - return FALSE; - - if (!gdk_event_triggers_context_menu ((GdkEvent *) event)) - return FALSE; - - in_press = TRUE; - gtk_widget_event (priv->browse_files_tree_view, (GdkEvent *) event); - in_press = FALSE; - - file_list_popup_menu (impl, event); - return TRUE; -} - -typedef struct { - OperationMode operation_mode; - gint general_column; - gint model_column; -} ColumnMap; - -/* Sets the sort column IDs for the file list; needs to be done whenever we - * change the model on the treeview. - */ -static void -file_list_set_sort_column_ids (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - gtk_tree_view_column_set_sort_column_id (priv->list_name_column, MODEL_COL_NAME); - gtk_tree_view_column_set_sort_column_id (priv->list_mtime_column, MODEL_COL_MTIME); - gtk_tree_view_column_set_sort_column_id (priv->list_size_column, MODEL_COL_SIZE); -} - -static gboolean -file_list_query_tooltip_cb (GtkWidget *widget, - gint x, - gint y, - gboolean keyboard_tip, - GtkTooltip *tooltip, - gpointer user_data) -{ - GtkFileChooserDefault *impl = user_data; - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkTreeModel *model; - GtkTreePath *path; - GtkTreeIter iter; - GFile *file; - gchar *filename; - - if (priv->operation_mode == OPERATION_MODE_BROWSE) - return FALSE; - - - if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (priv->browse_files_tree_view), - &x, &y, - keyboard_tip, - &model, &path, &iter)) - return FALSE; - - gtk_tree_model_get (model, &iter, - MODEL_COL_FILE, &file, - -1); - - if (file == NULL) - { - gtk_tree_path_free (path); - return FALSE; - } - - filename = g_file_get_path (file); - gtk_tooltip_set_text (tooltip, filename); - gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (priv->browse_files_tree_view), - tooltip, - path); - - g_free (filename); - g_object_unref (file); - gtk_tree_path_free (path); - - return TRUE; -} - -static void -set_icon_cell_renderer_fixed_size (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - gint xpad, ypad; - - gtk_cell_renderer_get_padding (priv->list_pixbuf_renderer, &xpad, &ypad); - gtk_cell_renderer_set_fixed_size (priv->list_pixbuf_renderer, - xpad * 2 + priv->icon_size, - ypad * 2 + priv->icon_size); -} - - -static void -location_entry_create (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (!priv->location_entry) - priv->location_entry = _gtk_file_chooser_entry_new (TRUE); - - _gtk_file_chooser_entry_set_local_only (GTK_FILE_CHOOSER_ENTRY (priv->location_entry), priv->local_only); - _gtk_file_chooser_entry_set_action (GTK_FILE_CHOOSER_ENTRY (priv->location_entry), priv->action); - gtk_entry_set_width_chars (GTK_ENTRY (priv->location_entry), 45); - gtk_entry_set_activates_default (GTK_ENTRY (priv->location_entry), TRUE); -} - -/* Creates the widgets specific to Save mode */ -static void -save_widgets_create (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkWidget *vbox; - GtkWidget *widget; - - if (priv->save_widgets != NULL) - return; - - location_switch_to_path_bar (impl); - - vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); - - priv->save_widgets_table = gtk_grid_new (); - gtk_box_pack_start (GTK_BOX (vbox), priv->save_widgets_table, FALSE, FALSE, 0); - gtk_widget_show (priv->save_widgets_table); - gtk_grid_set_row_spacing (GTK_GRID (priv->save_widgets_table), 12); - gtk_grid_set_column_spacing (GTK_GRID (priv->save_widgets_table), 12); - - /* Label */ - - widget = gtk_label_new_with_mnemonic (_("_Name:")); - gtk_widget_set_halign (widget, GTK_ALIGN_START); - gtk_widget_set_valign (widget, GTK_ALIGN_CENTER); - gtk_grid_attach (GTK_GRID (priv->save_widgets_table), widget, 0, 0, 1, 1); - gtk_widget_show (widget); - - /* Location entry */ - - location_entry_create (impl); - gtk_widget_set_hexpand (priv->location_entry, TRUE); - gtk_grid_attach (GTK_GRID (priv->save_widgets_table), priv->location_entry, 1, 0, 1, 1); - gtk_widget_show (priv->location_entry); - gtk_label_set_mnemonic_widget (GTK_LABEL (widget), priv->location_entry); - - /* Folder combo */ - priv->save_folder_label = gtk_label_new (NULL); - gtk_widget_set_halign (priv->save_folder_label, GTK_ALIGN_START); - gtk_widget_set_valign (priv->save_folder_label, GTK_ALIGN_CENTER); - gtk_grid_attach (GTK_GRID (priv->save_widgets_table), priv->save_folder_label, 0, 1, 1, 1); - gtk_widget_show (priv->save_folder_label); - - priv->save_widgets = vbox; - gtk_box_pack_start (GTK_BOX (impl), priv->save_widgets, FALSE, FALSE, 0); - gtk_box_reorder_child (GTK_BOX (impl), priv->save_widgets, 0); - gtk_widget_show (priv->save_widgets); -} - -/* Destroys the widgets specific to Save mode */ -static void -save_widgets_destroy (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (priv->save_widgets == NULL) - return; - - gtk_widget_destroy (priv->save_widgets); - priv->save_widgets = NULL; - priv->save_widgets_table = NULL; - priv->location_entry = NULL; - priv->save_folder_label = NULL; -} - -/* Turns on the path bar widget. Can be called even if we are already in that - * mode. - */ -static void -location_switch_to_path_bar (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (priv->location_entry) - { - gtk_widget_destroy (priv->location_entry); - priv->location_entry = NULL; - } - - gtk_widget_hide (priv->location_entry_box); -} - -/* Turns on the location entry. Can be called even if we are already in that - * mode. - */ -static void -location_switch_to_filename_entry (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - /* when in search or recent files mode, we are not showing the - * location_entry_box container, so there's no point in switching - * to it. - */ - if (priv->operation_mode == OPERATION_MODE_SEARCH || - priv->operation_mode == OPERATION_MODE_RECENT) - return; - - /* Box */ - - gtk_widget_show (priv->location_entry_box); - - /* Entry */ - - if (!priv->location_entry) - { - location_entry_create (impl); - gtk_box_pack_start (GTK_BOX (priv->location_entry_box), priv->location_entry, TRUE, TRUE, 0); - gtk_label_set_mnemonic_widget (GTK_LABEL (priv->location_label), priv->location_entry); - } - - /* Configure the entry */ - - _gtk_file_chooser_entry_set_base_folder (GTK_FILE_CHOOSER_ENTRY (priv->location_entry), priv->current_folder); - - /* Done */ - - gtk_widget_show (priv->location_entry); - gtk_widget_grab_focus (priv->location_entry); -} - -/* Sets a new location mode. set_buttons determines whether the toggle button - * for the mode will also be changed. - */ -static void -location_mode_set (GtkFileChooserDefault *impl, - LocationMode new_mode, - gboolean set_button) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN || - priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) - { - GtkWindow *toplevel; - GtkWidget *current_focus; - gboolean button_active; - gboolean switch_to_file_list; - - switch (new_mode) - { - case LOCATION_MODE_PATH_BAR: - button_active = FALSE; - - /* The location_entry will disappear when we switch to path bar mode. So, - * we'll focus the file list in that case, to avoid having a window with - * no focused widget. - */ - toplevel = get_toplevel (GTK_WIDGET (impl)); - switch_to_file_list = FALSE; - if (toplevel) - { - current_focus = gtk_window_get_focus (toplevel); - if (!current_focus || current_focus == priv->location_entry) - switch_to_file_list = TRUE; - } - - location_switch_to_path_bar (impl); - - if (switch_to_file_list) - gtk_widget_grab_focus (priv->browse_files_tree_view); - - break; - - case LOCATION_MODE_FILENAME_ENTRY: - button_active = TRUE; - location_switch_to_filename_entry (impl); - break; - - default: - g_assert_not_reached (); - return; - } - - if (set_button) - { - g_signal_handlers_block_by_func (priv->location_button, - G_CALLBACK (location_button_toggled_cb), impl); - - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->location_button), button_active); - - g_signal_handlers_unblock_by_func (priv->location_button, - G_CALLBACK (location_button_toggled_cb), impl); - } - } - - priv->location_mode = new_mode; -} - -static void -location_toggle_popup_handler (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - /* when in search or recent files mode, we are not showing the - * location_entry_box container, so there's no point in switching - * to it. - */ - if (priv->operation_mode == OPERATION_MODE_SEARCH || - priv->operation_mode == OPERATION_MODE_RECENT) - return; - - /* If the file entry is not visible, show it. - * If it is visible, turn it off only if it is focused. Otherwise, switch to the entry. - */ - if (priv->location_mode == LOCATION_MODE_PATH_BAR) - { - location_mode_set (impl, LOCATION_MODE_FILENAME_ENTRY, TRUE); - } - else if (priv->location_mode == LOCATION_MODE_FILENAME_ENTRY) - { - if (gtk_widget_has_focus (priv->location_entry)) - { - location_mode_set (impl, LOCATION_MODE_PATH_BAR, TRUE); - } - else - { - gtk_widget_grab_focus (priv->location_entry); - } - } -} - -/* Callback used when one of the location mode buttons is toggled */ -static void -location_button_toggled_cb (GtkToggleButton *toggle, - GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - gboolean is_active; - LocationMode new_mode; - - is_active = gtk_toggle_button_get_active (toggle); - - if (is_active) - { - g_assert (priv->location_mode == LOCATION_MODE_PATH_BAR); - new_mode = LOCATION_MODE_FILENAME_ENTRY; - } - else - { - g_assert (priv->location_mode == LOCATION_MODE_FILENAME_ENTRY); - new_mode = LOCATION_MODE_PATH_BAR; - } - - location_mode_set (impl, new_mode, FALSE); -} - -typedef enum { - PATH_BAR_FOLDER_PATH, - PATH_BAR_SELECT_A_FOLDER, - PATH_BAR_ERROR_NO_FILENAME, - PATH_BAR_ERROR_NO_FOLDER, - PATH_BAR_RECENTLY_USED, - PATH_BAR_SEARCH -} PathBarMode; - -/* Sets the info bar to show the appropriate informational or warning message */ -static void -info_bar_set (GtkFileChooserDefault *impl, PathBarMode mode) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - char *str; - gboolean free_str; - GtkMessageType message_type; - - free_str = FALSE; - - switch (mode) - { - case PATH_BAR_SELECT_A_FOLDER: - str = g_strconcat ("<i>", _("Please select a folder below"), "</i>", NULL); - free_str = TRUE; - message_type = GTK_MESSAGE_OTHER; - break; - - case PATH_BAR_ERROR_NO_FILENAME: - str = _("Please type a file name"); - message_type = GTK_MESSAGE_WARNING; - break; - - case PATH_BAR_ERROR_NO_FOLDER: - str = _("Please select a folder below"); - message_type = GTK_MESSAGE_WARNING; - break; - - default: - g_assert_not_reached (); - return; - } - - gtk_info_bar_set_message_type (GTK_INFO_BAR (priv->browse_select_a_folder_info_bar), message_type); - gtk_image_set_from_icon_name (GTK_IMAGE (priv->browse_select_a_folder_icon), - (message_type == GTK_MESSAGE_WARNING) ? "dialog-warning-symbolic" : "folder-symbolic", - GTK_ICON_SIZE_MENU); - gtk_label_set_markup (GTK_LABEL (priv->browse_select_a_folder_label), str); - - if (free_str) - g_free (str); -} - -/* Sets the path bar's mode to show a label, the actual folder path, or a - * warning message. You may call this function with PATH_BAR_ERROR_* directly - * if the pathbar is already showing the widgets you expect; otherwise, call - * path_bar_update() instead to set the appropriate widgets automatically. - */ -static void -path_bar_set_mode (GtkFileChooserDefault *impl, PathBarMode mode) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - gboolean path_bar_visible = FALSE; - gboolean special_mode_widgets_visible = FALSE; - gboolean info_bar_visible = FALSE; - gboolean create_folder_visible = FALSE; - - char *tmp; - - switch (mode) - { - case PATH_BAR_FOLDER_PATH: - path_bar_visible = TRUE; - break; - - case PATH_BAR_SELECT_A_FOLDER: - case PATH_BAR_ERROR_NO_FILENAME: - case PATH_BAR_ERROR_NO_FOLDER: - info_bar_set (impl, mode); - info_bar_visible = TRUE; - break; - - case PATH_BAR_RECENTLY_USED: - gtk_image_set_from_icon_name (GTK_IMAGE (priv->browse_special_mode_icon), "document-open-recent", GTK_ICON_SIZE_BUTTON); - - tmp = g_strdup_printf ("<b>%s</b>", _("Recently Used")); - gtk_label_set_markup (GTK_LABEL (priv->browse_special_mode_label), tmp); - g_free (tmp); - - special_mode_widgets_visible = TRUE; - break; - - case PATH_BAR_SEARCH: - gtk_image_set_from_icon_name (GTK_IMAGE (priv->browse_special_mode_icon), "edit-find-symbolic", GTK_ICON_SIZE_BUTTON); - - tmp = g_strdup_printf ("<b>%s</b>", _("Search:")); - gtk_label_set_markup (GTK_LABEL (priv->browse_special_mode_label), tmp); - g_free (tmp); - - special_mode_widgets_visible = TRUE; - break; - - default: - g_assert_not_reached (); - } - - gtk_widget_set_visible (priv->browse_path_bar, path_bar_visible); - gtk_widget_set_visible (priv->browse_special_mode_icon, special_mode_widgets_visible); - gtk_widget_set_visible (priv->browse_special_mode_label, special_mode_widgets_visible); - gtk_widget_set_visible (priv->browse_select_a_folder_info_bar, info_bar_visible); - - if (path_bar_visible) - { - if (priv->create_folders - && priv->action != GTK_FILE_CHOOSER_ACTION_OPEN - && priv->operation_mode != OPERATION_MODE_RECENT) - create_folder_visible = TRUE; - } - - gtk_widget_set_visible (priv->browse_new_folder_button, create_folder_visible); -} - - -static GObject* -gtk_file_chooser_default_constructor (GType type, - guint n_construct_properties, - GObjectConstructParam *construct_params) -{ - GtkFileChooserDefault *impl; - GtkFileChooserDefaultPrivate *priv; - GObject *object; - - profile_start ("start", NULL); - - object = G_OBJECT_CLASS (_gtk_file_chooser_default_parent_class)->constructor (type, - n_construct_properties, - construct_params); - impl = GTK_FILE_CHOOSER_DEFAULT (object); - priv = impl->priv; - - g_assert (priv->file_system); - - update_appearance (impl); - - profile_end ("end", NULL); - - return object; -} - -/* Sets the extra_widget by packing it in the appropriate place */ -static void -set_extra_widget (GtkFileChooserDefault *impl, - GtkWidget *extra_widget) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (extra_widget) - { - g_object_ref (extra_widget); - /* FIXME: is this right ? */ - gtk_widget_show (extra_widget); - } - - if (priv->extra_widget) - { - gtk_container_remove (GTK_CONTAINER (priv->extra_align), priv->extra_widget); - g_object_unref (priv->extra_widget); - } - - priv->extra_widget = extra_widget; - if (priv->extra_widget) - { - gtk_container_add (GTK_CONTAINER (priv->extra_align), priv->extra_widget); - gtk_widget_show (priv->extra_align); - } - else - gtk_widget_hide (priv->extra_align); -} - -static void -switch_to_home_dir (GtkFileChooserDefault *impl) -{ - const gchar *home = g_get_home_dir (); - GFile *home_file; - - if (home == NULL) - return; - - home_file = g_file_new_for_path (home); - - gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (impl), home_file, NULL); /* NULL-GError */ - - g_object_unref (home_file); -} - -static void -set_local_only (GtkFileChooserDefault *impl, - gboolean local_only) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (local_only != priv->local_only) - { - priv->local_only = local_only; - - if (priv->location_entry) - _gtk_file_chooser_entry_set_local_only (GTK_FILE_CHOOSER_ENTRY (priv->location_entry), local_only); - - gtk_places_sidebar_set_local_only (GTK_PLACES_SIDEBAR (priv->places_sidebar), local_only); - - if (local_only && priv->current_folder && - !_gtk_file_has_native_path (priv->current_folder)) - { - /* If we are pointing to a non-local folder, make an effort to change - * back to a local folder, but it's really up to the app to not cause - * such a situation, so we ignore errors. - */ - switch_to_home_dir (impl); - } - } -} - -/* Sets the file chooser to multiple selection mode */ -static void -set_select_multiple (GtkFileChooserDefault *impl, - gboolean select_multiple, - gboolean property_notify) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkTreeSelection *selection; - GtkSelectionMode mode; - - if (select_multiple == priv->select_multiple) - return; - - mode = select_multiple ? GTK_SELECTION_MULTIPLE : GTK_SELECTION_SINGLE; - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); - gtk_tree_selection_set_mode (selection, mode); - - gtk_tree_view_set_rubber_banding (GTK_TREE_VIEW (priv->browse_files_tree_view), select_multiple); - - priv->select_multiple = select_multiple; - g_object_notify (G_OBJECT (impl), "select-multiple"); - - check_preview_change (impl); -} - -static void -set_file_system_backend (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - profile_start ("start for backend", "default"); - - priv->file_system = _gtk_file_system_new (); - - profile_end ("end", NULL); -} - -static void -unset_file_system_backend (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - g_object_unref (priv->file_system); - - priv->file_system = NULL; -} - -/* Saves the widgets around the pathbar so they can be reparented later - * in the correct place. This function must be called paired with - * restore_path_bar(). - */ -static void -save_path_bar (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkWidget *parent; - - g_object_ref (priv->browse_path_bar_hbox); - - parent = gtk_widget_get_parent (priv->browse_path_bar_hbox); - if (parent) - gtk_container_remove (GTK_CONTAINER (parent), priv->browse_path_bar_hbox); -} - -/* Reparents the path bar and the "Create folder" button to the right place: - * Above the file list in Open mode, or to the right of the "Save in folder:" - * label in Save mode. The save_path_bar() function must be called before this - * one. - */ -static void -restore_path_bar (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN - || priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) - { - gtk_box_pack_start (GTK_BOX (priv->browse_header_box), priv->browse_path_bar_hbox, FALSE, FALSE, 0); - gtk_box_reorder_child (GTK_BOX (priv->browse_header_box), priv->browse_path_bar_hbox, 0); - } - else if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE - || priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) - { - gtk_widget_set_hexpand (priv->browse_path_bar_hbox, TRUE); - gtk_grid_attach (GTK_GRID (priv->save_widgets_table), priv->browse_path_bar_hbox, 1, 1, 1, 1); - } - else - g_assert_not_reached (); - - g_object_unref (priv->browse_path_bar_hbox); -} - -/* Takes the folder stored in a row in the recent_model, and puts it in the pathbar */ -static void -put_recent_folder_in_pathbar (GtkFileChooserDefault *impl, GtkTreeIter *iter) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GFile *file; - - gtk_tree_model_get (GTK_TREE_MODEL (priv->recent_model), iter, - MODEL_COL_FILE, &file, - -1); - _gtk_path_bar_set_file (GTK_PATH_BAR (priv->browse_path_bar), file, FALSE); - g_object_unref (file); -} - -/* Sets the pathbar in the appropriate mode according to the current operation mode and action. This is the central function for - * dealing with the pathbar's widgets; as long as impl->action and impl->operation_mode are set correctly, then calling this - * function will update all the pathbar's widgets. - */ -static void -path_bar_update (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - PathBarMode mode; - - switch (priv->operation_mode) - { - case OPERATION_MODE_BROWSE: - mode = PATH_BAR_FOLDER_PATH; - break; - - case OPERATION_MODE_RECENT: - if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) - { - GtkTreeSelection *selection; - gboolean have_selected; - GtkTreeIter iter; - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); - - /* Save mode means single-selection mode, so the following is valid */ - have_selected = gtk_tree_selection_get_selected (selection, NULL, &iter); - - if (have_selected) - { - mode = PATH_BAR_FOLDER_PATH; - put_recent_folder_in_pathbar (impl, &iter); - } - else - mode = PATH_BAR_SELECT_A_FOLDER; - } - else - mode = PATH_BAR_RECENTLY_USED; - - break; - - case OPERATION_MODE_SEARCH: - mode = PATH_BAR_SEARCH; - break; - - default: - g_assert_not_reached (); - return; - } - - path_bar_set_mode (impl, mode); -} - -static void -operation_mode_discard_search_widgets (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (priv->search_hbox) - { - gtk_widget_destroy (priv->search_hbox); - - priv->search_hbox = NULL; - priv->search_entry = NULL; - } -} - -/* Stops running operations like populating the browse model, searches, and the recent-files model */ -static void -operation_mode_stop (GtkFileChooserDefault *impl, OperationMode mode) -{ - switch (mode) - { - case OPERATION_MODE_BROWSE: - stop_loading_and_clear_list_model (impl, TRUE); - break; - - case OPERATION_MODE_SEARCH: - search_stop_searching (impl, FALSE); - search_clear_model (impl, TRUE); - - operation_mode_discard_search_widgets (impl); - break; - - case OPERATION_MODE_RECENT: - recent_stop_loading (impl); - recent_clear_model (impl, TRUE); - break; - - default: - g_assert_not_reached (); - } -} - -static void -operation_mode_set_browse (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - path_bar_update (impl); - - if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN || - priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) - { - gtk_widget_show (priv->location_button); - location_mode_set (impl, priv->location_mode, TRUE); - - if (priv->location_mode == LOCATION_MODE_FILENAME_ENTRY) - gtk_widget_show (priv->location_entry_box); - } -} - -static void -operation_mode_set_search (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - g_assert (priv->search_hbox == NULL); - g_assert (priv->search_entry == NULL); - g_assert (priv->search_model == NULL); - - search_setup_widgets (impl); -} - -static void -operation_mode_set_recent (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - path_bar_update (impl); - - /* Hide the location widgets temporarily */ - if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN || - priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) - { - gtk_widget_hide (priv->location_button); - gtk_widget_hide (priv->location_entry_box); - } - - recent_start_loading (impl); -} - -static void -operation_mode_set (GtkFileChooserDefault *impl, OperationMode mode) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GFile *file; - - operation_mode_stop (impl, priv->operation_mode); - - priv->operation_mode = mode; - - switch (priv->operation_mode) - { - case OPERATION_MODE_BROWSE: - operation_mode_set_browse (impl); - break; - - case OPERATION_MODE_SEARCH: - operation_mode_set_search (impl); - break; - - case OPERATION_MODE_RECENT: - operation_mode_set_recent (impl); - file = g_file_new_for_uri ("recent:///"); - gtk_places_sidebar_set_location (GTK_PLACES_SIDEBAR (priv->places_sidebar), file); - g_object_unref (file); - break; - - default: - g_assert_not_reached (); - return; - } -} - -/* This function is basically a do_all function. - * - * It sets the visibility on all the widgets based on the current state, and - * moves the custom_widget if needed. - */ -static void -update_appearance (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - save_path_bar (impl); - - if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE || - priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) - { - const char *text; - - gtk_widget_hide (priv->location_button); - save_widgets_create (impl); - - if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) - text = _("Save in folder:"); - else - text = _("Create in folder:"); - - gtk_label_set_text (GTK_LABEL (priv->save_folder_label), text); - - if (priv->select_multiple) - { - g_warning ("Save mode cannot be set in conjunction with multiple selection mode. " - "Re-setting to single selection mode."); - set_select_multiple (impl, FALSE, TRUE); - } - } - else if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN || - priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) - { - gtk_widget_show (priv->location_button); - save_widgets_destroy (impl); - location_mode_set (impl, priv->location_mode, TRUE); - } - - if (priv->location_entry) - _gtk_file_chooser_entry_set_action (GTK_FILE_CHOOSER_ENTRY (priv->location_entry), priv->action); - - restore_path_bar (impl); - path_bar_update (impl); - - /* This *is* needed; we need to redraw the file list because the "sensitivity" - * of files may change depending whether we are in a file or folder-only mode. - */ - gtk_widget_queue_draw (priv->browse_files_tree_view); - - emit_default_size_changed (impl); -} - -static void -gtk_file_chooser_default_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) - -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (object); - GtkFileChooserDefaultPrivate *priv = impl->priv; - - switch (prop_id) - { - case GTK_FILE_CHOOSER_PROP_ACTION: - { - GtkFileChooserAction action = g_value_get_enum (value); - - if (action != priv->action) - { - gtk_file_chooser_default_unselect_all (GTK_FILE_CHOOSER (impl)); - - if ((action == GTK_FILE_CHOOSER_ACTION_SAVE || - action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) - && priv->select_multiple) - { - g_warning ("Tried to change the file chooser action to SAVE or CREATE_FOLDER, but " - "this is not allowed in multiple selection mode. Resetting the file chooser " - "to single selection mode."); - set_select_multiple (impl, FALSE, TRUE); - } - priv->action = action; - update_cell_renderer_attributes (impl); - update_appearance (impl); - settings_load (impl); - } - } - break; - - case GTK_FILE_CHOOSER_PROP_FILTER: - set_current_filter (impl, g_value_get_object (value)); - break; - - case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY: - set_local_only (impl, g_value_get_boolean (value)); - break; - - case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET: - set_preview_widget (impl, g_value_get_object (value)); - break; - - case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE: - priv->preview_widget_active = g_value_get_boolean (value); - update_preview_widget_visibility (impl); - break; - - case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL: - priv->use_preview_label = g_value_get_boolean (value); - update_preview_widget_visibility (impl); - break; - - case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET: - set_extra_widget (impl, g_value_get_object (value)); - break; - - case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE: - { - gboolean select_multiple = g_value_get_boolean (value); - if ((priv->action == GTK_FILE_CHOOSER_ACTION_SAVE || - priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) - && select_multiple) - { - g_warning ("Tried to set the file chooser to multiple selection mode, but this is " - "not allowed in SAVE or CREATE_FOLDER modes. Ignoring the change and " - "leaving the file chooser in single selection mode."); - return; - } - - set_select_multiple (impl, select_multiple, FALSE); - } - break; - - case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN: - { - gboolean show_hidden = g_value_get_boolean (value); - if (show_hidden != priv->show_hidden) - { - priv->show_hidden = show_hidden; - - if (priv->browse_files_model) - _gtk_file_system_model_set_show_hidden (priv->browse_files_model, show_hidden); - } - } - break; - - case GTK_FILE_CHOOSER_PROP_DO_OVERWRITE_CONFIRMATION: - { - gboolean do_overwrite_confirmation = g_value_get_boolean (value); - priv->do_overwrite_confirmation = do_overwrite_confirmation; - } - break; - - case GTK_FILE_CHOOSER_PROP_CREATE_FOLDERS: - { - gboolean create_folders = g_value_get_boolean (value); - priv->create_folders = create_folders; - update_appearance (impl); - } - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -gtk_file_chooser_default_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (object); - GtkFileChooserDefaultPrivate *priv = impl->priv; - - switch (prop_id) - { - case GTK_FILE_CHOOSER_PROP_ACTION: - g_value_set_enum (value, priv->action); - break; - - case GTK_FILE_CHOOSER_PROP_FILTER: - g_value_set_object (value, priv->current_filter); - break; - - case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY: - g_value_set_boolean (value, priv->local_only); - break; - - case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET: - g_value_set_object (value, priv->preview_widget); - break; - - case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE: - g_value_set_boolean (value, priv->preview_widget_active); - break; - - case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL: - g_value_set_boolean (value, priv->use_preview_label); - break; - - case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET: - g_value_set_object (value, priv->extra_widget); - break; - - case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE: - g_value_set_boolean (value, priv->select_multiple); - break; - - case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN: - g_value_set_boolean (value, priv->show_hidden); - break; - - case GTK_FILE_CHOOSER_PROP_DO_OVERWRITE_CONFIRMATION: - g_value_set_boolean (value, priv->do_overwrite_confirmation); - break; - - case GTK_FILE_CHOOSER_PROP_CREATE_FOLDERS: - g_value_set_boolean (value, priv->create_folders); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -/* This cancels everything that may be going on in the background. */ -static void -cancel_all_operations (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GSList *l; - - pending_select_files_free (impl); - - if (priv->reload_icon_cancellables) - { - for (l = priv->reload_icon_cancellables; l; l = l->next) - { - GCancellable *cancellable = G_CANCELLABLE (l->data); - g_cancellable_cancel (cancellable); - } - g_slist_free (priv->reload_icon_cancellables); - priv->reload_icon_cancellables = NULL; - } - - if (priv->loading_shortcuts) - { - for (l = priv->loading_shortcuts; l; l = l->next) - { - GCancellable *cancellable = G_CANCELLABLE (l->data); - g_cancellable_cancel (cancellable); - } - g_slist_free (priv->loading_shortcuts); - priv->loading_shortcuts = NULL; - } - - if (priv->file_list_drag_data_received_cancellable) - { - g_cancellable_cancel (priv->file_list_drag_data_received_cancellable); - priv->file_list_drag_data_received_cancellable = NULL; - } - - if (priv->update_current_folder_cancellable) - { - g_cancellable_cancel (priv->update_current_folder_cancellable); - priv->update_current_folder_cancellable = NULL; - } - - if (priv->should_respond_get_info_cancellable) - { - g_cancellable_cancel (priv->should_respond_get_info_cancellable); - priv->should_respond_get_info_cancellable = NULL; - } - - if (priv->file_exists_get_info_cancellable) - { - g_cancellable_cancel (priv->file_exists_get_info_cancellable); - priv->file_exists_get_info_cancellable = NULL; - } - - if (priv->update_from_entry_cancellable) - { - g_cancellable_cancel (priv->update_from_entry_cancellable); - priv->update_from_entry_cancellable = NULL; - } - - if (priv->shortcuts_activate_iter_cancellable) - { - g_cancellable_cancel (priv->shortcuts_activate_iter_cancellable); - priv->shortcuts_activate_iter_cancellable = NULL; - } - - search_stop_searching (impl, TRUE); - recent_stop_loading (impl); -} - -/* Removes the settings signal handler. It's safe to call multiple times */ -static void -remove_settings_signal (GtkFileChooserDefault *impl, - GdkScreen *screen) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (priv->settings_signal_id) - { - GtkSettings *settings; - - settings = gtk_settings_get_for_screen (screen); - g_signal_handler_disconnect (settings, - priv->settings_signal_id); - priv->settings_signal_id = 0; - } -} - -static void -gtk_file_chooser_default_dispose (GObject *object) -{ - GtkFileChooserDefault *impl = (GtkFileChooserDefault *) object; - GtkFileChooserDefaultPrivate *priv = impl->priv; - - cancel_all_operations (impl); - - if (priv->extra_widget) - { - g_object_unref (priv->extra_widget); - priv->extra_widget = NULL; - } - - remove_settings_signal (impl, gtk_widget_get_screen (GTK_WIDGET (impl))); - - if (priv->bookmarks_manager) - { - _gtk_bookmarks_manager_free (priv->bookmarks_manager); - priv->bookmarks_manager = NULL; - } - - G_OBJECT_CLASS (_gtk_file_chooser_default_parent_class)->dispose (object); -} - -/* We override show-all since we have internal widgets that - * shouldn't be shown when you call show_all(), like the filter - * combo box. - */ -static void -gtk_file_chooser_default_show_all (GtkWidget *widget) -{ - GtkFileChooserDefault *impl = (GtkFileChooserDefault *) widget; - GtkFileChooserDefaultPrivate *priv = impl->priv; - - gtk_widget_show (widget); - - if (priv->extra_widget) - gtk_widget_show_all (priv->extra_widget); -} - -/* Handler for GtkWindow::set-focus; this is where we save the last-focused - * widget on our toplevel. See gtk_file_chooser_default_hierarchy_changed() - */ -static void -toplevel_set_focus_cb (GtkWindow *window, - GtkWidget *focus, - GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - priv->toplevel_last_focus_widget = gtk_window_get_focus (window); -} - -/* We monitor the focus widget on our toplevel to be able to know which widget - * was last focused at the time our "should_respond" method gets called. - */ -static void -gtk_file_chooser_default_hierarchy_changed (GtkWidget *widget, - GtkWidget *previous_toplevel) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (widget); - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkWidget *toplevel; - - toplevel = gtk_widget_get_toplevel (widget); - - if (previous_toplevel && - priv->toplevel_set_focus_id != 0) - { - g_signal_handler_disconnect (previous_toplevel, - priv->toplevel_set_focus_id); - priv->toplevel_set_focus_id = 0; - priv->toplevel_last_focus_widget = NULL; - } - - if (gtk_widget_is_toplevel (toplevel)) - { - g_assert (priv->toplevel_set_focus_id == 0); - priv->toplevel_set_focus_id = g_signal_connect (toplevel, "set-focus", - G_CALLBACK (toplevel_set_focus_cb), impl); - priv->toplevel_last_focus_widget = gtk_window_get_focus (GTK_WINDOW (toplevel)); - } -} - -/* Changes the icons wherever it is needed */ -static void -change_icon_theme (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - gint width, height; - - profile_start ("start", NULL); - - if (gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, &height)) - priv->icon_size = MAX (width, height); - else - priv->icon_size = FALLBACK_ICON_SIZE; - - /* the first cell in the first column is the icon column, and we have a fixed size there */ - set_icon_cell_renderer_fixed_size (impl); - - if (priv->browse_files_model) - _gtk_file_system_model_clear_cache (priv->browse_files_model, MODEL_COL_SURFACE); - gtk_widget_queue_resize (priv->browse_files_tree_view); - - profile_end ("end", NULL); -} - -/* Callback used when a GtkSettings value changes */ -static void -settings_notify_cb (GObject *object, - GParamSpec *pspec, - GtkFileChooserDefault *impl) -{ - const char *name; - - profile_start ("start", NULL); - - name = g_param_spec_get_name (pspec); - - if (strcmp (name, "gtk-icon-theme-name") == 0) - change_icon_theme (impl); - - profile_end ("end", NULL); -} - -/* Installs a signal handler for GtkSettings so that we can monitor changes in - * the icon theme. - */ -static void -check_icon_theme (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkSettings *settings; - - profile_start ("start", NULL); - - if (priv->settings_signal_id) - { - profile_end ("end", NULL); - return; - } - - if (gtk_widget_has_screen (GTK_WIDGET (impl))) - { - settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (impl))); - priv->settings_signal_id = g_signal_connect (settings, "notify", - G_CALLBACK (settings_notify_cb), impl); - - change_icon_theme (impl); - } - - profile_end ("end", NULL); -} - -static void -gtk_file_chooser_default_style_updated (GtkWidget *widget) -{ - GtkFileChooserDefault *impl; - - profile_start ("start", NULL); - - impl = GTK_FILE_CHOOSER_DEFAULT (widget); - - profile_msg (" parent class style_udpated start", NULL); - GTK_WIDGET_CLASS (_gtk_file_chooser_default_parent_class)->style_updated (widget); - profile_msg (" parent class style_updated end", NULL); - - if (gtk_widget_has_screen (GTK_WIDGET (impl))) - change_icon_theme (impl); - - emit_default_size_changed (impl); - - profile_end ("end", NULL); -} - -static void -gtk_file_chooser_default_screen_changed (GtkWidget *widget, - GdkScreen *previous_screen) -{ - GtkFileChooserDefault *impl; - - profile_start ("start", NULL); - - impl = GTK_FILE_CHOOSER_DEFAULT (widget); - - if (GTK_WIDGET_CLASS (_gtk_file_chooser_default_parent_class)->screen_changed) - GTK_WIDGET_CLASS (_gtk_file_chooser_default_parent_class)->screen_changed (widget, previous_screen); - - remove_settings_signal (impl, previous_screen); - check_icon_theme (impl); - - emit_default_size_changed (impl); - - profile_end ("end", NULL); -} - -static void -set_sort_column (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkTreeSortable *sortable; - - sortable = GTK_TREE_SORTABLE (gtk_tree_view_get_model (GTK_TREE_VIEW (priv->browse_files_tree_view))); - - /* can happen when we're still populating the model */ - if (sortable == NULL) - return; - - gtk_tree_sortable_set_sort_column_id (sortable, - priv->sort_column, - priv->sort_order); -} - -static void -settings_load (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - LocationMode location_mode; - gboolean show_hidden; - gboolean show_size_column; - gboolean sort_directories_first; - gint sort_column; - GtkSortType sort_order; - StartupMode startup_mode; - gint sidebar_width; - GSettings *settings; - - settings = _gtk_file_chooser_get_settings_for_widget (GTK_WIDGET (impl)); - - location_mode = g_settings_get_enum (settings, SETTINGS_KEY_LOCATION_MODE); - show_hidden = g_settings_get_boolean (settings, SETTINGS_KEY_SHOW_HIDDEN); - show_size_column = g_settings_get_boolean (settings, SETTINGS_KEY_SHOW_SIZE_COLUMN); - sort_column = g_settings_get_enum (settings, SETTINGS_KEY_SORT_COLUMN); - sort_order = g_settings_get_enum (settings, SETTINGS_KEY_SORT_ORDER); - sidebar_width = g_settings_get_int (settings, SETTINGS_KEY_SIDEBAR_WIDTH); - startup_mode = g_settings_get_enum (settings, SETTINGS_KEY_STARTUP_MODE); - sort_directories_first = g_settings_get_boolean (settings, SETTINGS_KEY_SORT_DIRECTORIES_FIRST); - - location_mode_set (impl, location_mode, TRUE); - - gtk_file_chooser_set_show_hidden (GTK_FILE_CHOOSER (impl), show_hidden); - - priv->show_size_column = show_size_column; - gtk_tree_view_column_set_visible (priv->list_size_column, show_size_column); - - priv->sort_column = sort_column; - priv->sort_order = sort_order; - priv->startup_mode = startup_mode; - priv->sort_directories_first = sort_directories_first; - - /* We don't call set_sort_column() here as the models may not have been - * created yet. The individual functions that create and set the models will - * call set_sort_column() themselves. - */ - - gtk_paned_set_position (GTK_PANED (priv->browse_widgets_hpaned), sidebar_width); -} - -static void -settings_save (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GSettings *settings; - - settings = _gtk_file_chooser_get_settings_for_widget (GTK_WIDGET (impl)); - - /* All the other state */ - - g_settings_set_enum (settings, SETTINGS_KEY_LOCATION_MODE, priv->location_mode); - g_settings_set_boolean (settings, SETTINGS_KEY_SHOW_HIDDEN, - gtk_file_chooser_get_show_hidden (GTK_FILE_CHOOSER (impl))); - g_settings_set_boolean (settings, SETTINGS_KEY_SHOW_SIZE_COLUMN, priv->show_size_column); - g_settings_set_enum (settings, SETTINGS_KEY_SORT_COLUMN, priv->sort_column); - g_settings_set_enum (settings, SETTINGS_KEY_SORT_ORDER, priv->sort_order); - g_settings_set_int (settings, SETTINGS_KEY_SIDEBAR_WIDTH, - gtk_paned_get_position (GTK_PANED (priv->browse_widgets_hpaned))); - - /* Now apply the settings */ - g_settings_apply (settings); -} - -/* GtkWidget::realize method */ -static void -gtk_file_chooser_default_realize (GtkWidget *widget) -{ - GtkFileChooserDefault *impl; - - impl = GTK_FILE_CHOOSER_DEFAULT (widget); - - GTK_WIDGET_CLASS (_gtk_file_chooser_default_parent_class)->realize (widget); - - emit_default_size_changed (impl); -} - -/* Changes the current folder to $CWD */ -static void -switch_to_cwd (GtkFileChooserDefault *impl) -{ - char *current_working_dir; - - current_working_dir = g_get_current_dir (); - gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (impl), current_working_dir); - g_free (current_working_dir); -} - -/* Sets the file chooser to showing Recent Files or $CWD, depending on the - * user's settings. - */ -static void -set_startup_mode (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - switch (priv->startup_mode) - { - case STARTUP_MODE_RECENT: - operation_mode_set (impl, OPERATION_MODE_RECENT); - break; - - case STARTUP_MODE_CWD: - switch_to_cwd (impl); - break; - - default: - g_assert_not_reached (); - } -} - -static gboolean -shortcut_exists (GtkFileChooserDefault *impl, GFile *needle) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GSList *haystack; - GSList *l; - gboolean exists; - - exists = FALSE; - - haystack = gtk_places_sidebar_list_shortcuts (GTK_PLACES_SIDEBAR (priv->places_sidebar)); - for (l = haystack; l; l = l->next) - { - GFile *hay; - - hay = G_FILE (l->data); - if (g_file_equal (hay, needle)) - { - exists = TRUE; - break; - } - } - g_slist_free_full (haystack, g_object_unref); - - return exists; -} - -static void -add_cwd_to_sidebar_if_needed (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - char *cwd; - GFile *cwd_file; - GFile *home_file; - - cwd = g_get_current_dir (); - cwd_file = g_file_new_for_path (cwd); - g_free (cwd); - - if (shortcut_exists (impl, cwd_file)) - goto out; - - home_file = g_file_new_for_path (g_get_home_dir ()); - - /* We only add an item for $CWD if it is different from $HOME. This way, - * applications which get launched from a shell in a terminal (by someone who - * knows what they are doing) will get an item for $CWD in the places sidebar, - * and "normal" applications launched from the desktop shell (whose $CWD is - * $HOME) won't get any extra clutter in the sidebar. - */ - if (!g_file_equal (home_file, cwd_file)) - gtk_places_sidebar_add_shortcut (GTK_PLACES_SIDEBAR (priv->places_sidebar), cwd_file); - - g_object_unref (home_file); - - out: - g_object_unref (cwd_file); -} - -/* GtkWidget::map method */ -static void -gtk_file_chooser_default_map (GtkWidget *widget) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (widget); - GtkFileChooserDefaultPrivate *priv = impl->priv; - - profile_start ("start", NULL); - - GTK_WIDGET_CLASS (_gtk_file_chooser_default_parent_class)->map (widget); - - settings_load (impl); - - add_cwd_to_sidebar_if_needed (impl); - - if (priv->operation_mode == OPERATION_MODE_BROWSE) - { - switch (priv->reload_state) - { - case RELOAD_EMPTY: - set_startup_mode (impl); - break; - - case RELOAD_HAS_FOLDER: - /* Nothing; we are already loading or loaded, so we - * don't need to reload - */ - break; - - default: - g_assert_not_reached (); - } - } - - profile_end ("end", NULL); -} - -/* GtkWidget::unmap method */ -static void -gtk_file_chooser_default_unmap (GtkWidget *widget) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (widget); - GtkFileChooserDefaultPrivate *priv = impl->priv; - - settings_save (impl); - - cancel_all_operations (impl); - priv->reload_state = RELOAD_EMPTY; - - GTK_WIDGET_CLASS (_gtk_file_chooser_default_parent_class)->unmap (widget); -} - -#define COMPARE_DIRECTORIES \ - GtkFileChooserDefault *impl = user_data; \ - GtkFileChooserDefaultPrivate *priv = impl->priv; \ - GtkFileSystemModel *fs_model = GTK_FILE_SYSTEM_MODEL (model); \ - gboolean dir_a, dir_b; \ - \ - dir_a = g_value_get_boolean (_gtk_file_system_model_get_value (fs_model, a, MODEL_COL_IS_FOLDER)); \ - dir_b = g_value_get_boolean (_gtk_file_system_model_get_value (fs_model, b, MODEL_COL_IS_FOLDER)); \ - \ - if (priv->sort_directories_first && dir_a != dir_b) \ - return priv->list_sort_ascending ? (dir_a ? -1 : 1) : (dir_a ? 1 : -1) /* Directories *always* go first */ - -/* Sort callback for the filename column */ -static gint -name_sort_func (GtkTreeModel *model, - GtkTreeIter *a, - GtkTreeIter *b, - gpointer user_data) -{ - COMPARE_DIRECTORIES; - else - { - const char *key_a, *key_b; - gint result; - - key_a = g_value_get_string (_gtk_file_system_model_get_value (fs_model, a, MODEL_COL_NAME_COLLATED)); - key_b = g_value_get_string (_gtk_file_system_model_get_value (fs_model, b, MODEL_COL_NAME_COLLATED)); - - if (key_a && key_b) - result = strcmp (key_a, key_b); - else if (key_a) - result = 1; - else if (key_b) - result = -1; - else - result = 0; - - return result; - } -} - -/* Sort callback for the size column */ -static gint -size_sort_func (GtkTreeModel *model, - GtkTreeIter *a, - GtkTreeIter *b, - gpointer user_data) -{ - COMPARE_DIRECTORIES; - else - { - gint64 size_a, size_b; - - size_a = g_value_get_int64 (_gtk_file_system_model_get_value (fs_model, a, MODEL_COL_SIZE)); - size_b = g_value_get_int64 (_gtk_file_system_model_get_value (fs_model, b, MODEL_COL_SIZE)); - - return size_a < size_b ? -1 : (size_a == size_b ? 0 : 1); - } -} - -/* Sort callback for the mtime column */ -static gint -mtime_sort_func (GtkTreeModel *model, - GtkTreeIter *a, - GtkTreeIter *b, - gpointer user_data) -{ - COMPARE_DIRECTORIES; - else - { - glong ta, tb; - - ta = g_value_get_long (_gtk_file_system_model_get_value (fs_model, a, MODEL_COL_MTIME)); - tb = g_value_get_long (_gtk_file_system_model_get_value (fs_model, b, MODEL_COL_MTIME)); - - return ta < tb ? -1 : (ta == tb ? 0 : 1); - } -} - -/* Callback used when the sort column changes. We cache the sort order for use - * in name_sort_func(). - */ -static void -list_sort_column_changed_cb (GtkTreeSortable *sortable, - GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - gint sort_column_id; - GtkSortType sort_type; - - if (gtk_tree_sortable_get_sort_column_id (sortable, &sort_column_id, &sort_type)) - { - priv->list_sort_ascending = (sort_type == GTK_SORT_ASCENDING); - priv->sort_column = sort_column_id; - priv->sort_order = sort_type; - } -} - -static void -set_busy_cursor (GtkFileChooserDefault *impl, - gboolean busy) -{ - GtkWidget *widget; - GtkWindow *toplevel; - GdkDisplay *display; - GdkCursor *cursor; - - toplevel = get_toplevel (GTK_WIDGET (impl)); - widget = GTK_WIDGET (toplevel); - if (!toplevel || !gtk_widget_get_realized (widget)) - return; - - display = gtk_widget_get_display (widget); - - if (busy) - cursor = gdk_cursor_new_for_display (display, GDK_WATCH); - else - cursor = NULL; - - gdk_window_set_cursor (gtk_widget_get_window (widget), cursor); - gdk_display_flush (display); - - if (cursor) - g_object_unref (cursor); -} - -/* Creates a sort model to wrap the file system model and sets it on the tree view */ -static void -load_set_model (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - profile_start ("start", NULL); - - g_assert (priv->browse_files_model != NULL); - - profile_msg (" gtk_tree_view_set_model start", NULL); - gtk_tree_view_set_model (GTK_TREE_VIEW (priv->browse_files_tree_view), - GTK_TREE_MODEL (priv->browse_files_model)); - gtk_tree_view_columns_autosize (GTK_TREE_VIEW (priv->browse_files_tree_view)); - gtk_tree_view_set_search_column (GTK_TREE_VIEW (priv->browse_files_tree_view), - MODEL_COL_NAME); - file_list_set_sort_column_ids (impl); - set_sort_column (impl); - profile_msg (" gtk_tree_view_set_model end", NULL); - priv->list_sort_ascending = TRUE; - - profile_end ("end", NULL); -} - -/* Timeout callback used when the loading timer expires */ -static gboolean -load_timeout_cb (gpointer data) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (data); - GtkFileChooserDefaultPrivate *priv = impl->priv; - - profile_start ("start", NULL); - - g_assert (priv->load_state == LOAD_PRELOAD); - g_assert (priv->load_timeout_id != 0); - g_assert (priv->browse_files_model != NULL); - - priv->load_timeout_id = 0; - priv->load_state = LOAD_LOADING; - - load_set_model (impl); - - profile_end ("end", NULL); - - return FALSE; -} - -/* Sets up a new load timer for the model and switches to the LOAD_PRELOAD state */ -static void -load_setup_timer (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - g_assert (priv->load_timeout_id == 0); - g_assert (priv->load_state != LOAD_PRELOAD); - - priv->load_timeout_id = gdk_threads_add_timeout (MAX_LOADING_TIME, load_timeout_cb, impl); - g_source_set_name_by_id (priv->load_timeout_id, "[gtk+] load_timeout_cb"); - priv->load_state = LOAD_PRELOAD; -} - -/* Removes the load timeout; changes the impl->load_state to the specified value. */ -static void -load_remove_timer (GtkFileChooserDefault *impl, LoadState new_load_state) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (priv->load_timeout_id != 0) - { - g_assert (priv->load_state == LOAD_PRELOAD); - - g_source_remove (priv->load_timeout_id); - priv->load_timeout_id = 0; - } - else - g_assert (priv->load_state == LOAD_EMPTY || - priv->load_state == LOAD_LOADING || - priv->load_state == LOAD_FINISHED); - - g_assert (new_load_state == LOAD_EMPTY || - new_load_state == LOAD_LOADING || - new_load_state == LOAD_FINISHED); - priv->load_state = new_load_state; -} - -/* Selects the first row in the file list */ -static void -browse_files_select_first_row (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkTreePath *path; - GtkTreeIter dummy_iter; - GtkTreeModel *tree_model; - - tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->browse_files_tree_view)); - - if (!tree_model) - return; - - path = gtk_tree_path_new_from_indices (0, -1); - - /* If the list is empty, do nothing. */ - if (gtk_tree_model_get_iter (tree_model, &dummy_iter, path)) - { - /* Although the following call to gtk_tree_view_set_cursor() is intended to - * only change the focus to the first row (not select it), GtkTreeView *will* - * select the row anyway due to bug #492206. So, we'll use a flag to - * keep our own callbacks from changing the location_entry when the selection - * is changed. This entire function, browse_files_select_first_row(), may - * go away when that bug is fixed in GtkTreeView. - */ - priv->auto_selecting_first_row = TRUE; - - gtk_tree_view_set_cursor (GTK_TREE_VIEW (priv->browse_files_tree_view), path, NULL, FALSE); - - priv->auto_selecting_first_row = FALSE; - } - gtk_tree_path_free (path); -} - -struct center_selected_row_closure { - GtkFileChooserDefault *impl; - gboolean already_centered; -}; - -/* Callback used from gtk_tree_selection_selected_foreach(); centers the - * selected row in the tree view. - */ -static void -center_selected_row_foreach_cb (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) -{ - struct center_selected_row_closure *closure; - - closure = data; - if (closure->already_centered) - return; - - gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (closure->impl->priv->browse_files_tree_view), path, NULL, TRUE, 0.5, 0.0); - closure->already_centered = TRUE; -} - -/* Centers the selected row in the tree view */ -static void -browse_files_center_selected_row (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - struct center_selected_row_closure closure; - GtkTreeSelection *selection; - - closure.impl = impl; - closure.already_centered = FALSE; - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); - gtk_tree_selection_selected_foreach (selection, center_selected_row_foreach_cb, &closure); -} - -static gboolean -show_and_select_files (GtkFileChooserDefault *impl, - GSList *files) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkTreeSelection *selection; - GtkFileSystemModel *fsmodel; - gboolean enabled_hidden, removed_filters; - gboolean selected_a_file; - GSList *walk; - - g_assert (priv->load_state == LOAD_FINISHED); - g_assert (priv->browse_files_model != NULL); - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); - fsmodel = GTK_FILE_SYSTEM_MODEL (gtk_tree_view_get_model (GTK_TREE_VIEW (priv->browse_files_tree_view))); - - g_assert (fsmodel == priv->browse_files_model); - - enabled_hidden = priv->show_hidden; - removed_filters = (priv->current_filter == NULL); - - selected_a_file = FALSE; - - for (walk = files; walk; walk = walk->next) - { - GFile *file = walk->data; - GtkTreeIter iter; - - /* Is it a hidden file? */ - - if (!_gtk_file_system_model_get_iter_for_file (fsmodel, &iter, file)) - continue; - - if (!_gtk_file_system_model_iter_is_visible (fsmodel, &iter)) - { - GFileInfo *info = _gtk_file_system_model_get_info (fsmodel, &iter); - - if (!enabled_hidden && - (g_file_info_get_is_hidden (info) || - g_file_info_get_is_backup (info))) - { - g_object_set (impl, "show-hidden", TRUE, NULL); - enabled_hidden = TRUE; - } - } - - /* Is it a filtered file? */ - - if (!_gtk_file_system_model_get_iter_for_file (fsmodel, &iter, file)) - continue; /* re-get the iter as it may change when the model refilters */ - - if (!_gtk_file_system_model_iter_is_visible (fsmodel, &iter)) - { - /* Maybe we should have a way to ask the fsmodel if it had filtered a file */ - if (!removed_filters) - { - set_current_filter (impl, NULL); - removed_filters = TRUE; - } - } - - /* Okay, can we select the file now? */ - - if (!_gtk_file_system_model_get_iter_for_file (fsmodel, &iter, file)) - continue; - - if (_gtk_file_system_model_iter_is_visible (fsmodel, &iter)) - { - GtkTreePath *path; - - gtk_tree_selection_select_iter (selection, &iter); - - path = gtk_tree_model_get_path (GTK_TREE_MODEL (fsmodel), &iter); - gtk_tree_view_set_cursor (GTK_TREE_VIEW (priv->browse_files_tree_view), - path, NULL, FALSE); - gtk_tree_path_free (path); - - selected_a_file = TRUE; - } - } - - browse_files_center_selected_row (impl); - - return selected_a_file; -} - -/* Processes the pending operation when a folder is finished loading */ -static void -pending_select_files_process (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - g_assert (priv->load_state == LOAD_FINISHED); - g_assert (priv->browse_files_model != NULL); - - if (priv->pending_select_files) - { - show_and_select_files (impl, priv->pending_select_files); - pending_select_files_free (impl); - browse_files_center_selected_row (impl); - } - else - { - /* We only select the first row if the chooser is actually mapped --- - * selecting the first row is to help the user when he is interacting with - * the chooser, but sometimes a chooser works not on behalf of the user, - * but rather on behalf of something else like GtkFileChooserButton. In - * that case, the chooser's selection should be what the caller expects, - * as the user can't see that something else got selected. See bug #165264. - */ - if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN && - gtk_widget_get_mapped (GTK_WIDGET (impl))) - browse_files_select_first_row (impl); - } - - g_assert (priv->pending_select_files == NULL); -} - -static void -show_error_on_reading_current_folder (GtkFileChooserDefault *impl, GError *error) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GFileInfo *info; - char *msg; - - info = g_file_query_info (priv->current_folder, - G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, - G_FILE_QUERY_INFO_NONE, - NULL, - NULL); - if (info) - { - msg = g_strdup_printf (_("Could not read the contents of %s"), g_file_info_get_display_name (info)); - g_object_unref (info); - } - else - msg = g_strdup (_("Could not read the contents of the folder")); - - error_message (impl, msg, error->message); - g_free (msg); -} - -/* Callback used when the file system model finishes loading */ -static void -browse_files_model_finished_loading_cb (GtkFileSystemModel *model, - GError *error, - GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - profile_start ("start", NULL); - - if (error) - show_error_on_reading_current_folder (impl, error); - - if (priv->load_state == LOAD_PRELOAD) - { - load_remove_timer (impl, LOAD_FINISHED); - load_set_model (impl); - } - else if (priv->load_state == LOAD_LOADING) - { - /* Nothing */ - } - else - { - /* We can't g_assert_not_reached(), as something other than us may have - * initiated a folder reload. See #165556. - */ - profile_end ("end", NULL); - return; - } - - g_assert (priv->load_timeout_id == 0); - - priv->load_state = LOAD_FINISHED; - - pending_select_files_process (impl); - set_busy_cursor (impl, FALSE); -#ifdef PROFILE_FILE_CHOOSER - access ("MARK: *** FINISHED LOADING", F_OK); -#endif - - profile_end ("end", NULL); -} - -static void -stop_loading_and_clear_list_model (GtkFileChooserDefault *impl, - gboolean remove_from_treeview) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - load_remove_timer (impl, LOAD_EMPTY); - - if (priv->browse_files_model) - { - g_object_unref (priv->browse_files_model); - priv->browse_files_model = NULL; - } - - if (remove_from_treeview) - gtk_tree_view_set_model (GTK_TREE_VIEW (priv->browse_files_tree_view), NULL); -} - -static char * -my_g_format_time_for_display (GtkFileChooserDefault *impl, - glong secs) -{ - GDateTime *now, *time; - GTimeSpan time_diff; - gchar *clock_format; - gboolean use_24 = TRUE; - const gchar *format; - gchar *date_str; - GSettings *settings; - - now = g_date_time_new_now_local (); - time = g_date_time_new_from_unix_local (secs); - time_diff = g_date_time_difference (now, time); - - settings = _gtk_file_chooser_get_settings_for_widget (GTK_WIDGET (impl)); - clock_format = g_settings_get_string (settings, "clock-format"); - use_24 = g_strcmp0 (clock_format, "24h") == 0; - g_free (clock_format); - - /* Translators: see g_date_time_format() for details on the format */ - if (time_diff >= 0 && time_diff < G_TIME_SPAN_DAY) - format = use_24 ? _("%H:%M") : _("%-I:%M %P"); - else if (time_diff >= 0 && time_diff < 2 * G_TIME_SPAN_DAY) - format = use_24 ? _("Yesterday at %H:%M") : _("Yesterday at %-I:%M %P"); - else if (time_diff >= 0 && time_diff < 7 * G_TIME_SPAN_DAY) - format = "%A"; /* Days from last week */ - else - format = "%x"; /* Any other date */ - - date_str = g_date_time_format (time, format); - - g_date_time_unref (time); - g_date_time_unref (now); - - return date_str; -} - -static void -copy_attribute (GFileInfo *to, GFileInfo *from, const char *attribute) -{ - GFileAttributeType type; - gpointer value; - - if (g_file_info_get_attribute_data (from, attribute, &type, &value, NULL)) - g_file_info_set_attribute (to, attribute, type, value); -} - -static void -file_system_model_got_thumbnail (GObject *object, GAsyncResult *res, gpointer data) -{ - GtkFileSystemModel *model = data; /* might be unreffed if operation was cancelled */ - GFile *file = G_FILE (object); - GFileInfo *queried, *info; - GtkTreeIter iter; - - queried = g_file_query_info_finish (file, res, NULL); - if (queried == NULL) - return; - - gdk_threads_enter (); - - /* now we know model is valid */ - - /* file was deleted */ - if (!_gtk_file_system_model_get_iter_for_file (model, &iter, file)) - { - gdk_threads_leave (); - return; - } - - info = g_file_info_dup (_gtk_file_system_model_get_info (model, &iter)); - - copy_attribute (info, queried, G_FILE_ATTRIBUTE_THUMBNAIL_PATH); - copy_attribute (info, queried, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED); - copy_attribute (info, queried, G_FILE_ATTRIBUTE_STANDARD_ICON); - - _gtk_file_system_model_update_file (model, file, info); - - g_object_unref (info); - - gdk_threads_leave (); -} - -static gboolean -file_system_model_set (GtkFileSystemModel *model, - GFile *file, - GFileInfo *info, - int column, - GValue *value, - gpointer data) -{ - GtkFileChooserDefault *impl = data; - GtkFileChooserDefaultPrivate *priv = impl->priv; - - switch (column) - { - case MODEL_COL_FILE: - g_value_set_object (value, file); - break; - case MODEL_COL_NAME: - if (info == NULL) - g_value_set_string (value, DEFAULT_NEW_FOLDER_NAME); - else - g_value_set_string (value, g_file_info_get_display_name (info)); - break; - case MODEL_COL_NAME_COLLATED: - if (info == NULL) - g_value_take_string (value, g_utf8_collate_key_for_filename (DEFAULT_NEW_FOLDER_NAME, -1)); - else - g_value_take_string (value, g_utf8_collate_key_for_filename (g_file_info_get_display_name (info), -1)); - break; - case MODEL_COL_IS_FOLDER: - g_value_set_boolean (value, info == NULL || _gtk_file_info_consider_as_directory (info)); - break; - case MODEL_COL_IS_SENSITIVE: - if (info) - { - gboolean sensitive = TRUE; - - if (!(priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER - || priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)) - { - sensitive = TRUE; /* for file modes... */ - } - else if (!_gtk_file_info_consider_as_directory (info)) - { - sensitive = FALSE; /* for folder modes, files are not sensitive... */ - } - else - { - /* ... and for folder modes, folders are sensitive only if the filter says so */ - GtkTreeIter iter; - if (!_gtk_file_system_model_get_iter_for_file (model, &iter, file)) - g_assert_not_reached (); - sensitive = !_gtk_file_system_model_iter_is_filtered_out (model, &iter); - } - - g_value_set_boolean (value, sensitive); - } - else - g_value_set_boolean (value, TRUE); - break; - case MODEL_COL_SURFACE: - if (info) - { - if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_ICON)) - { - g_value_take_boxed (value, _gtk_file_info_render_icon (info, GTK_WIDGET (impl), priv->icon_size)); - } - else - { - GtkTreeModel *tree_model; - GtkTreePath *path, *start, *end; - GtkTreeIter iter; - - if (priv->browse_files_tree_view == NULL || - g_file_info_has_attribute (info, "filechooser::queried")) - return FALSE; - - tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->browse_files_tree_view)); - if (tree_model != GTK_TREE_MODEL (model)) - return FALSE; - - if (!_gtk_file_system_model_get_iter_for_file (model, - &iter, - file)) - g_assert_not_reached (); - if (!gtk_tree_view_get_visible_range (GTK_TREE_VIEW (priv->browse_files_tree_view), &start, &end)) - return FALSE; - path = gtk_tree_model_get_path (tree_model, &iter); - if (gtk_tree_path_compare (start, path) != 1 && - gtk_tree_path_compare (path, end) != 1) - { - g_file_info_set_attribute_boolean (info, "filechooser::queried", TRUE); - g_file_query_info_async (file, - G_FILE_ATTRIBUTE_THUMBNAIL_PATH "," - G_FILE_ATTRIBUTE_THUMBNAILING_FAILED "," - G_FILE_ATTRIBUTE_STANDARD_ICON, - G_FILE_QUERY_INFO_NONE, - G_PRIORITY_DEFAULT, - _gtk_file_system_model_get_cancellable (model), - file_system_model_got_thumbnail, - model); - } - gtk_tree_path_free (path); - gtk_tree_path_free (start); - gtk_tree_path_free (end); - return FALSE; - } - } - else - g_value_set_object (value, NULL); - break; - case MODEL_COL_SIZE: - g_value_set_int64 (value, info ? g_file_info_get_size (info) : 0); - break; - case MODEL_COL_SIZE_TEXT: - if (info == NULL || _gtk_file_info_consider_as_directory (info)) - g_value_set_string (value, NULL); - else - g_value_take_string (value, g_format_size (g_file_info_get_size (info))); - break; - case MODEL_COL_MTIME: - case MODEL_COL_MTIME_TEXT: - { - GTimeVal tv; - if (info == NULL) - break; - g_file_info_get_modification_time (info, &tv); - if (column == MODEL_COL_MTIME) - g_value_set_long (value, tv.tv_sec); - else if (tv.tv_sec == 0) - g_value_set_static_string (value, _("Unknown")); - else - g_value_take_string (value, my_g_format_time_for_display (impl, tv.tv_sec)); - break; - } - case MODEL_COL_ELLIPSIZE: - g_value_set_enum (value, info ? PANGO_ELLIPSIZE_END : PANGO_ELLIPSIZE_NONE); - break; - default: - g_assert_not_reached (); - break; - } - - return TRUE; -} - -/* Gets rid of the old list model and creates a new one for the current folder */ -static gboolean -set_list_model (GtkFileChooserDefault *impl, - GError **error) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - g_assert (priv->current_folder != NULL); - - profile_start ("start", NULL); - - stop_loading_and_clear_list_model (impl, TRUE); - - set_busy_cursor (impl, TRUE); - - priv->browse_files_model = - _gtk_file_system_model_new_for_directory (priv->current_folder, - MODEL_ATTRIBUTES, - file_system_model_set, - impl, - MODEL_COLUMN_TYPES); - - _gtk_file_system_model_set_show_hidden (priv->browse_files_model, priv->show_hidden); - - profile_msg (" set sort function", NULL); - gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->browse_files_model), MODEL_COL_NAME, name_sort_func, impl, NULL); - gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->browse_files_model), MODEL_COL_SIZE, size_sort_func, impl, NULL); - gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->browse_files_model), MODEL_COL_MTIME, mtime_sort_func, impl, NULL); - gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (priv->browse_files_model), NULL, NULL, NULL); - set_sort_column (impl); - priv->list_sort_ascending = TRUE; - g_signal_connect (priv->browse_files_model, "sort-column-changed", - G_CALLBACK (list_sort_column_changed_cb), impl); - - load_setup_timer (impl); /* This changes the state to LOAD_PRELOAD */ - - g_signal_connect (priv->browse_files_model, "finished-loading", - G_CALLBACK (browse_files_model_finished_loading_cb), impl); - - _gtk_file_system_model_set_filter (priv->browse_files_model, priv->current_filter); - - profile_end ("end", NULL); - - return TRUE; -} - -struct update_chooser_entry_selected_foreach_closure { - int num_selected; - GtkTreeIter first_selected_iter; -}; - -static gint -compare_utf8_filenames (const gchar *a, - const gchar *b) -{ - gchar *a_folded, *b_folded; - gint retval; - - a_folded = g_utf8_strdown (a, -1); - b_folded = g_utf8_strdown (b, -1); - - retval = strcmp (a_folded, b_folded); - - g_free (a_folded); - g_free (b_folded); - - return retval; -} - -static void -update_chooser_entry_selected_foreach (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) -{ - struct update_chooser_entry_selected_foreach_closure *closure; - - closure = data; - closure->num_selected++; - - if (closure->num_selected == 1) - closure->first_selected_iter = *iter; -} - -static void -update_chooser_entry (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkTreeSelection *selection; - struct update_chooser_entry_selected_foreach_closure closure; - - /* no need to update the file chooser's entry if there's no entry */ - if (priv->operation_mode == OPERATION_MODE_SEARCH || - !priv->location_entry) - return; - - if (!(priv->action == GTK_FILE_CHOOSER_ACTION_SAVE - || priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER - || ((priv->action == GTK_FILE_CHOOSER_ACTION_OPEN - || priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) - && priv->location_mode == LOCATION_MODE_FILENAME_ENTRY))) - return; - - g_assert (priv->location_entry != NULL); - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); - closure.num_selected = 0; - gtk_tree_selection_selected_foreach (selection, update_chooser_entry_selected_foreach, &closure); - - if (closure.num_selected == 0) - { - if (priv->operation_mode == OPERATION_MODE_RECENT) - _gtk_file_chooser_entry_set_base_folder (GTK_FILE_CHOOSER_ENTRY (priv->location_entry), NULL); - else - goto maybe_clear_entry; - } - else if (closure.num_selected == 1) - { - if (priv->operation_mode == OPERATION_MODE_BROWSE) - { - GFileInfo *info; - gboolean change_entry; - - info = _gtk_file_system_model_get_info (priv->browse_files_model, &closure.first_selected_iter); - - /* If the cursor moved to the row of the newly created folder, - * retrieving info will return NULL. - */ - if (!info) - return; - - g_free (priv->browse_files_last_selected_name); - priv->browse_files_last_selected_name = - g_strdup (g_file_info_get_display_name (info)); - - if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN || - priv->action == GTK_FILE_CHOOSER_ACTION_SAVE || - priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) - { - /* Don't change the name when clicking on a folder... */ - change_entry = (! _gtk_file_info_consider_as_directory (info)); - } - else - change_entry = TRUE; /* ... unless we are in SELECT_FOLDER mode */ - - if (change_entry && !priv->auto_selecting_first_row) - { - gtk_entry_set_text (GTK_ENTRY (priv->location_entry), priv->browse_files_last_selected_name); - - if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) - _gtk_file_chooser_entry_select_filename (GTK_FILE_CHOOSER_ENTRY (priv->location_entry)); - } - - return; - } - else if (priv->operation_mode == OPERATION_MODE_RECENT - && priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) - { - GFile *folder; - - /* Set the base folder on the name entry, so it will do completion relative to the correct recent-folder */ - - gtk_tree_model_get (GTK_TREE_MODEL (priv->recent_model), &closure.first_selected_iter, - MODEL_COL_FILE, &folder, - -1); - _gtk_file_chooser_entry_set_base_folder (GTK_FILE_CHOOSER_ENTRY (priv->location_entry), folder); - g_object_unref (folder); - return; - } - } - else - { - g_assert (!(priv->action == GTK_FILE_CHOOSER_ACTION_SAVE || - priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)); - - /* Multiple selection, so just clear the entry. */ - g_free (priv->browse_files_last_selected_name); - priv->browse_files_last_selected_name = NULL; - - gtk_entry_set_text (GTK_ENTRY (priv->location_entry), ""); - return; - } - - maybe_clear_entry: - - if ((priv->action == GTK_FILE_CHOOSER_ACTION_OPEN || priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) - && priv->browse_files_last_selected_name) - { - const char *entry_text; - int len; - gboolean clear_entry; - - entry_text = gtk_entry_get_text (GTK_ENTRY (priv->location_entry)); - len = strlen (entry_text); - if (len != 0) - { - /* The file chooser entry may have appended a "/" to its text. - * So take it out, and compare the result to the old selection. - */ - if (entry_text[len - 1] == G_DIR_SEPARATOR) - { - gchar *tmp; - - tmp = g_strndup (entry_text, len - 1); - clear_entry = (compare_utf8_filenames (priv->browse_files_last_selected_name, tmp) == 0); - g_free (tmp); - } - else - clear_entry = (compare_utf8_filenames (priv->browse_files_last_selected_name, entry_text) == 0); - } - else - clear_entry = FALSE; - - if (clear_entry) - gtk_entry_set_text (GTK_ENTRY (priv->location_entry), ""); - } -} - -static gboolean -gtk_file_chooser_default_set_current_folder (GtkFileChooser *chooser, - GFile *file, - GError **error) -{ - return gtk_file_chooser_default_update_current_folder (chooser, file, FALSE, FALSE, error); -} - - -struct UpdateCurrentFolderData -{ - GtkFileChooserDefault *impl; - GFile *file; - gboolean keep_trail; - gboolean clear_entry; - GFile *original_file; - GError *original_error; -}; - -static void -update_current_folder_mount_enclosing_volume_cb (GCancellable *cancellable, - GtkFileSystemVolume *volume, - const GError *error, - gpointer user_data) -{ - struct UpdateCurrentFolderData *data = user_data; - GtkFileChooserDefault *impl = data->impl; - GtkFileChooserDefaultPrivate *priv = impl->priv; - gboolean cancelled = g_cancellable_is_cancelled (cancellable); - - if (cancellable != priv->update_current_folder_cancellable) - goto out; - - priv->update_current_folder_cancellable = NULL; - set_busy_cursor (impl, FALSE); - - if (cancelled) - goto out; - - if (error) - { - error_changing_folder_dialog (data->impl, data->file, g_error_copy (error)); - priv->reload_state = RELOAD_EMPTY; - goto out; - } - - change_folder_and_display_error (impl, data->file, data->clear_entry); - -out: - g_object_unref (data->file); - g_free (data); - - g_object_unref (cancellable); -} - -static void -update_current_folder_get_info_cb (GCancellable *cancellable, - GFileInfo *info, - const GError *error, - gpointer user_data) -{ - gboolean cancelled = g_cancellable_is_cancelled (cancellable); - struct UpdateCurrentFolderData *data = user_data; - GtkFileChooserDefault *impl = data->impl; - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (cancellable != priv->update_current_folder_cancellable) - goto out; - - priv->update_current_folder_cancellable = NULL; - priv->reload_state = RELOAD_EMPTY; - - set_busy_cursor (impl, FALSE); - - if (cancelled) - goto out; - - if (error) - { - GFile *parent_file; - - if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_MOUNTED)) - { - GMountOperation *mount_operation; - GtkWidget *toplevel; - - g_object_unref (cancellable); - toplevel = gtk_widget_get_toplevel (GTK_WIDGET (impl)); - - mount_operation = gtk_mount_operation_new (GTK_WINDOW (toplevel)); - - set_busy_cursor (impl, TRUE); - - priv->update_current_folder_cancellable = - _gtk_file_system_mount_enclosing_volume (priv->file_system, data->file, - mount_operation, - update_current_folder_mount_enclosing_volume_cb, - data); - - return; - } - - if (!data->original_file) - { - data->original_file = g_object_ref (data->file); - data->original_error = g_error_copy (error); - } - - parent_file = g_file_get_parent (data->file); - - /* get parent path and try to change the folder to that */ - if (parent_file) - { - g_object_unref (data->file); - data->file = parent_file; - - g_object_unref (cancellable); - - /* restart the update current folder operation */ - priv->reload_state = RELOAD_HAS_FOLDER; - - priv->update_current_folder_cancellable = - _gtk_file_system_get_info (priv->file_system, data->file, - "standard::type", - update_current_folder_get_info_cb, - data); - - set_busy_cursor (impl, TRUE); - - return; - } - else - { - /* Error and bail out, ignoring "not found" errors since they're useless: - * they only happen when a program defaults to a folder that has been (re)moved. - */ - if (!g_error_matches (data->original_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) - error_changing_folder_dialog (impl, data->original_file, data->original_error); - else - g_error_free (data->original_error); - - g_object_unref (data->original_file); - - goto out; - } - } - - if (data->original_file) - { - /* Error and bail out, ignoring "not found" errors since they're useless: - * they only happen when a program defaults to a folder that has been (re)moved. - */ - if (!g_error_matches (data->original_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) - error_changing_folder_dialog (impl, data->original_file, data->original_error); - else - g_error_free (data->original_error); - - g_object_unref (data->original_file); - } - - if (! _gtk_file_info_consider_as_directory (info)) - goto out; - - _gtk_path_bar_set_file (GTK_PATH_BAR (priv->browse_path_bar), data->file, data->keep_trail); - - if (priv->current_folder != data->file) - { - if (priv->current_folder) - g_object_unref (priv->current_folder); - - priv->current_folder = g_object_ref (data->file); - } - - priv->reload_state = RELOAD_HAS_FOLDER; - - /* Set the folder on the save entry */ - - if (priv->location_entry) - { - _gtk_file_chooser_entry_set_base_folder (GTK_FILE_CHOOSER_ENTRY (priv->location_entry), - priv->current_folder); - - if (data->clear_entry) - gtk_entry_set_text (GTK_ENTRY (priv->location_entry), ""); - } - - /* Create a new list model. This is slightly evil; we store the result value - * but perform more actions rather than returning immediately even if it - * generates an error. - */ - set_list_model (impl, NULL); - - /* Refresh controls */ - - gtk_places_sidebar_set_location (GTK_PLACES_SIDEBAR (priv->places_sidebar), priv->current_folder); - - g_signal_emit_by_name (impl, "current-folder-changed", 0); - - check_preview_change (impl); - - g_signal_emit_by_name (impl, "selection-changed", 0); - -out: - g_object_unref (data->file); - g_free (data); - - g_object_unref (cancellable); -} - -static gboolean -gtk_file_chooser_default_update_current_folder (GtkFileChooser *chooser, - GFile *file, - gboolean keep_trail, - gboolean clear_entry, - GError **error) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - GtkFileChooserDefaultPrivate *priv = impl->priv; - struct UpdateCurrentFolderData *data; - - profile_start ("start", NULL); - - g_object_ref (file); - - operation_mode_set (impl, OPERATION_MODE_BROWSE); - - if (priv->local_only && !_gtk_file_has_native_path (file)) - { - g_set_error_literal (error, - GTK_FILE_CHOOSER_ERROR, - GTK_FILE_CHOOSER_ERROR_BAD_FILENAME, - _("Cannot change to folder because it is not local")); - - g_object_unref (file); - profile_end ("end - not local", NULL); - return FALSE; - } - - if (priv->update_current_folder_cancellable) - g_cancellable_cancel (priv->update_current_folder_cancellable); - - /* Test validity of path here. */ - data = g_new0 (struct UpdateCurrentFolderData, 1); - data->impl = impl; - data->file = g_object_ref (file); - data->keep_trail = keep_trail; - data->clear_entry = clear_entry; - - priv->reload_state = RELOAD_HAS_FOLDER; - - priv->update_current_folder_cancellable = - _gtk_file_system_get_info (priv->file_system, file, - "standard::type", - update_current_folder_get_info_cb, - data); - - set_busy_cursor (impl, TRUE); - g_object_unref (file); - - profile_end ("end", NULL); - return TRUE; -} - -static GFile * -gtk_file_chooser_default_get_current_folder (GtkFileChooser *chooser) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (priv->operation_mode == OPERATION_MODE_SEARCH || - priv->operation_mode == OPERATION_MODE_RECENT) - return NULL; - - if (priv->current_folder) - return g_object_ref (priv->current_folder); - - return NULL; -} - -static void -gtk_file_chooser_default_set_current_name (GtkFileChooser *chooser, - const gchar *name) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - GtkFileChooserDefaultPrivate *priv = impl->priv; - - g_return_if_fail (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE || - priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER); - - pending_select_files_free (impl); - gtk_entry_set_text (GTK_ENTRY (priv->location_entry), name); -} - -static gchar * -gtk_file_chooser_default_get_current_name (GtkFileChooser *chooser) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - GtkFileChooserDefaultPrivate *priv = impl->priv; - - g_return_val_if_fail (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE || - priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER, - NULL); - - return g_strdup (gtk_entry_get_text (GTK_ENTRY (priv->location_entry))); -} - -static gboolean -gtk_file_chooser_default_select_file (GtkFileChooser *chooser, - GFile *file, - GError **error) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - GtkFileChooserDefaultPrivate *priv = impl->priv; - GFile *parent_file; - gboolean same_path; - - parent_file = g_file_get_parent (file); - - if (!parent_file) - return gtk_file_chooser_set_current_folder_file (chooser, file, error); - - if (priv->operation_mode == OPERATION_MODE_SEARCH || - priv->operation_mode == OPERATION_MODE_RECENT || - priv->load_state == LOAD_EMPTY) - { - same_path = FALSE; - } - else - { - g_assert (priv->current_folder != NULL); - - same_path = g_file_equal (parent_file, priv->current_folder); - } - - if (same_path && priv->load_state == LOAD_FINISHED) - { - gboolean result; - GSList files; - - files.data = (gpointer) file; - files.next = NULL; - - result = show_and_select_files (impl, &files); - g_object_unref (parent_file); - return result; - } - - pending_select_files_add (impl, file); - - if (!same_path) - { - gboolean result; - - result = gtk_file_chooser_set_current_folder_file (chooser, parent_file, error); - g_object_unref (parent_file); - return result; - } - - g_object_unref (parent_file); - return TRUE; -} - -static void -gtk_file_chooser_default_unselect_file (GtkFileChooser *chooser, - GFile *file) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkTreeView *tree_view = GTK_TREE_VIEW (priv->browse_files_tree_view); - GtkTreeIter iter; - - if (!priv->browse_files_model) - return; - - if (!_gtk_file_system_model_get_iter_for_file (priv->browse_files_model, - &iter, - file)) - return; - - gtk_tree_selection_unselect_iter (gtk_tree_view_get_selection (tree_view), - &iter); -} - -static gboolean -maybe_select (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (data); - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkTreeSelection *selection; - gboolean is_sensitive; - gboolean is_folder; - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); - - gtk_tree_model_get (model, iter, - MODEL_COL_IS_FOLDER, &is_folder, - MODEL_COL_IS_SENSITIVE, &is_sensitive, - -1); - - if (is_sensitive && - ((is_folder && priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) || - (!is_folder && priv->action == GTK_FILE_CHOOSER_ACTION_OPEN))) - gtk_tree_selection_select_iter (selection, iter); - else - gtk_tree_selection_unselect_iter (selection, iter); - - return FALSE; -} - -static void -gtk_file_chooser_default_select_all (GtkFileChooser *chooser) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (priv->operation_mode == OPERATION_MODE_SEARCH || - priv->operation_mode == OPERATION_MODE_RECENT) - { - GtkTreeSelection *selection; - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); - gtk_tree_selection_select_all (selection); - return; - } - - if (priv->select_multiple) - gtk_tree_model_foreach (GTK_TREE_MODEL (priv->browse_files_model), - maybe_select, impl); -} - -static void -gtk_file_chooser_default_unselect_all (GtkFileChooser *chooser) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); - - gtk_tree_selection_unselect_all (selection); - pending_select_files_free (impl); -} - -/* Checks whether the filename entry for the Save modes contains a well-formed filename. - * - * is_well_formed_ret - whether what the user typed passes gkt_file_system_make_path() - * - * is_empty_ret - whether the file entry is totally empty - * - * is_file_part_empty_ret - whether the file part is empty (will be if user types "foobar/", and - * the path will be "$cwd/foobar") - */ -static void -check_save_entry (GtkFileChooserDefault *impl, - GFile **file_ret, - gboolean *is_well_formed_ret, - gboolean *is_empty_ret, - gboolean *is_file_part_empty_ret, - gboolean *is_folder) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkFileChooserEntry *chooser_entry; - GFile *current_folder; - const char *file_part; - GFile *file; - GError *error; - - g_assert (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE - || priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER - || ((priv->action == GTK_FILE_CHOOSER_ACTION_OPEN - || priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) - && priv->location_mode == LOCATION_MODE_FILENAME_ENTRY)); - - chooser_entry = GTK_FILE_CHOOSER_ENTRY (priv->location_entry); - - if (strlen (gtk_entry_get_text (GTK_ENTRY (chooser_entry))) == 0) - { - *file_ret = NULL; - *is_well_formed_ret = TRUE; - *is_empty_ret = TRUE; - *is_file_part_empty_ret = TRUE; - *is_folder = FALSE; - - return; - } - - *is_empty_ret = FALSE; - - current_folder = _gtk_file_chooser_entry_get_current_folder (chooser_entry); - if (!current_folder) - { - *file_ret = NULL; - *is_well_formed_ret = FALSE; - *is_file_part_empty_ret = FALSE; - *is_folder = FALSE; - - return; - } - - file_part = _gtk_file_chooser_entry_get_file_part (chooser_entry); - - if (!file_part || file_part[0] == '\0') - { - *file_ret = current_folder; - *is_well_formed_ret = TRUE; - *is_file_part_empty_ret = TRUE; - *is_folder = TRUE; - - return; - } - - *is_file_part_empty_ret = FALSE; - - error = NULL; - file = g_file_get_child_for_display_name (current_folder, file_part, &error); - g_object_unref (current_folder); - - if (!file) - { - error_building_filename_dialog (impl, error); - *file_ret = NULL; - *is_well_formed_ret = FALSE; - *is_folder = FALSE; - - return; - } - - *file_ret = file; - *is_well_formed_ret = TRUE; - *is_folder = _gtk_file_chooser_entry_get_is_folder (chooser_entry, file); -} - -struct get_files_closure { - GtkFileChooserDefault *impl; - GSList *result; - GFile *file_from_entry; -}; - -static void -get_files_foreach (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) -{ - struct get_files_closure *info; - GFile *file; - GtkFileSystemModel *fs_model; - - info = data; - fs_model = info->impl->priv->browse_files_model; - - file = _gtk_file_system_model_get_file (fs_model, iter); - if (!file) - return; /* We are on the editable row */ - - if (!info->file_from_entry || !g_file_equal (info->file_from_entry, file)) - info->result = g_slist_prepend (info->result, g_object_ref (file)); -} - -static GSList * -gtk_file_chooser_default_get_files (GtkFileChooser *chooser) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - GtkFileChooserDefaultPrivate *priv = impl->priv; - struct get_files_closure info; - GtkWindow *toplevel; - GtkWidget *current_focus; - gboolean file_list_seen; - - info.impl = impl; - info.result = NULL; - info.file_from_entry = NULL; - - if (priv->operation_mode == OPERATION_MODE_SEARCH) - return search_get_selected_files (impl); - - if (priv->operation_mode == OPERATION_MODE_RECENT) - { - if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) - { - file_list_seen = TRUE; - goto file_entry; - } - else - return recent_get_selected_files (impl); - } - - toplevel = get_toplevel (GTK_WIDGET (impl)); - if (toplevel) - current_focus = gtk_window_get_focus (toplevel); - else - current_focus = NULL; - - file_list_seen = FALSE; - if (current_focus == priv->browse_files_tree_view) - { - GtkTreeSelection *selection; - - file_list: - - file_list_seen = TRUE; - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); - gtk_tree_selection_selected_foreach (selection, get_files_foreach, &info); - - /* If there is no selection in the file list, we probably have this situation: - * - * 1. The user typed a filename in the SAVE filename entry ("foo.txt"). - * 2. He then double-clicked on a folder ("bar") in the file list - * - * So we want the selection to be "bar/foo.txt". Jump to the case for the - * filename entry to see if that is the case. - */ - if (info.result == NULL && priv->location_entry) - goto file_entry; - } - else if (priv->location_entry && current_focus == priv->location_entry) - { - gboolean is_well_formed, is_empty, is_file_part_empty, is_folder; - - file_entry: - - check_save_entry (impl, &info.file_from_entry, &is_well_formed, &is_empty, &is_file_part_empty, &is_folder); - - if (is_empty) - goto out; - - if (!is_well_formed) - return NULL; - - if (is_file_part_empty && priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) - { - g_object_unref (info.file_from_entry); - return NULL; - } - - if (info.file_from_entry) - info.result = g_slist_prepend (info.result, info.file_from_entry); - else if (!file_list_seen) - goto file_list; - else - return NULL; - } - else if (priv->toplevel_last_focus_widget == priv->browse_files_tree_view) - goto file_list; - else if (priv->location_entry && priv->toplevel_last_focus_widget == priv->location_entry) - goto file_entry; - else - { - /* The focus is on a dialog's action area button or something else */ - if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE || - priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) - goto file_entry; - else - goto file_list; - } - - out: - - /* If there's no folder selected, and we're in SELECT_FOLDER mode, then we - * fall back to the current directory */ - if (priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER && - info.result == NULL) - { - GFile *current_folder; - - current_folder = gtk_file_chooser_get_current_folder_file (chooser); - - if (current_folder) - info.result = g_slist_prepend (info.result, current_folder); - } - - return g_slist_reverse (info.result); -} - -GFile * -gtk_file_chooser_default_get_preview_file (GtkFileChooser *chooser) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (priv->preview_file) - return g_object_ref (priv->preview_file); - else - return NULL; -} - -static GtkFileSystem * -gtk_file_chooser_default_get_file_system (GtkFileChooser *chooser) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - GtkFileChooserDefaultPrivate *priv = impl->priv; - - return priv->file_system; -} - -/* Shows or hides the filter widgets */ -static void -show_filters (GtkFileChooserDefault *impl, - gboolean show) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (show) - gtk_widget_show (priv->filter_combo_hbox); - else - gtk_widget_hide (priv->filter_combo_hbox); -} - -static void -gtk_file_chooser_default_add_filter (GtkFileChooser *chooser, - GtkFileFilter *filter) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - GtkFileChooserDefaultPrivate *priv = impl->priv; - const gchar *name; - - if (g_slist_find (priv->filters, filter)) - { - g_warning ("gtk_file_chooser_add_filter() called on filter already in list\n"); - return; - } - - g_object_ref_sink (filter); - priv->filters = g_slist_append (priv->filters, filter); - - name = gtk_file_filter_get_name (filter); - if (!name) - name = "Untitled filter"; /* Place-holder, doesn't need to be marked for translation */ - - gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (priv->filter_combo), name); - - if (!g_slist_find (priv->filters, priv->current_filter)) - set_current_filter (impl, filter); - - show_filters (impl, TRUE); -} - -static void -gtk_file_chooser_default_remove_filter (GtkFileChooser *chooser, - GtkFileFilter *filter) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkTreeModel *model; - GtkTreeIter iter; - gint filter_index; - - filter_index = g_slist_index (priv->filters, filter); - - if (filter_index < 0) - { - g_warning ("gtk_file_chooser_remove_filter() called on filter not in list\n"); - return; - } - - priv->filters = g_slist_remove (priv->filters, filter); - - if (filter == priv->current_filter) - { - if (priv->filters) - set_current_filter (impl, priv->filters->data); - else - set_current_filter (impl, NULL); - } - - /* Remove row from the combo box */ - model = gtk_combo_box_get_model (GTK_COMBO_BOX (priv->filter_combo)); - if (!gtk_tree_model_iter_nth_child (model, &iter, NULL, filter_index)) - g_assert_not_reached (); - - gtk_list_store_remove (GTK_LIST_STORE (model), &iter); - - g_object_unref (filter); - - if (!priv->filters) - show_filters (impl, FALSE); -} - -static GSList * -gtk_file_chooser_default_list_filters (GtkFileChooser *chooser) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - GtkFileChooserDefaultPrivate *priv = impl->priv; - - return g_slist_copy (priv->filters); -} - -static gboolean -gtk_file_chooser_default_add_shortcut_folder (GtkFileChooser *chooser, - GFile *file, - GError **error) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - GtkFileChooserDefaultPrivate *priv = impl->priv; - - gtk_places_sidebar_add_shortcut (GTK_PLACES_SIDEBAR (priv->places_sidebar), file); - return TRUE; -} - -static gboolean -gtk_file_chooser_default_remove_shortcut_folder (GtkFileChooser *chooser, - GFile *file, - GError **error) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - GtkFileChooserDefaultPrivate *priv = impl->priv; - - gtk_places_sidebar_remove_shortcut (GTK_PLACES_SIDEBAR (priv->places_sidebar), file); - return TRUE; -} - -static GSList * -gtk_file_chooser_default_list_shortcut_folders (GtkFileChooser *chooser) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - GtkFileChooserDefaultPrivate *priv = impl->priv; - - return gtk_places_sidebar_list_shortcuts (GTK_PLACES_SIDEBAR (priv->places_sidebar)); -} - -/* Guesses a size based upon font sizes */ -static void -find_good_size_from_style (GtkWidget *widget, - gint *width, - gint *height) -{ - GtkStyleContext *context; - GtkStateFlags state; - double font_size; - GdkScreen *screen; - double resolution; - - context = gtk_widget_get_style_context (widget); - state = gtk_widget_get_state_flags (widget); - - screen = gtk_widget_get_screen (widget); - if (screen) - { - resolution = gdk_screen_get_resolution (screen); - if (resolution < 0.0) /* will be -1 if the resolution is not defined in the GdkScreen */ - resolution = 96.0; - } - else - resolution = 96.0; /* wheeee */ - - gtk_style_context_get (context, state, "font-size", &font_size, NULL); - font_size = font_size * resolution / 72.0 + 0.5; - - *width = font_size * NUM_CHARS; - *height = font_size * NUM_LINES; -} - -static void -gtk_file_chooser_default_get_default_size (GtkFileChooserEmbed *chooser_embed, - gint *default_width, - gint *default_height) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser_embed); - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkRequisition req; - int x, y, width, height; - GSettings *settings; - - settings = _gtk_file_chooser_get_settings_for_widget (GTK_WIDGET (impl)); - - g_settings_get (settings, SETTINGS_KEY_WINDOW_POSITION, "(ii)", &x, &y); - g_settings_get (settings, SETTINGS_KEY_WINDOW_SIZE, "(ii)", &width, &height); - - if (x >= 0 && y >= 0 && width > 0 && height > 0) - { - *default_width = width; - *default_height = height; - return; - } - - find_good_size_from_style (GTK_WIDGET (chooser_embed), default_width, default_height); - - if (priv->preview_widget_active && - priv->preview_widget && - gtk_widget_get_visible (priv->preview_widget)) - { - gtk_widget_get_preferred_size (priv->preview_box, - &req, NULL); - *default_width += PREVIEW_HBOX_SPACING + req.width; - } - - if (priv->extra_widget && - gtk_widget_get_visible (priv->extra_widget)) - { - gtk_widget_get_preferred_size (priv->extra_align, - &req, NULL); - *default_height += gtk_box_get_spacing (GTK_BOX (chooser_embed)) + req.height; - } -} - -struct switch_folder_closure { - GtkFileChooserDefault *impl; - GFile *file; - int num_selected; -}; - -/* Used from gtk_tree_selection_selected_foreach() in switch_to_selected_folder() */ -static void -switch_folder_foreach_cb (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) -{ - struct switch_folder_closure *closure; - - closure = data; - - closure->file = _gtk_file_system_model_get_file (closure->impl->priv->browse_files_model, iter); - closure->num_selected++; -} - -/* Changes to the selected folder in the list view */ -static void -switch_to_selected_folder (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkTreeSelection *selection; - struct switch_folder_closure closure; - - /* We do this with foreach() rather than get_selected() as we may be in - * multiple selection mode - */ - - closure.impl = impl; - closure.file = NULL; - closure.num_selected = 0; - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); - gtk_tree_selection_selected_foreach (selection, switch_folder_foreach_cb, &closure); - - g_assert (closure.file && closure.num_selected == 1); - - change_folder_and_display_error (impl, closure.file, FALSE); -} - -/* Gets the GFileInfo for the selected row in the file list; assumes single - * selection mode. - */ -static GFileInfo * -get_selected_file_info_from_file_list (GtkFileChooserDefault *impl, - gboolean *had_selection) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkTreeSelection *selection; - GtkTreeIter iter; - GFileInfo *info; - - g_assert (!priv->select_multiple); - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); - if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) - { - *had_selection = FALSE; - return NULL; - } - - *had_selection = TRUE; - - info = _gtk_file_system_model_get_info (priv->browse_files_model, &iter); - return info; -} - -/* Gets the display name of the selected file in the file list; assumes single - * selection mode and that something is selected. - */ -static const gchar * -get_display_name_from_file_list (GtkFileChooserDefault *impl) -{ - GFileInfo *info; - gboolean had_selection; - - info = get_selected_file_info_from_file_list (impl, &had_selection); - g_assert (had_selection); - g_assert (info != NULL); - - return g_file_info_get_display_name (info); -} - -static void -add_custom_button_to_dialog (GtkDialog *dialog, - const gchar *mnemonic_label, - gint response_id) -{ - GtkWidget *button; - - button = gtk_button_new_with_mnemonic (mnemonic_label); - gtk_widget_set_can_default (button, TRUE); - gtk_widget_show (button); - - gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, response_id); -} - -/* Presents an overwrite confirmation dialog; returns whether we should accept - * the filename. - */ -static gboolean -confirm_dialog_should_accept_filename (GtkFileChooserDefault *impl, - const gchar *file_part, - const gchar *folder_display_name) -{ - GtkWindow *toplevel; - GtkWidget *dialog; - int response; - - toplevel = get_toplevel (GTK_WIDGET (impl)); - - dialog = gtk_message_dialog_new (toplevel, - GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_QUESTION, - GTK_BUTTONS_NONE, - _("A file named “%s” already exists. Do you want to replace it?"), - file_part); - gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), - _("The file already exists in “%s”. Replacing it will " - "overwrite its contents."), - folder_display_name); - - gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Cancel"), GTK_RESPONSE_CANCEL); - add_custom_button_to_dialog (GTK_DIALOG (dialog), _("_Replace"), GTK_RESPONSE_ACCEPT); -G_GNUC_BEGIN_IGNORE_DEPRECATIONS - gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), - GTK_RESPONSE_ACCEPT, - GTK_RESPONSE_CANCEL, - -1); -G_GNUC_END_IGNORE_DEPRECATIONS - gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT); - - if (gtk_window_has_group (toplevel)) - gtk_window_group_add_window (gtk_window_get_group (toplevel), - GTK_WINDOW (dialog)); - - response = gtk_dialog_run (GTK_DIALOG (dialog)); - - gtk_widget_destroy (dialog); - - return (response == GTK_RESPONSE_ACCEPT); -} - -struct GetDisplayNameData -{ - GtkFileChooserDefault *impl; - gchar *file_part; -}; - -/* Every time we request a response explicitly, we need to save the selection to the recently-used list, - * as requesting a response means, "the dialog is confirmed". - */ -static void -request_response_and_add_to_recent_list (GtkFileChooserDefault *impl) -{ - g_signal_emit_by_name (impl, "response-requested"); - add_selection_to_recent_list (impl); -} - -static void -confirmation_confirm_get_info_cb (GCancellable *cancellable, - GFileInfo *info, - const GError *error, - gpointer user_data) -{ - gboolean cancelled = g_cancellable_is_cancelled (cancellable); - gboolean should_respond = FALSE; - struct GetDisplayNameData *data = user_data; - GtkFileChooserDefaultPrivate *priv = data->impl->priv; - - if (cancellable != priv->should_respond_get_info_cancellable) - goto out; - - priv->should_respond_get_info_cancellable = NULL; - - if (cancelled) - goto out; - - if (error) - /* Huh? Did the folder disappear? Let the caller deal with it */ - should_respond = TRUE; - else - should_respond = confirm_dialog_should_accept_filename (data->impl, data->file_part, g_file_info_get_display_name (info)); - - set_busy_cursor (data->impl, FALSE); - if (should_respond) - request_response_and_add_to_recent_list (data->impl); - -out: - g_object_unref (data->impl); - g_free (data->file_part); - g_free (data); - - g_object_unref (cancellable); -} - -/* Does overwrite confirmation if appropriate, and returns whether the dialog - * should respond. Can get the file part from the file list or the save entry. - */ -static gboolean -should_respond_after_confirm_overwrite (GtkFileChooserDefault *impl, - const gchar *file_part, - GFile *parent_file) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkFileChooserConfirmation conf; - - if (!priv->do_overwrite_confirmation) - return TRUE; - - conf = GTK_FILE_CHOOSER_CONFIRMATION_CONFIRM; - - g_signal_emit_by_name (impl, "confirm-overwrite", &conf); - - switch (conf) - { - case GTK_FILE_CHOOSER_CONFIRMATION_CONFIRM: - { - struct GetDisplayNameData *data; - - g_assert (file_part != NULL); - - data = g_new0 (struct GetDisplayNameData, 1); - data->impl = g_object_ref (impl); - data->file_part = g_strdup (file_part); - - if (priv->should_respond_get_info_cancellable) - g_cancellable_cancel (priv->should_respond_get_info_cancellable); - - priv->should_respond_get_info_cancellable = - _gtk_file_system_get_info (priv->file_system, parent_file, - "standard::display-name", - confirmation_confirm_get_info_cb, - data); - set_busy_cursor (data->impl, TRUE); - return FALSE; - } - - case GTK_FILE_CHOOSER_CONFIRMATION_ACCEPT_FILENAME: - return TRUE; - - case GTK_FILE_CHOOSER_CONFIRMATION_SELECT_AGAIN: - return FALSE; - - default: - g_assert_not_reached (); - return FALSE; - } -} - -struct FileExistsData -{ - GtkFileChooserDefault *impl; - gboolean file_exists_and_is_not_folder; - GFile *parent_file; - GFile *file; -}; - -static void -name_entry_get_parent_info_cb (GCancellable *cancellable, - GFileInfo *info, - const GError *error, - gpointer user_data) -{ - gboolean parent_is_folder; - gboolean cancelled = g_cancellable_is_cancelled (cancellable); - struct FileExistsData *data = user_data; - GtkFileChooserDefault *impl = data->impl; - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (cancellable != priv->should_respond_get_info_cancellable) - goto out; - - priv->should_respond_get_info_cancellable = NULL; - - set_busy_cursor (impl, FALSE); - - if (cancelled) - goto out; - - if (!info) - parent_is_folder = FALSE; - else - parent_is_folder = _gtk_file_info_consider_as_directory (info); - - if (parent_is_folder) - { - if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN) - { - 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) */ - } - else if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) - { - if (data->file_exists_and_is_not_folder) - { - gboolean retval; - char *file_part; - - /* Dup the string because the string may be modified - * depending on what clients do in the confirm-overwrite - * signal and this corrupts the pointer - */ - file_part = g_strdup (_gtk_file_chooser_entry_get_file_part (GTK_FILE_CHOOSER_ENTRY (priv->location_entry))); - retval = should_respond_after_confirm_overwrite (impl, file_part, data->parent_file); - g_free (file_part); - - if (retval) - request_response_and_add_to_recent_list (impl); - } - else - request_response_and_add_to_recent_list (impl); - } - else if (priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER - || priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) - { - GError *mkdir_error = NULL; - - /* In both cases (SELECT_FOLDER and CREATE_FOLDER), if you type - * "/blah/nonexistent" you *will* want a folder created. - */ - - set_busy_cursor (impl, TRUE); - g_file_make_directory (data->file, NULL, &mkdir_error); - set_busy_cursor (impl, FALSE); - - if (!mkdir_error) - request_response_and_add_to_recent_list (impl); - else - error_creating_folder_dialog (impl, data->file, mkdir_error); - } - else - g_assert_not_reached (); - } - else - { - if (info) - { - /* The parent exists, but it's not a folder! Someone probably typed existing_file.txt/subfile.txt */ - error_with_file_under_nonfolder (impl, data->parent_file); - } - else - { - GError *error_copy; - - /* The parent folder is not readable for some reason */ - - error_copy = g_error_copy (error); - error_changing_folder_dialog (impl, data->parent_file, error_copy); - } - } - -out: - g_object_unref (data->impl); - g_object_unref (data->file); - g_object_unref (data->parent_file); - g_free (data); - - g_object_unref (cancellable); -} - -static void -file_exists_get_info_cb (GCancellable *cancellable, - GFileInfo *info, - const GError *error, - gpointer user_data) -{ - gboolean data_ownership_taken = FALSE; - gboolean cancelled = g_cancellable_is_cancelled (cancellable); - gboolean file_exists; - gboolean is_folder; - gboolean needs_parent_check = FALSE; - struct FileExistsData *data = user_data; - GtkFileChooserDefault *impl = data->impl; - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (cancellable != priv->file_exists_get_info_cancellable) - goto out; - - priv->file_exists_get_info_cancellable = NULL; - - set_busy_cursor (impl, FALSE); - - if (cancelled) - goto out; - - file_exists = (info != NULL); - is_folder = (file_exists && _gtk_file_info_consider_as_directory (info)); - - if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN) - { - if (is_folder) - change_folder_and_display_error (impl, data->file, TRUE); - else - { - if (file_exists) - request_response_and_add_to_recent_list (impl); /* user typed an existing filename; we are done */ - else - needs_parent_check = TRUE; /* file doesn't exist; see if its parent exists */ - } - } - else if (priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) - { - if (file_exists && !is_folder) - { - /* Oops, the user typed the name of an existing path which is not - * a folder - */ - error_creating_folder_over_existing_file_dialog (impl, data->file, - g_error_copy (error)); - } - else - { - needs_parent_check = TRUE; - } - } - else if (priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) - { - if (!file_exists) - { - needs_parent_check = TRUE; - } - else - { - if (is_folder) - { - /* User typed a folder; we are done */ - request_response_and_add_to_recent_list (impl); - } - else - error_selecting_folder_over_existing_file_dialog (impl); - } - } - else if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) - { - if (is_folder) - change_folder_and_display_error (impl, data->file, TRUE); - else - if (!file_exists && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_FILENAME_TOO_LONG)) - error_filename_to_long_dialog (data->impl); - else - needs_parent_check = TRUE; - } - else - { - g_assert_not_reached(); - } - - if (needs_parent_check) { - /* check that everything up to the last path component exists (i.e. the parent) */ - - data->file_exists_and_is_not_folder = file_exists && !is_folder; - data_ownership_taken = TRUE; - - if (priv->should_respond_get_info_cancellable) - g_cancellable_cancel (priv->should_respond_get_info_cancellable); - - priv->should_respond_get_info_cancellable = - _gtk_file_system_get_info (priv->file_system, - data->parent_file, - "standard::type", - name_entry_get_parent_info_cb, - data); - set_busy_cursor (impl, TRUE); - } - -out: - if (!data_ownership_taken) - { - g_object_unref (impl); - g_object_unref (data->file); - g_object_unref (data->parent_file); - g_free (data); - } - - g_object_unref (cancellable); -} - -static void -paste_text_received (GtkClipboard *clipboard, - const gchar *text, - GtkFileChooserDefault *impl) -{ - GFile *file; - - if (!text) - return; - - file = g_file_new_for_uri (text); - - if (!gtk_file_chooser_default_select_file (GTK_FILE_CHOOSER (impl), file, NULL)) - location_popup_handler (impl, text); - - g_object_unref (file); -} - -/* Handler for the "location-popup-on-paste" keybinding signal */ -static void -location_popup_on_paste_handler (GtkFileChooserDefault *impl) -{ - GtkClipboard *clipboard = gtk_widget_get_clipboard (GTK_WIDGET (impl), - GDK_SELECTION_CLIPBOARD); - gtk_clipboard_request_text (clipboard, - (GtkClipboardTextReceivedFunc) paste_text_received, - impl); -} - -/* Implementation for GtkFileChooserEmbed::should_respond() */ -static void -add_selection_to_recent_list (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GSList *files; - GSList *l; - - files = gtk_file_chooser_default_get_files (GTK_FILE_CHOOSER (impl)); - - for (l = files; l; l = l->next) - { - GFile *file = l->data; - char *uri; - - uri = g_file_get_uri (file); - if (uri) - { - gtk_recent_manager_add_item (priv->recent_manager, uri); - g_free (uri); - } - } - - g_slist_foreach (files, (GFunc) g_object_unref, NULL); - g_slist_free (files); -} - -static gboolean -gtk_file_chooser_default_should_respond (GtkFileChooserEmbed *chooser_embed) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser_embed); - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkWidget *toplevel; - GtkWidget *current_focus; - gboolean retval; - - toplevel = gtk_widget_get_toplevel (GTK_WIDGET (impl)); - g_assert (GTK_IS_WINDOW (toplevel)); - - retval = FALSE; - - current_focus = gtk_window_get_focus (GTK_WINDOW (toplevel)); - - if (current_focus == priv->browse_files_tree_view) - { - /* The following array encodes what we do based on the priv->action and the - * number of files selected. - */ - typedef enum { - NOOP, /* Do nothing (don't respond) */ - RESPOND, /* Respond immediately */ - RESPOND_OR_SWITCH, /* Respond immediately if the selected item is a file; switch to it if it is a folder */ - ALL_FILES, /* Respond only if everything selected is a file */ - ALL_FOLDERS, /* Respond only if everything selected is a folder */ - SAVE_ENTRY, /* Go to the code for handling the save entry */ - NOT_REACHED /* Sanity check */ - } ActionToTake; - static const ActionToTake what_to_do[4][3] = { - /* 0 selected 1 selected many selected */ - /* ACTION_OPEN */ { NOOP, RESPOND_OR_SWITCH, ALL_FILES }, - /* ACTION_SAVE */ { SAVE_ENTRY, RESPOND_OR_SWITCH, NOT_REACHED }, - /* ACTION_SELECT_FOLDER */ { RESPOND, ALL_FOLDERS, ALL_FOLDERS }, - /* ACTION_CREATE_FOLDER */ { SAVE_ENTRY, ALL_FOLDERS, NOT_REACHED } - }; - - int num_selected; - gboolean all_files, all_folders; - int k; - ActionToTake action; - - file_list: - - g_assert (priv->action >= GTK_FILE_CHOOSER_ACTION_OPEN && priv->action <= GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER); - - if (priv->operation_mode == OPERATION_MODE_SEARCH) - { - retval = search_should_respond (impl); - goto out; - } - - if (priv->operation_mode == OPERATION_MODE_RECENT) - { - if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) - goto save_entry; - else - { - retval = recent_should_respond (impl); - goto out; - } - } - - selection_check (impl, &num_selected, &all_files, &all_folders); - - if (num_selected > 2) - k = 2; - else - k = num_selected; - - action = what_to_do [priv->action] [k]; - - switch (action) - { - case NOOP: - return FALSE; - - case RESPOND: - retval = TRUE; - goto out; - - case RESPOND_OR_SWITCH: - g_assert (num_selected == 1); - - if (all_folders) - { - switch_to_selected_folder (impl); - return FALSE; - } - else if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) - { - retval = should_respond_after_confirm_overwrite (impl, - get_display_name_from_file_list (impl), - priv->current_folder); - goto out; - } - else - { - retval = TRUE; - goto out; - } - - case ALL_FILES: - retval = all_files; - goto out; - - case ALL_FOLDERS: - retval = all_folders; - goto out; - - case SAVE_ENTRY: - goto save_entry; - - default: - g_assert_not_reached (); - } - } - else if ((priv->location_entry != NULL) && (current_focus == priv->location_entry)) - { - GFile *file; - gboolean is_well_formed, is_empty, is_file_part_empty; - gboolean is_folder; - GtkFileChooserEntry *entry; - GError *error; - - save_entry: - - g_assert (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE - || priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER - || ((priv->action == GTK_FILE_CHOOSER_ACTION_OPEN - || priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) - && priv->location_mode == LOCATION_MODE_FILENAME_ENTRY)); - - entry = GTK_FILE_CHOOSER_ENTRY (priv->location_entry); - check_save_entry (impl, &file, &is_well_formed, &is_empty, &is_file_part_empty, &is_folder); - - if (!is_well_formed) - { - if (!is_empty - && priv->action == GTK_FILE_CHOOSER_ACTION_SAVE - && priv->operation_mode == OPERATION_MODE_RECENT) - { - path_bar_set_mode (impl, PATH_BAR_ERROR_NO_FOLDER); -#if 0 - /* We'll #ifdef this out, as the fucking treeview selects its first row, - * thus changing our assumption that no selection is present - setting - * a selection causes the error message from path_bar_set_mode() to go away, - * but we want the user to see that message! - */ - gtk_widget_grab_focus (priv->browse_files_tree_view); -#endif - } - /* FIXME: else show an "invalid filename" error as the pathbar mode? */ - - return FALSE; - } - - if (is_empty) - { - if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE - || priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) - { - path_bar_set_mode (impl, PATH_BAR_ERROR_NO_FILENAME); - gtk_widget_grab_focus (priv->location_entry); - return FALSE; - } - - goto file_list; - } - - g_assert (file != NULL); - - error = NULL; - if (is_folder) - { - if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN || - priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) - { - change_folder_and_display_error (impl, file, TRUE); - } - else if (priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER || - priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) - { - /* The folder already exists, so we do not need to create it. - * Just respond to terminate the dialog. - */ - retval = TRUE; - } - else - { - g_assert_not_reached (); - } - } - else - { - struct FileExistsData *data; - - /* We need to check whether file exists and whether it is a folder - - * the GtkFileChooserEntry *does* report is_folder==FALSE as a false - * negative (it doesn't know yet if your last path component is a - * folder). - */ - - data = g_new0 (struct FileExistsData, 1); - data->impl = g_object_ref (impl); - data->file = g_object_ref (file); - data->parent_file = _gtk_file_chooser_entry_get_current_folder (entry); - - if (priv->file_exists_get_info_cancellable) - g_cancellable_cancel (priv->file_exists_get_info_cancellable); - - priv->file_exists_get_info_cancellable = - _gtk_file_system_get_info (priv->file_system, file, - "standard::type", - file_exists_get_info_cb, - data); - - set_busy_cursor (impl, TRUE); - - if (error != NULL) - g_error_free (error); - } - - g_object_unref (file); - } - else if (priv->toplevel_last_focus_widget == priv->browse_files_tree_view) - { - /* The focus is on a dialog's action area button, *and* the widget that - * was focused immediately before it is the file list. - */ - goto file_list; - } - else if (priv->operation_mode == OPERATION_MODE_SEARCH && priv->toplevel_last_focus_widget == priv->search_entry) - { - search_entry_activate_cb (GTK_ENTRY (priv->search_entry), impl); - return FALSE; - } - else if (priv->location_entry && priv->toplevel_last_focus_widget == priv->location_entry) - { - /* The focus is on a dialog's action area button, *and* the widget that - * was focused immediately before it is the location entry. - */ - goto save_entry; - } - else - /* The focus is on a dialog's action area button or something else */ - if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE - || priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) - goto save_entry; - else - goto file_list; - - out: - - if (retval) - add_selection_to_recent_list (impl); - - return retval; -} - -/* Implementation for GtkFileChooserEmbed::initial_focus() */ -static void -gtk_file_chooser_default_initial_focus (GtkFileChooserEmbed *chooser_embed) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser_embed); - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkWidget *widget; - - if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN || - priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) - { - if (priv->location_mode == LOCATION_MODE_PATH_BAR - || priv->operation_mode == OPERATION_MODE_RECENT) - widget = priv->browse_files_tree_view; - else - widget = priv->location_entry; - } - else if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE || - priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) - widget = priv->location_entry; - else - { - g_assert_not_reached (); - widget = NULL; - } - - g_assert (widget != NULL); - gtk_widget_grab_focus (widget); -} - -/* Callback used from gtk_tree_selection_selected_foreach(); gets the selected GFiles */ -static void -search_selected_foreach_get_file_cb (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) -{ - GSList **list; - GFile *file; - - list = data; - - gtk_tree_model_get (model, iter, MODEL_COL_FILE, &file, -1); - *list = g_slist_prepend (*list, file); /* The file already has a new ref courtesy of gtk_tree_model_get(); this will be unreffed by the caller */ -} - -/* Constructs a list of the selected paths in search mode */ -static GSList * -search_get_selected_files (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GSList *result; - GtkTreeSelection *selection; - - result = NULL; - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); - gtk_tree_selection_selected_foreach (selection, search_selected_foreach_get_file_cb, &result); - result = g_slist_reverse (result); - - return result; -} - -/* Called from ::should_respond(). We return whether there are selected files - * in the search list. - */ -static gboolean -search_should_respond (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkTreeSelection *selection; - - g_assert (priv->operation_mode == OPERATION_MODE_SEARCH); - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); - return (gtk_tree_selection_count_selected_rows (selection) != 0); -} - -/* Adds one hit from the search engine to the search_model */ -static void -search_add_hit (GtkFileChooserDefault *impl, - gchar *uri) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GFile *file; - - file = g_file_new_for_uri (uri); - if (!file) - return; - - if (!g_file_is_native (file)) - { - g_object_unref (file); - return; - } - - _gtk_file_system_model_add_and_query_file (priv->search_model, - file, - MODEL_ATTRIBUTES); - - g_object_unref (file); -} - -/* Callback used from GtkSearchEngine when we get new hits */ -static void -search_engine_hits_added_cb (GtkSearchEngine *engine, - GList *hits, - gpointer data) -{ - GtkFileChooserDefault *impl; - GList *l; - - impl = GTK_FILE_CHOOSER_DEFAULT (data); - - for (l = hits; l; l = l->next) - search_add_hit (impl, (gchar*)l->data); -} - -/* Callback used from GtkSearchEngine when the query is done running */ -static void -search_engine_finished_cb (GtkSearchEngine *engine, - gpointer data) -{ - GtkFileChooserDefault *impl; - - impl = GTK_FILE_CHOOSER_DEFAULT (data); - -#if 0 - /* EB: setting the model here will avoid loads of row events, - * but it'll make the search look like blocked. - */ - gtk_tree_view_set_model (GTK_TREE_VIEW (impl->browse_files_tree_view), - GTK_TREE_MODEL (impl->search_model)); - file_list_set_sort_column_ids (impl); -#endif - - /* FMQ: if search was empty, say that we got no hits */ - set_busy_cursor (impl, FALSE); -} - -/* Displays a generic error when we cannot create a GtkSearchEngine. - * It would be better if _gtk_search_engine_new() gave us a GError - * with a better message, but it doesn't do that right now. - */ -static void -search_error_could_not_create_client (GtkFileChooserDefault *impl) -{ - error_message (impl, - _("Could not start the search process"), - _("The program was not able to create a connection to the indexer " - "daemon. Please make sure it is running.")); -} - -static void -search_engine_error_cb (GtkSearchEngine *engine, - const gchar *message, - gpointer data) -{ - GtkFileChooserDefault *impl; - - impl = GTK_FILE_CHOOSER_DEFAULT (data); - - search_stop_searching (impl, TRUE); - error_message (impl, _("Could not send the search request"), message); - - set_busy_cursor (impl, FALSE); -} - -/* Frees the data in the search_model */ -static void -search_clear_model (GtkFileChooserDefault *impl, - gboolean remove_from_treeview) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (!priv->search_model) - return; - - g_object_unref (priv->search_model); - priv->search_model = NULL; - - if (remove_from_treeview) - gtk_tree_view_set_model (GTK_TREE_VIEW (priv->browse_files_tree_view), NULL); -} - -/* Stops any ongoing searches; does not touch the search_model */ -static void -search_stop_searching (GtkFileChooserDefault *impl, - gboolean remove_query) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (remove_query && priv->search_query) - { - g_object_unref (priv->search_query); - priv->search_query = NULL; - } - - if (priv->search_engine) - { - _gtk_search_engine_stop (priv->search_engine); - - g_object_unref (priv->search_engine); - priv->search_engine = NULL; - } -} - -/* Creates the search_model and puts it in the tree view */ -static void -search_setup_model (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - g_assert (priv->search_model == NULL); - - priv->search_model = _gtk_file_system_model_new (file_system_model_set, - impl, - MODEL_COLUMN_TYPES); - - gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->search_model), - MODEL_COL_NAME, - name_sort_func, - impl, NULL); - gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->search_model), - MODEL_COL_MTIME, - mtime_sort_func, - impl, NULL); - gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->search_model), - MODEL_COL_SIZE, - size_sort_func, - impl, NULL); - set_sort_column (impl); - - /* EB: setting the model here will make the hits list update feel - * more "alive" than setting the model at the end of the search - * run - */ - gtk_tree_view_set_model (GTK_TREE_VIEW (priv->browse_files_tree_view), - GTK_TREE_MODEL (priv->search_model)); - file_list_set_sort_column_ids (impl); -} - -/* Creates a new query with the specified text and launches it */ -static void -search_start_query (GtkFileChooserDefault *impl, - const gchar *query_text) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - search_stop_searching (impl, FALSE); - search_clear_model (impl, TRUE); - search_setup_model (impl); - set_busy_cursor (impl, TRUE); - - if (priv->search_engine == NULL) - priv->search_engine = _gtk_search_engine_new (); - - if (!priv->search_engine) - { - set_busy_cursor (impl, FALSE); - search_error_could_not_create_client (impl); /* lame; we don't get an error code or anything */ - return; - } - - if (!priv->search_query) - { - priv->search_query = _gtk_query_new (); - _gtk_query_set_text (priv->search_query, query_text); - } - - _gtk_search_engine_set_query (priv->search_engine, priv->search_query); - - g_signal_connect (priv->search_engine, "hits-added", - G_CALLBACK (search_engine_hits_added_cb), impl); - g_signal_connect (priv->search_engine, "finished", - G_CALLBACK (search_engine_finished_cb), impl); - g_signal_connect (priv->search_engine, "error", - G_CALLBACK (search_engine_error_cb), impl); - - _gtk_search_engine_start (priv->search_engine); -} - -/* Callback used when the user presses Enter while typing on the search - * entry; starts the query - */ -static void -search_entry_activate_cb (GtkEntry *entry, - gpointer data) -{ - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (data); - GtkFileChooserDefaultPrivate *priv = impl->priv; - const char *text; - - text = gtk_entry_get_text (GTK_ENTRY (priv->search_entry)); - if (strlen (text) == 0) - return; - - /* reset any existing query object */ - if (priv->search_query) - { - g_object_unref (priv->search_query); - priv->search_query = NULL; - } - - search_start_query (impl, text); -} - -static gboolean -focus_entry_idle_cb (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - gdk_threads_enter (); - - g_source_destroy (priv->focus_entry_idle); - priv->focus_entry_idle = NULL; - - if (priv->search_entry) - gtk_widget_grab_focus (priv->search_entry); - - gdk_threads_leave (); - - return FALSE; -} - -static void -focus_search_entry_in_idle (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - /* bgo#634558 - When the user clicks on the Search entry in the shortcuts - * pane, we get a selection-changed signal and we set up the search widgets. - * However, gtk_tree_view_button_press() focuses the treeview *after* making - * the change to the selection. So, we need to re-focus the search entry - * after the treeview has finished doing its work; we'll do that in an idle - * handler. - */ - - if (!priv->focus_entry_idle) - priv->focus_entry_idle = add_idle_while_impl_is_alive (impl, G_CALLBACK (focus_entry_idle_cb)); -} - -/* Hides the path bar and creates the search entry */ -static void -search_setup_widgets (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - priv->search_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); - - path_bar_update (impl); - - priv->search_entry = gtk_entry_new (); - g_signal_connect (priv->search_entry, "activate", - G_CALLBACK (search_entry_activate_cb), - impl); - gtk_box_pack_start (GTK_BOX (priv->search_hbox), priv->search_entry, TRUE, TRUE, 0); - - /* if there already is a query, restart it */ - if (priv->search_query) - { - gchar *query = _gtk_query_get_text (priv->search_query); - - if (query) - { - gtk_entry_set_text (GTK_ENTRY (priv->search_entry), query); - search_start_query (impl, query); - - g_free (query); - } - else - { - g_object_unref (priv->search_query); - priv->search_query = NULL; - } - } - - /* Box for search widgets */ - gtk_box_pack_start (GTK_BOX (priv->browse_path_bar_hbox), priv->search_hbox, TRUE, TRUE, 0); - gtk_widget_show_all (priv->search_hbox); - gtk_size_group_add_widget (GTK_SIZE_GROUP (priv->browse_path_bar_size_group), priv->search_hbox); - - /* Hide the location widgets temporarily */ - - if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN || - priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) - { - gtk_widget_hide (priv->location_button); - gtk_widget_hide (priv->location_entry_box); - } - - focus_search_entry_in_idle (impl); - - /* FMQ: hide the filter combo? */ -} - -/* - * Recent files support - */ - -/* Frees the data in the recent_model */ -static void -recent_clear_model (GtkFileChooserDefault *impl, - gboolean remove_from_treeview) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (!priv->recent_model) - return; - - if (remove_from_treeview) - gtk_tree_view_set_model (GTK_TREE_VIEW (priv->browse_files_tree_view), NULL); - - g_object_unref (priv->recent_model); - priv->recent_model = NULL; -} - -/* Stops any ongoing loading of the recent files list; does - * not touch the recent_model - */ -static void -recent_stop_loading (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (priv->load_recent_id) - { - g_source_remove (priv->load_recent_id); - priv->load_recent_id = 0; - } -} - -static void -recent_setup_model (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - g_assert (priv->recent_model == NULL); - - priv->recent_model = _gtk_file_system_model_new (file_system_model_set, - impl, - MODEL_COLUMN_TYPES); - - _gtk_file_system_model_set_filter (priv->recent_model, - priv->current_filter); - gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->recent_model), - MODEL_COL_NAME, - name_sort_func, - impl, NULL); - gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->recent_model), - MODEL_COL_SIZE, - size_sort_func, - impl, NULL); - gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->recent_model), - MODEL_COL_MTIME, - mtime_sort_func, - impl, NULL); - set_sort_column (impl); -} - -typedef struct -{ - GtkFileChooserDefault *impl; - GList *items; -} RecentLoadData; - -static void -recent_idle_cleanup (gpointer data) -{ - RecentLoadData *load_data = data; - GtkFileChooserDefault *impl = load_data->impl; - GtkFileChooserDefaultPrivate *priv = impl->priv; - - gtk_tree_view_set_model (GTK_TREE_VIEW (priv->browse_files_tree_view), - GTK_TREE_MODEL (priv->recent_model)); - file_list_set_sort_column_ids (impl); - gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (priv->recent_model), MODEL_COL_MTIME, GTK_SORT_DESCENDING); - - set_busy_cursor (impl, FALSE); - - priv->load_recent_id = 0; - - g_free (load_data); -} - -/* Populates the file system model with the GtkRecentInfo* items in the provided list; frees the items */ -static void -populate_model_with_recent_items (GtkFileChooserDefault *impl, GList *items) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - gint limit; - GList *l; - int n; - - limit = DEFAULT_RECENT_FILES_LIMIT; - - n = 0; - - for (l = items; l; l = l->next) - { - GtkRecentInfo *info = l->data; - GFile *file; - - file = g_file_new_for_uri (gtk_recent_info_get_uri (info)); - _gtk_file_system_model_add_and_query_file (priv->recent_model, - file, - MODEL_ATTRIBUTES); - g_object_unref (file); - - n++; - if (limit != -1 && n >= limit) - break; - } -} - -static void -populate_model_with_folders (GtkFileChooserDefault *impl, GList *items) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GList *folders; - GList *l; - - folders = _gtk_file_chooser_extract_recent_folders (items); - - for (l = folders; l; l = l->next) - { - GFile *folder = l->data; - - _gtk_file_system_model_add_and_query_file (priv->recent_model, - folder, - MODEL_ATTRIBUTES); - } - - g_list_foreach (folders, (GFunc) g_object_unref, NULL); - g_list_free (folders); -} - -static gboolean -recent_idle_load (gpointer data) -{ - RecentLoadData *load_data = data; - GtkFileChooserDefault *impl = load_data->impl; - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (!priv->recent_manager) - return FALSE; - - load_data->items = gtk_recent_manager_get_items (priv->recent_manager); - if (!load_data->items) - return FALSE; - - if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN) - populate_model_with_recent_items (impl, load_data->items); - else - populate_model_with_folders (impl, load_data->items); - - g_list_foreach (load_data->items, (GFunc) gtk_recent_info_unref, NULL); - g_list_free (load_data->items); - load_data->items = NULL; - - return FALSE; -} - -static void -recent_start_loading (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - RecentLoadData *load_data; - - recent_stop_loading (impl); - recent_clear_model (impl, TRUE); - recent_setup_model (impl); - set_busy_cursor (impl, TRUE); - - g_assert (priv->load_recent_id == 0); - - load_data = g_new (RecentLoadData, 1); - load_data->impl = impl; - load_data->items = NULL; - - /* begin lazy loading the recent files into the model */ - priv->load_recent_id = gdk_threads_add_idle_full (G_PRIORITY_HIGH_IDLE + 30, - recent_idle_load, - load_data, - recent_idle_cleanup); -} - -static void -recent_selected_foreach_get_file_cb (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) -{ - GSList **list; - GFile *file; - - list = data; - - gtk_tree_model_get (model, iter, MODEL_COL_FILE, &file, -1); - *list = g_slist_prepend (*list, file); -} - -/* Constructs a list of the selected paths in recent files mode */ -static GSList * -recent_get_selected_files (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GSList *result; - GtkTreeSelection *selection; - - result = NULL; - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); - gtk_tree_selection_selected_foreach (selection, recent_selected_foreach_get_file_cb, &result); - result = g_slist_reverse (result); - - return result; -} - -/* Called from ::should_respond(). We return whether there are selected - * files in the recent files list. - */ -static gboolean -recent_should_respond (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkTreeSelection *selection; - - g_assert (priv->operation_mode == OPERATION_MODE_RECENT); - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); - return (gtk_tree_selection_count_selected_rows (selection) != 0); -} - -static void -set_current_filter (GtkFileChooserDefault *impl, - GtkFileFilter *filter) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (priv->current_filter != filter) - { - int filter_index; - - /* NULL filters are allowed to reset to non-filtered status - */ - filter_index = g_slist_index (priv->filters, filter); - if (priv->filters && filter && filter_index < 0) - return; - - if (priv->current_filter) - g_object_unref (priv->current_filter); - priv->current_filter = filter; - if (priv->current_filter) - { - g_object_ref_sink (priv->current_filter); - } - - if (priv->filters) - gtk_combo_box_set_active (GTK_COMBO_BOX (priv->filter_combo), - filter_index); - - if (priv->browse_files_model) - { - _gtk_file_system_model_set_filter (priv->browse_files_model, priv->current_filter); - _gtk_file_system_model_clear_cache (priv->browse_files_model, MODEL_COL_IS_SENSITIVE); - } - - if (priv->search_model) - { - _gtk_file_system_model_set_filter (priv->search_model, filter); - _gtk_file_system_model_clear_cache (priv->search_model, MODEL_COL_IS_SENSITIVE); - } - - if (priv->recent_model) - { - _gtk_file_system_model_set_filter (priv->recent_model, filter); - _gtk_file_system_model_clear_cache (priv->recent_model, MODEL_COL_IS_SENSITIVE); - } - - g_object_notify (G_OBJECT (impl), "filter"); - } -} - -static void -filter_combo_changed (GtkComboBox *combo_box, - GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - gint new_index = gtk_combo_box_get_active (combo_box); - GtkFileFilter *new_filter = g_slist_nth_data (priv->filters, new_index); - - set_current_filter (impl, new_filter); -} - -static void -check_preview_change (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkTreePath *cursor_path; - GFile *new_file; - char *new_display_name; - GtkTreeModel *model; - - gtk_tree_view_get_cursor (GTK_TREE_VIEW (priv->browse_files_tree_view), &cursor_path, NULL); - model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->browse_files_tree_view)); - if (cursor_path) - { - GtkTreeIter iter; - - gtk_tree_model_get_iter (model, &iter, cursor_path); - gtk_tree_model_get (model, &iter, - MODEL_COL_FILE, &new_file, - MODEL_COL_NAME, &new_display_name, - -1); - - gtk_tree_path_free (cursor_path); - } - else - { - new_file = NULL; - new_display_name = NULL; - } - - if (new_file != priv->preview_file && - !(new_file && priv->preview_file && - g_file_equal (new_file, priv->preview_file))) - { - if (priv->preview_file) - { - g_object_unref (priv->preview_file); - g_free (priv->preview_display_name); - } - - if (new_file) - { - priv->preview_file = new_file; - priv->preview_display_name = new_display_name; - } - else - { - priv->preview_file = NULL; - priv->preview_display_name = NULL; - g_free (new_display_name); - } - - if (priv->use_preview_label && priv->preview_label) - gtk_label_set_text (GTK_LABEL (priv->preview_label), priv->preview_display_name); - - g_signal_emit_by_name (impl, "update-preview"); - } - else - { - if (new_file) - g_object_unref (new_file); - - g_free (new_display_name); - } -} - -static gboolean -list_select_func (GtkTreeSelection *selection, - GtkTreeModel *model, - GtkTreePath *path, - gboolean path_currently_selected, - gpointer data) -{ - GtkFileChooserDefault *impl = data; - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER || - priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) - { - GtkTreeIter iter; - gboolean is_sensitive; - gboolean is_folder; - - if (!gtk_tree_model_get_iter (model, &iter, path)) - return FALSE; - gtk_tree_model_get (model, &iter, - MODEL_COL_IS_SENSITIVE, &is_sensitive, - MODEL_COL_IS_FOLDER, &is_folder, - -1); - if (!is_sensitive || !is_folder) - return FALSE; - } - - return TRUE; -} - -static void -list_selection_changed (GtkTreeSelection *selection, - GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - /* See if we are in the new folder editable row for Save mode */ - if (priv->operation_mode == OPERATION_MODE_BROWSE && - priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) - { - GFileInfo *info; - gboolean had_selection; - - info = get_selected_file_info_from_file_list (impl, &had_selection); - if (!had_selection) - goto out; /* normal processing */ - - if (!info) - return; /* We are on the editable row for New Folder */ - } - - out: - - if (priv->location_entry) - update_chooser_entry (impl); - - path_bar_update (impl); - - check_preview_change (impl); - check_file_list_menu_sensitivity (impl); - - g_signal_emit_by_name (impl, "selection-changed", 0); -} - -/* Callback used when a row in the file list is activated */ -static void -list_row_activated (GtkTreeView *tree_view, - GtkTreePath *path, - GtkTreeViewColumn *column, - GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GFile *file; - GtkTreeIter iter; - GtkTreeModel *model; - gboolean is_folder; - gboolean is_sensitive; - - model = gtk_tree_view_get_model (tree_view); - - if (!gtk_tree_model_get_iter (model, &iter, path)) - return; - - gtk_tree_model_get (model, &iter, - MODEL_COL_FILE, &file, - MODEL_COL_IS_FOLDER, &is_folder, - MODEL_COL_IS_SENSITIVE, &is_sensitive, - -1); - - if (is_sensitive && is_folder && file) - { - change_folder_and_display_error (impl, file, FALSE); - goto out; - } - - if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN || - priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) - g_signal_emit_by_name (impl, "file-activated"); - - out: - - if (file) - g_object_unref (file); -} - -static void -path_bar_clicked (GtkPathBar *path_bar, - GFile *file, - GFile *child_file, - gboolean child_is_hidden, - GtkFileChooserDefault *impl) -{ - if (child_file) - pending_select_files_add (impl, child_file); - - if (!change_folder_and_display_error (impl, file, FALSE)) - return; - - /* Say we have "/foo/bar/[.baz]" and the user clicks on "bar". We should then - * show hidden files so that ".baz" appears in the file list, as it will still - * be shown in the path bar: "/foo/[bar]/.baz" - */ - if (child_is_hidden) - g_object_set (impl, "show-hidden", TRUE, NULL); -} - -static void -update_cell_renderer_attributes (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GtkTreeViewColumn *column; - GtkCellRenderer *renderer; - GList *walk, *list; - - /* Keep the following column numbers in sync with create_file_list() */ - - /* name */ - column = gtk_tree_view_get_column (GTK_TREE_VIEW (priv->browse_files_tree_view), 0); - list = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column)); - for (walk = list; walk; walk = walk->next) - { - renderer = walk->data; - if (GTK_IS_CELL_RENDERER_PIXBUF (renderer)) - { - gtk_tree_view_column_set_attributes (column, renderer, - "surface", MODEL_COL_SURFACE, - NULL); - } - else - { - gtk_tree_view_column_set_attributes (column, renderer, - "text", MODEL_COL_NAME, - "ellipsize", MODEL_COL_ELLIPSIZE, - NULL); - } - - gtk_tree_view_column_add_attribute (column, renderer, "sensitive", MODEL_COL_IS_SENSITIVE); - } - g_list_free (list); - - /* size */ - column = gtk_tree_view_get_column (GTK_TREE_VIEW (priv->browse_files_tree_view), 1); - list = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column)); - renderer = list->data; - gtk_tree_view_column_set_attributes (column, renderer, - "text", MODEL_COL_SIZE_TEXT, - NULL); - - gtk_tree_view_column_add_attribute (column, renderer, "sensitive", MODEL_COL_IS_SENSITIVE); - g_list_free (list); - - /* mtime */ - column = gtk_tree_view_get_column (GTK_TREE_VIEW (priv->browse_files_tree_view), 2); - list = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column)); - renderer = list->data; - gtk_tree_view_column_set_attributes (column, renderer, - "text", MODEL_COL_MTIME_TEXT, - NULL); - gtk_tree_view_column_add_attribute (column, renderer, "sensitive", MODEL_COL_IS_SENSITIVE); - g_list_free (list); -} - -GtkWidget * -_gtk_file_chooser_default_new (void) -{ - return g_object_new (GTK_TYPE_FILE_CHOOSER_DEFAULT, NULL); -} - -static void -location_set_user_text (GtkFileChooserDefault *impl, - const gchar *path) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - gtk_entry_set_text (GTK_ENTRY (priv->location_entry), path); - gtk_editable_set_position (GTK_EDITABLE (priv->location_entry), -1); -} - -static void -location_popup_handler (GtkFileChooserDefault *impl, - const gchar *path) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - if (priv->operation_mode != OPERATION_MODE_BROWSE) - { - GtkWidget *widget_to_focus; - - operation_mode_set (impl, OPERATION_MODE_BROWSE); - - if (priv->current_folder) - change_folder_and_display_error (impl, priv->current_folder, FALSE); - - if (priv->location_mode == LOCATION_MODE_PATH_BAR) - widget_to_focus = priv->browse_files_tree_view; - else - widget_to_focus = priv->location_entry; - - gtk_widget_grab_focus (widget_to_focus); - return; - } - - if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN || - priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) - { - if (!path) - return; - - location_mode_set (impl, LOCATION_MODE_FILENAME_ENTRY, TRUE); - location_set_user_text (impl, path); - } - else if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE || - priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) - { - gtk_widget_grab_focus (priv->location_entry); - if (path != NULL) - location_set_user_text (impl, path); - } - else - g_assert_not_reached (); -} - -/* Handler for the "up-folder" keybinding signal */ -static void -up_folder_handler (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - _gtk_path_bar_up (GTK_PATH_BAR (priv->browse_path_bar)); -} - -/* Handler for the "down-folder" keybinding signal */ -static void -down_folder_handler (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - _gtk_path_bar_down (GTK_PATH_BAR (priv->browse_path_bar)); -} - -/* Handler for the "home-folder" keybinding signal */ -static void -home_folder_handler (GtkFileChooserDefault *impl) -{ - switch_to_home_dir (impl); -} - -/* Handler for the "desktop-folder" keybinding signal */ -static void -desktop_folder_handler (GtkFileChooserDefault *impl) -{ - const char *name; - - name = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP); - /* "To disable a directory, point it to the homedir." - * See http://freedesktop.org/wiki/Software/xdg-user-dirs - **/ - if (!g_strcmp0 (name, g_get_home_dir ())) { - return; - } - - gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (impl), name); -} - -/* Handler for the "search-shortcut" keybinding signal */ -static void -search_shortcut_handler (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - operation_mode_set (impl, OPERATION_MODE_SEARCH); - - /* we want the entry widget to grab the focus the first - * time, not the browse_files_tree_view widget. - */ - if (priv->search_entry) - gtk_widget_grab_focus (priv->search_entry); -} - -/* Handler for the "recent-shortcut" keybinding signal */ -static void -recent_shortcut_handler (GtkFileChooserDefault *impl) -{ - operation_mode_set (impl, OPERATION_MODE_RECENT); -} - -static void -quick_bookmark_handler (GtkFileChooserDefault *impl, - gint bookmark_index) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - GFile *file; - - file = gtk_places_sidebar_get_nth_bookmark (GTK_PLACES_SIDEBAR (priv->places_sidebar), bookmark_index); - - if (file) - { - change_folder_and_display_error (impl, file, FALSE); - g_object_unref (file); - } -} - -static void -show_hidden_handler (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv = impl->priv; - - g_object_set (impl, - "show-hidden", !priv->show_hidden, - NULL); -} - -static void -add_normal_and_shifted_binding (GtkBindingSet *binding_set, - guint keyval, - GdkModifierType modifiers, - const gchar *signal_name) -{ - gtk_binding_entry_add_signal (binding_set, - keyval, modifiers, - signal_name, 0); - - gtk_binding_entry_add_signal (binding_set, - keyval, modifiers | GDK_SHIFT_MASK, - signal_name, 0); -} - -static void -_gtk_file_chooser_default_class_init (GtkFileChooserDefaultClass *class) -{ - static const guint quick_bookmark_keyvals[10] = { - 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 - }; - GObjectClass *gobject_class = G_OBJECT_CLASS (class); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); - GtkBindingSet *binding_set; - int i; - - gobject_class->finalize = gtk_file_chooser_default_finalize; - gobject_class->constructor = gtk_file_chooser_default_constructor; - gobject_class->set_property = gtk_file_chooser_default_set_property; - gobject_class->get_property = gtk_file_chooser_default_get_property; - gobject_class->dispose = gtk_file_chooser_default_dispose; - - widget_class->show_all = gtk_file_chooser_default_show_all; - widget_class->realize = gtk_file_chooser_default_realize; - widget_class->map = gtk_file_chooser_default_map; - widget_class->unmap = gtk_file_chooser_default_unmap; - widget_class->hierarchy_changed = gtk_file_chooser_default_hierarchy_changed; - widget_class->style_updated = gtk_file_chooser_default_style_updated; - widget_class->screen_changed = gtk_file_chooser_default_screen_changed; - - signals[LOCATION_POPUP] = - g_signal_new_class_handler (I_("location-popup"), - G_OBJECT_CLASS_TYPE (class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_CALLBACK (location_popup_handler), - NULL, NULL, - _gtk_marshal_VOID__STRING, - G_TYPE_NONE, 1, G_TYPE_STRING); - - signals[LOCATION_POPUP_ON_PASTE] = - g_signal_new_class_handler (I_("location-popup-on-paste"), - G_OBJECT_CLASS_TYPE (class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_CALLBACK (location_popup_on_paste_handler), - NULL, NULL, - _gtk_marshal_VOID__VOID, - G_TYPE_NONE, 0); - - signals[LOCATION_TOGGLE_POPUP] = - g_signal_new_class_handler (I_("location-toggle-popup"), - G_OBJECT_CLASS_TYPE (class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_CALLBACK (location_toggle_popup_handler), - NULL, NULL, - _gtk_marshal_VOID__VOID, - G_TYPE_NONE, 0); - - signals[UP_FOLDER] = - g_signal_new_class_handler (I_("up-folder"), - G_OBJECT_CLASS_TYPE (class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_CALLBACK (up_folder_handler), - NULL, NULL, - _gtk_marshal_VOID__VOID, - G_TYPE_NONE, 0); - - signals[DOWN_FOLDER] = - g_signal_new_class_handler (I_("down-folder"), - G_OBJECT_CLASS_TYPE (class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_CALLBACK (down_folder_handler), - NULL, NULL, - _gtk_marshal_VOID__VOID, - G_TYPE_NONE, 0); - - signals[HOME_FOLDER] = - g_signal_new_class_handler (I_("home-folder"), - G_OBJECT_CLASS_TYPE (class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_CALLBACK (home_folder_handler), - NULL, NULL, - _gtk_marshal_VOID__VOID, - G_TYPE_NONE, 0); - - signals[DESKTOP_FOLDER] = - g_signal_new_class_handler (I_("desktop-folder"), - G_OBJECT_CLASS_TYPE (class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_CALLBACK (desktop_folder_handler), - NULL, NULL, - _gtk_marshal_VOID__VOID, - G_TYPE_NONE, 0); - - signals[QUICK_BOOKMARK] = - g_signal_new_class_handler (I_("quick-bookmark"), - G_OBJECT_CLASS_TYPE (class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_CALLBACK (quick_bookmark_handler), - NULL, NULL, - _gtk_marshal_VOID__INT, - G_TYPE_NONE, 1, G_TYPE_INT); - - signals[SHOW_HIDDEN] = - g_signal_new_class_handler (I_("show-hidden"), - G_OBJECT_CLASS_TYPE (class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_CALLBACK (show_hidden_handler), - NULL, NULL, - _gtk_marshal_VOID__VOID, - G_TYPE_NONE, 0); - - signals[SEARCH_SHORTCUT] = - g_signal_new_class_handler (I_("search-shortcut"), - G_OBJECT_CLASS_TYPE (class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_CALLBACK (search_shortcut_handler), - NULL, NULL, - _gtk_marshal_VOID__VOID, - G_TYPE_NONE, 0); - - signals[RECENT_SHORTCUT] = - g_signal_new_class_handler (I_("recent-shortcut"), - G_OBJECT_CLASS_TYPE (class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_CALLBACK (recent_shortcut_handler), - NULL, NULL, - _gtk_marshal_VOID__VOID, - G_TYPE_NONE, 0); - - binding_set = gtk_binding_set_by_class (class); - - gtk_binding_entry_add_signal (binding_set, - GDK_KEY_l, GDK_CONTROL_MASK, - "location-toggle-popup", - 0); - - gtk_binding_entry_add_signal (binding_set, - GDK_KEY_slash, 0, - "location-popup", - 1, G_TYPE_STRING, "/"); - gtk_binding_entry_add_signal (binding_set, - GDK_KEY_KP_Divide, 0, - "location-popup", - 1, G_TYPE_STRING, "/"); - -#ifdef G_OS_UNIX - gtk_binding_entry_add_signal (binding_set, - GDK_KEY_asciitilde, 0, - "location-popup", - 1, G_TYPE_STRING, "~"); -#endif - - gtk_binding_entry_add_signal (binding_set, - GDK_KEY_v, GDK_CONTROL_MASK, - "location-popup-on-paste", - 0); - - add_normal_and_shifted_binding (binding_set, - GDK_KEY_Up, GDK_MOD1_MASK, - "up-folder"); - - add_normal_and_shifted_binding (binding_set, - GDK_KEY_KP_Up, GDK_MOD1_MASK, - "up-folder"); - - add_normal_and_shifted_binding (binding_set, - GDK_KEY_Down, GDK_MOD1_MASK, - "down-folder"); - add_normal_and_shifted_binding (binding_set, - GDK_KEY_KP_Down, GDK_MOD1_MASK, - "down-folder"); - - gtk_binding_entry_add_signal (binding_set, - GDK_KEY_Home, GDK_MOD1_MASK, - "home-folder", - 0); - gtk_binding_entry_add_signal (binding_set, - GDK_KEY_KP_Home, GDK_MOD1_MASK, - "home-folder", - 0); - gtk_binding_entry_add_signal (binding_set, - GDK_KEY_d, GDK_MOD1_MASK, - "desktop-folder", - 0); - gtk_binding_entry_add_signal (binding_set, - GDK_KEY_h, GDK_CONTROL_MASK, - "show-hidden", - 0); - gtk_binding_entry_add_signal (binding_set, - GDK_KEY_s, GDK_MOD1_MASK, - "search-shortcut", - 0); - gtk_binding_entry_add_signal (binding_set, - GDK_KEY_r, GDK_MOD1_MASK, - "recent-shortcut", - 0); - - for (i = 0; i < 10; i++) - gtk_binding_entry_add_signal (binding_set, - quick_bookmark_keyvals[i], GDK_MOD1_MASK, - "quick-bookmark", - 1, G_TYPE_INT, i); - - _gtk_file_chooser_install_properties (gobject_class); - - /* Bind class to template */ - gtk_widget_class_set_template_from_resource (widget_class, - "/org/gtk/libgtk/ui/gtkfilechooserdefault.ui"); - - /* A *lot* of widgets that we need to handle .... */ - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, browse_widgets_box); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, browse_widgets_hpaned); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, browse_header_box); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, browse_widgets_box); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, places_sidebar); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, browse_files_tree_view); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, browse_new_folder_button); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, browse_path_bar_hbox); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, browse_path_bar_size_group); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, browse_path_bar); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, browse_special_mode_icon); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, browse_special_mode_label); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, browse_select_a_folder_info_bar); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, browse_select_a_folder_label); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, browse_select_a_folder_icon); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, filter_combo_hbox); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, filter_combo); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, preview_box); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, extra_align); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, location_button); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, location_entry_box); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, location_label); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, list_name_column); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, list_pixbuf_renderer); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, list_name_renderer); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, list_mtime_column); - gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserDefault, list_size_column); - - /* And a *lot* of callbacks to bind ... */ - gtk_widget_class_bind_template_callback (widget_class, browse_files_key_press_event_cb); - gtk_widget_class_bind_template_callback (widget_class, file_list_drag_drop_cb); - gtk_widget_class_bind_template_callback (widget_class, file_list_drag_data_received_cb); - gtk_widget_class_bind_template_callback (widget_class, list_popup_menu_cb); - gtk_widget_class_bind_template_callback (widget_class, file_list_query_tooltip_cb); - gtk_widget_class_bind_template_callback (widget_class, list_button_press_event_cb); - gtk_widget_class_bind_template_callback (widget_class, list_row_activated); - gtk_widget_class_bind_template_callback (widget_class, file_list_drag_motion_cb); - gtk_widget_class_bind_template_callback (widget_class, list_selection_changed); - gtk_widget_class_bind_template_callback (widget_class, renderer_editing_canceled_cb); - gtk_widget_class_bind_template_callback (widget_class, renderer_edited_cb); - gtk_widget_class_bind_template_callback (widget_class, filter_combo_changed); - gtk_widget_class_bind_template_callback (widget_class, location_button_toggled_cb); - gtk_widget_class_bind_template_callback (widget_class, new_folder_button_clicked); - gtk_widget_class_bind_template_callback (widget_class, path_bar_clicked); - gtk_widget_class_bind_template_callback (widget_class, places_sidebar_open_location_cb); - gtk_widget_class_bind_template_callback (widget_class, places_sidebar_show_error_message_cb); -} - -static void -post_process_ui (GtkFileChooserDefault *impl) -{ - GtkTreeSelection *selection; - GtkCellRenderer *cell; - GList *cells; - - /* Some qdata, qdata can't be set with GtkBuilder */ - g_object_set_data (G_OBJECT (impl->priv->browse_files_tree_view), "fmq-name", "file_list"); - g_object_set_data (G_OBJECT (impl->priv->browse_files_tree_view), I_("GtkFileChooserDefault"), impl); - - /* Setup file list treeview */ - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->priv->browse_files_tree_view)); - gtk_tree_selection_set_select_function (selection, - list_select_func, - impl, NULL); - gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (impl->priv->browse_files_tree_view), - GDK_BUTTON1_MASK, - NULL, 0, - GDK_ACTION_COPY | GDK_ACTION_MOVE); - gtk_drag_source_add_uri_targets (impl->priv->browse_files_tree_view); - - gtk_drag_dest_set (impl->priv->browse_files_tree_view, - GTK_DEST_DEFAULT_ALL, - NULL, 0, - GDK_ACTION_COPY | GDK_ACTION_MOVE); - gtk_drag_dest_add_uri_targets (impl->priv->browse_files_tree_view); - - /* File browser treemodel columns are shared between GtkFileChooser implementations, - * so we don't set cell renderer attributes in GtkBuilder, but rather keep that - * in code. - */ - file_list_set_sort_column_ids (impl); - update_cell_renderer_attributes (impl); - - /* Get the combo's text renderer and set ellipsize parameters, - * perhaps GtkComboBoxText should declare the cell renderer - * as an 'internal-child', then we could configure it in GtkBuilder - * instead of hard coding it here. - */ - cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (impl->priv->filter_combo)); - g_assert (cells); - cell = cells->data; - g_object_set (G_OBJECT (cell), - "ellipsize", PANGO_ELLIPSIZE_END, - NULL); - - g_list_free (cells); - - /* Set the GtkPathBar file system backend */ - _gtk_path_bar_set_file_system (GTK_PATH_BAR (impl->priv->browse_path_bar), impl->priv->file_system); - - /* Set the fixed size icon renderer, this requires - * that priv->icon_size be already setup. - */ - set_icon_cell_renderer_fixed_size (impl); -} - -static void -_gtk_file_chooser_default_init (GtkFileChooserDefault *impl) -{ - GtkFileChooserDefaultPrivate *priv; - - profile_start ("start", NULL); -#ifdef PROFILE_FILE_CHOOSER - access ("MARK: *** CREATE FILE CHOOSER", F_OK); -#endif - impl->priv = _gtk_file_chooser_default_get_instance_private (impl); - priv = impl->priv; - - priv->local_only = TRUE; - priv->preview_widget_active = TRUE; - priv->use_preview_label = TRUE; - priv->select_multiple = FALSE; - priv->show_hidden = FALSE; - priv->show_size_column = TRUE; - priv->icon_size = FALLBACK_ICON_SIZE; - priv->load_state = LOAD_EMPTY; - priv->reload_state = RELOAD_EMPTY; - priv->pending_select_files = NULL; - priv->location_mode = LOCATION_MODE_PATH_BAR; - priv->operation_mode = OPERATION_MODE_BROWSE; - priv->sort_column = MODEL_COL_NAME; - priv->sort_order = GTK_SORT_ASCENDING; - priv->recent_manager = gtk_recent_manager_get_default (); - priv->create_folders = TRUE; - priv->auto_selecting_first_row = FALSE; - - /* Ensure GTK+ private types used by the template - * definition before calling gtk_widget_init_template() - */ - g_type_ensure (GTK_TYPE_PATH_BAR); - gtk_widget_init_template (GTK_WIDGET (impl)); - gtk_widget_set_size_request (priv->browse_files_tree_view, 280, -1); - - set_file_system_backend (impl); - - if (g_settings_schema_source_lookup (g_settings_schema_source_get_default (), - "org.gnome.desktop.interface", - TRUE) != NULL) - - priv->bookmarks_manager = _gtk_bookmarks_manager_new (NULL, NULL); - - /* Setup various attributes and callbacks in the UI - * which cannot be done with GtkBuilder. - */ - post_process_ui (impl); - - profile_end ("end", NULL); -} diff --git a/gtk/gtkfilechooserdefault.h b/gtk/gtkfilechooserdefault.h deleted file mode 100644 index 8b2f3f3ae1..0000000000 --- a/gtk/gtkfilechooserdefault.h +++ /dev/null @@ -1,38 +0,0 @@ -/* GTK - The GIMP Toolkit - * gtkfilechooserdefault.h: Default implementation of GtkFileChooser - * Copyright (C) 2003, Red Hat, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef __GTK_FILE_CHOOSER_DEFAULT_H__ -#define __GTK_FILE_CHOOSER_DEFAULT_H__ - -#include "gtkfilesystem.h" -#include <gtk/gtkwidget.h> - -G_BEGIN_DECLS - -#define GTK_TYPE_FILE_CHOOSER_DEFAULT (_gtk_file_chooser_default_get_type ()) -#define GTK_FILE_CHOOSER_DEFAULT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_FILE_CHOOSER_DEFAULT, GtkFileChooserDefault)) -#define GTK_IS_FILE_CHOOSER_DEFAULT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_FILE_CHOOSER_DEFAULT)) - -typedef struct _GtkFileChooserDefault GtkFileChooserDefault; - -GType _gtk_file_chooser_default_get_type (void) G_GNUC_CONST; -GtkWidget *_gtk_file_chooser_default_new (void); - -G_END_DECLS - -#endif /* __GTK_FILE_CHOOSER_DEFAULT_H__ */ diff --git a/gtk/gtkfilechooserwidget.c b/gtk/gtkfilechooserwidget.c index 92618fa572..9501a85ae3 100644 --- a/gtk/gtkfilechooserwidget.c +++ b/gtk/gtkfilechooserwidget.c @@ -1,3 +1,4 @@ +/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */ /* GTK - The GIMP Toolkit * gtkfilechooserwidget.c: Embeddable file selector widget * Copyright (C) 2003, Red Hat, Inc. @@ -16,146 +17,7492 @@ * License along with this library. If not, see <http://www.gnu.org/licenses/>. */ +/* TODO: + * + * * Fix FIXME-places-sidebar + */ + #include "config.h" -#include "gtkfilechooserprivate.h" #include "gtkfilechooserwidget.h" -#include "gtkfilechooserdefault.h" -#include "gtkfilechooserutils.h" -#include "gtktypebuiltins.h" + +#include "gtkbindings.h" +#include "gtkcelllayout.h" +#include "gtkcellrendererpixbuf.h" +#include "gtkcellrenderertext.h" +#include "gtkcheckmenuitem.h" +#include "gtkclipboard.h" +#include "gtkcomboboxtext.h" +#include "gtkentry.h" +#include "gtkexpander.h" +#include "gtkfilechooserprivate.h" +#include "gtkfilechooserdialog.h" #include "gtkfilechooserembed.h" +#include "gtkfilechooserentry.h" +#include "gtkfilechooserutils.h" +#include "gtkfilechooser.h" +#include "gtkfilesystem.h" +#include "gtkfilesystemmodel.h" +#include "gtkframe.h" +#include "gtkgrid.h" +#include "deprecated/gtkiconfactory.h" +#include "gtkicontheme.h" +#include "gtkimage.h" +#include "deprecated/gtkimagemenuitem.h" +#include "gtkinfobar.h" +#include "gtklabel.h" +#include "gtkmarshalers.h" +#include "gtkmessagedialog.h" +#include "gtkmountoperation.h" +#include "gtkpaned.h" +#include "gtkpathbar.h" +#include "gtkplacessidebar.h" +#include "gtkprivate.h" +#include "gtkradiobutton.h" +#include "gtkrecentfilter.h" +#include "gtkrecentmanager.h" +#include "gtkscrolledwindow.h" +#include "gtkseparatormenuitem.h" +#include "gtksettings.h" +#include "gtksizegroup.h" +#include "gtksizerequest.h" +#include "gtktoolbar.h" +#include "gtktoolbutton.h" +#include "gtktooltip.h" +#include "gtktreednd.h" +#include "gtktreeprivate.h" +#include "gtktreeselection.h" +#include "gtkbox.h" #include "gtkorientable.h" #include "gtkintl.h" +#include <cairo-gobject.h> +#include <errno.h> +#include <string.h> +#include <time.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <locale.h> -/** - * SECTION:gtkfilechooserwidget - * @Short_description: File chooser widget that can be embedded in other widgets - * @Title: GtkFileChooserWidget - * @See_also: #GtkFileChooser, #GtkFileChooserDialog - * - * #GtkFileChooserWidget is a widget suitable for selecting files. - * It is the main building block of a #GtkFileChooserDialog. Most - * applications will only need to use the latter; you can use - * #GtkFileChooserWidget as part of a larger window if you have - * special needs. - * - * Note that #GtkFileChooserWidget does not have any methods of its - * own. Instead, you should use the functions that work on a - * #GtkFileChooser. - */ +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef G_OS_WIN32 +#include <io.h> +#endif + +/* Values for GtkSelection-related "info" fields */ +#define SELECTION_TEXT 0 +#define SELECTION_URI 1 + +/* Profiling stuff */ +#undef PROFILE_FILE_CHOOSER +#ifdef PROFILE_FILE_CHOOSER + + +#ifndef F_OK +#define F_OK 0 +#endif + +#define PROFILE_INDENT 4 + +static int profile_indent; + +static void +profile_add_indent (int indent) +{ + profile_indent += indent; + if (profile_indent < 0) + g_error ("You screwed up your indentation"); +} -struct _GtkFileChooserWidgetPrivate +void +_gtk_file_chooser_profile_log (const char *func, int indent, const char *msg1, const char *msg2) { - GtkWidget *impl; + char *str; + + if (indent < 0) + profile_add_indent (indent); + + if (profile_indent == 0) + str = g_strdup_printf ("MARK: %s %s %s", func ? func : "", msg1 ? msg1 : "", msg2 ? msg2 : ""); + else + str = g_strdup_printf ("MARK: %*c %s %s %s", profile_indent - 1, ' ', func ? func : "", msg1 ? msg1 : "", msg2 ? msg2 : ""); + + access (str, F_OK); + g_free (str); + + if (indent > 0) + profile_add_indent (indent); +} + +#define profile_start(x, y) _gtk_file_chooser_profile_log (G_STRFUNC, PROFILE_INDENT, x, y) +#define profile_end(x, y) _gtk_file_chooser_profile_log (G_STRFUNC, -PROFILE_INDENT, x, y) +#define profile_msg(x, y) _gtk_file_chooser_profile_log (NULL, 0, x, y) +#else +#define profile_start(x, y) +#define profile_end(x, y) +#define profile_msg(x, y) +#endif + + +typedef enum { + LOAD_EMPTY, /* There is no model */ + LOAD_PRELOAD, /* Model is loading and a timer is running; model isn't inserted into the tree yet */ + LOAD_LOADING, /* Timeout expired, model is inserted into the tree, but not fully loaded yet */ + LOAD_FINISHED /* Model is fully loaded and inserted into the tree */ +} LoadState; + +typedef enum { + RELOAD_EMPTY, /* No folder has been set */ + RELOAD_HAS_FOLDER /* We have a folder, although it may not be completely loaded yet; no need to reload */ +} ReloadState; + +typedef enum { + LOCATION_MODE_PATH_BAR, + LOCATION_MODE_FILENAME_ENTRY +} LocationMode; + +typedef enum { + OPERATION_MODE_BROWSE, + OPERATION_MODE_SEARCH, + OPERATION_MODE_RECENT +} OperationMode; + +typedef enum { + STARTUP_MODE_RECENT, + STARTUP_MODE_CWD +} StartupMode; + +struct _GtkFileChooserWidgetPrivate { + GtkFileChooserAction action; + + GtkFileSystem *file_system; + + /* Save mode widgets */ + GtkWidget *save_widgets; + GtkWidget *save_widgets_table; + + GtkWidget *save_folder_label; + + /* The file browsing widgets */ + GtkWidget *browse_widgets_box; + GtkWidget *browse_widgets_hpaned; + GtkWidget *browse_header_box; + GtkWidget *browse_files_tree_view; + GtkWidget *browse_files_popup_menu; + GtkWidget *browse_files_popup_menu_add_shortcut_item; + GtkWidget *browse_files_popup_menu_hidden_files_item; + GtkWidget *browse_files_popup_menu_size_column_item; + GtkWidget *browse_files_popup_menu_copy_file_location_item; + GtkWidget *browse_files_popup_menu_visit_file_item; + GtkWidget *browse_new_folder_button; + GtkWidget *browse_path_bar_hbox; + GtkSizeGroup *browse_path_bar_size_group; + GtkWidget *browse_path_bar; + GtkWidget *browse_special_mode_icon; + GtkWidget *browse_special_mode_label; + GtkWidget *browse_select_a_folder_info_bar; + GtkWidget *browse_select_a_folder_label; + GtkWidget *browse_select_a_folder_icon; + + GtkFileSystemModel *browse_files_model; + char *browse_files_last_selected_name; + + GtkWidget *places_sidebar; + StartupMode startup_mode; + + /* OPERATION_MODE_SEARCH */ + GtkWidget *search_hbox; + GtkWidget *search_entry; + GtkSearchEngine *search_engine; + GtkQuery *search_query; + GtkFileSystemModel *search_model; + + /* OPERATION_MODE_RECENT */ + GtkRecentManager *recent_manager; + GtkFileSystemModel *recent_model; + guint load_recent_id; + + GtkWidget *filter_combo_hbox; + GtkWidget *filter_combo; + GtkWidget *preview_box; + GtkWidget *preview_label; + GtkWidget *preview_widget; + GtkWidget *extra_align; + GtkWidget *extra_widget; + + GtkWidget *location_button; + GtkWidget *location_entry_box; + GtkWidget *location_label; + GtkWidget *location_entry; + LocationMode location_mode; + + /* Handles */ + GSList *loading_shortcuts; + GSList *reload_icon_cancellables; + GCancellable *file_list_drag_data_received_cancellable; + GCancellable *update_current_folder_cancellable; + GCancellable *should_respond_get_info_cancellable; + GCancellable *file_exists_get_info_cancellable; + GCancellable *update_from_entry_cancellable; + GCancellable *shortcuts_activate_iter_cancellable; + + LoadState load_state; + ReloadState reload_state; + guint load_timeout_id; + + OperationMode operation_mode; + + GSList *pending_select_files; + + GtkFileFilter *current_filter; + GSList *filters; + + GtkBookmarksManager *bookmarks_manager; + + int num_volumes; + int num_shortcuts; + int num_bookmarks; + + gulong volumes_changed_id; + gulong bookmarks_changed_id; + + GFile *current_volume_file; + GFile *current_folder; + GFile *preview_file; + char *preview_display_name; + + GtkTreeViewColumn *list_name_column; + GtkCellRenderer *list_name_renderer; + GtkCellRenderer *list_pixbuf_renderer; + GtkTreeViewColumn *list_mtime_column; + GtkTreeViewColumn *list_size_column; + + GSource *edited_idle; + char *edited_new_text; + + gulong settings_signal_id; + int icon_size; + + GSource *focus_entry_idle; + + gulong toplevel_set_focus_id; + GtkWidget *toplevel_last_focus_widget; + + gint sort_column; + GtkSortType sort_order; + + /* Flags */ + + guint local_only : 1; + guint preview_widget_active : 1; + guint use_preview_label : 1; + guint select_multiple : 1; + guint show_hidden : 1; + guint sort_directories_first : 1; + guint do_overwrite_confirmation : 1; + guint list_sort_ascending : 1; + guint shortcuts_current_folder_active : 1; + guint show_size_column : 1; + guint create_folders : 1; + guint auto_selecting_first_row : 1; +}; + +#define MAX_LOADING_TIME 500 + +#define DEFAULT_NEW_FOLDER_NAME _("Type name of new folder") + +/* Signal IDs */ +enum { + LOCATION_POPUP, + LOCATION_POPUP_ON_PASTE, + UP_FOLDER, + DOWN_FOLDER, + HOME_FOLDER, + DESKTOP_FOLDER, + QUICK_BOOKMARK, + LOCATION_TOGGLE_POPUP, + SHOW_HIDDEN, + SEARCH_SHORTCUT, + RECENT_SHORTCUT, + + LAST_SIGNAL }; +static guint signals[LAST_SIGNAL] = { 0 }; + +#define MODEL_ATTRIBUTES "standard::name,standard::type,standard::display-name," \ + "standard::is-hidden,standard::is-backup,standard::size," \ + "standard::content-type,time::modified" +enum { + /* the first 3 must be these due to settings caching sort column */ + MODEL_COL_NAME, + MODEL_COL_SIZE, + MODEL_COL_MTIME, + MODEL_COL_FILE, + MODEL_COL_NAME_COLLATED, + MODEL_COL_IS_FOLDER, + MODEL_COL_IS_SENSITIVE, + MODEL_COL_SURFACE, + MODEL_COL_SIZE_TEXT, + MODEL_COL_MTIME_TEXT, + MODEL_COL_ELLIPSIZE, + MODEL_COL_NUM_COLUMNS +}; + +/* This list of types is passed to _gtk_file_system_model_new*() */ +#define MODEL_COLUMN_TYPES \ + MODEL_COL_NUM_COLUMNS, \ + G_TYPE_STRING, /* MODEL_COL_NAME */ \ + G_TYPE_INT64, /* MODEL_COL_SIZE */ \ + G_TYPE_LONG, /* MODEL_COL_MTIME */ \ + G_TYPE_FILE, /* MODEL_COL_FILE */ \ + G_TYPE_STRING, /* MODEL_COL_NAME_COLLATED */ \ + G_TYPE_BOOLEAN, /* MODEL_COL_IS_FOLDER */ \ + G_TYPE_BOOLEAN, /* MODEL_COL_IS_SENSITIVE */ \ + CAIRO_GOBJECT_TYPE_SURFACE, /* MODEL_COL_SURFACE */ \ + G_TYPE_STRING, /* MODEL_COL_SIZE_TEXT */ \ + G_TYPE_STRING, /* MODEL_COL_MTIME_TEXT */ \ + PANGO_TYPE_ELLIPSIZE_MODE /* MODEL_COL_ELLIPSIZE */ + +/* Identifiers for target types */ +enum { + GTK_TREE_MODEL_ROW, +}; + +#define DEFAULT_RECENT_FILES_LIMIT 50 + +/* Icon size for if we can't get it from the theme */ +#define FALLBACK_ICON_SIZE 16 + +#define PREVIEW_HBOX_SPACING 12 +#define NUM_LINES 45 +#define NUM_CHARS 60 + +static void gtk_file_chooser_widget_iface_init (GtkFileChooserIface *iface); +static void gtk_file_chooser_embed_default_iface_init (GtkFileChooserEmbedIface *iface); + static GObject* gtk_file_chooser_widget_constructor (GType type, - guint n_construct_properties, - GObjectConstructParam *construct_params); + guint n_construct_properties, + GObjectConstructParam *construct_params); +static void gtk_file_chooser_widget_finalize (GObject *object); static void gtk_file_chooser_widget_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec); + guint prop_id, + const GValue *value, + GParamSpec *pspec); static void gtk_file_chooser_widget_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec); + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void gtk_file_chooser_widget_dispose (GObject *object); +static void gtk_file_chooser_widget_show_all (GtkWidget *widget); +static void gtk_file_chooser_widget_realize (GtkWidget *widget); +static void gtk_file_chooser_widget_map (GtkWidget *widget); +static void gtk_file_chooser_widget_unmap (GtkWidget *widget); +static void gtk_file_chooser_widget_hierarchy_changed (GtkWidget *widget, + GtkWidget *previous_toplevel); +static void gtk_file_chooser_widget_style_updated (GtkWidget *widget); +static void gtk_file_chooser_widget_screen_changed (GtkWidget *widget, + GdkScreen *previous_screen); + +static gboolean gtk_file_chooser_widget_set_current_folder (GtkFileChooser *chooser, + GFile *folder, + GError **error); +static gboolean gtk_file_chooser_widget_update_current_folder (GtkFileChooser *chooser, + GFile *folder, + gboolean keep_trail, + gboolean clear_entry, + GError **error); +static GFile * gtk_file_chooser_widget_get_current_folder (GtkFileChooser *chooser); +static void gtk_file_chooser_widget_set_current_name (GtkFileChooser *chooser, + const gchar *name); +static gchar * gtk_file_chooser_widget_get_current_name (GtkFileChooser *chooser); +static gboolean gtk_file_chooser_widget_select_file (GtkFileChooser *chooser, + GFile *file, + GError **error); +static void gtk_file_chooser_widget_unselect_file (GtkFileChooser *chooser, + GFile *file); +static void gtk_file_chooser_widget_select_all (GtkFileChooser *chooser); +static void gtk_file_chooser_widget_unselect_all (GtkFileChooser *chooser); +static GSList * gtk_file_chooser_widget_get_files (GtkFileChooser *chooser); +static GFile * gtk_file_chooser_widget_get_preview_file (GtkFileChooser *chooser); +static GtkFileSystem *gtk_file_chooser_widget_get_file_system (GtkFileChooser *chooser); +static void gtk_file_chooser_widget_add_filter (GtkFileChooser *chooser, + GtkFileFilter *filter); +static void gtk_file_chooser_widget_remove_filter (GtkFileChooser *chooser, + GtkFileFilter *filter); +static GSList * gtk_file_chooser_widget_list_filters (GtkFileChooser *chooser); +static gboolean gtk_file_chooser_widget_add_shortcut_folder (GtkFileChooser *chooser, + GFile *file, + GError **error); +static gboolean gtk_file_chooser_widget_remove_shortcut_folder (GtkFileChooser *chooser, + GFile *file, + GError **error); +static GSList * gtk_file_chooser_widget_list_shortcut_folders (GtkFileChooser *chooser); + +static void gtk_file_chooser_widget_get_default_size (GtkFileChooserEmbed *chooser_embed, + gint *default_width, + gint *default_height); +static gboolean gtk_file_chooser_widget_should_respond (GtkFileChooserEmbed *chooser_embed); +static void gtk_file_chooser_widget_initial_focus (GtkFileChooserEmbed *chooser_embed); + +static void add_selection_to_recent_list (GtkFileChooserWidget *impl); + +static void location_popup_handler (GtkFileChooserWidget *impl, + const gchar *path); +static void location_popup_on_paste_handler (GtkFileChooserWidget *impl); +static void location_toggle_popup_handler (GtkFileChooserWidget *impl); +static void up_folder_handler (GtkFileChooserWidget *impl); +static void down_folder_handler (GtkFileChooserWidget *impl); +static void home_folder_handler (GtkFileChooserWidget *impl); +static void desktop_folder_handler (GtkFileChooserWidget *impl); +static void quick_bookmark_handler (GtkFileChooserWidget *impl, + gint bookmark_index); +static void show_hidden_handler (GtkFileChooserWidget *impl); +static void search_shortcut_handler (GtkFileChooserWidget *impl); +static void recent_shortcut_handler (GtkFileChooserWidget *impl); +static void update_appearance (GtkFileChooserWidget *impl); + +static void operation_mode_set (GtkFileChooserWidget *impl, OperationMode mode); + +static void set_current_filter (GtkFileChooserWidget *impl, + GtkFileFilter *filter); +static void check_preview_change (GtkFileChooserWidget *impl); + +static void filter_combo_changed (GtkComboBox *combo_box, + GtkFileChooserWidget *impl); + +static gboolean list_select_func (GtkTreeSelection *selection, + GtkTreeModel *model, + GtkTreePath *path, + gboolean path_currently_selected, + gpointer data); + +static void list_selection_changed (GtkTreeSelection *tree_selection, + GtkFileChooserWidget *impl); +static void list_row_activated (GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + GtkFileChooserWidget *impl); + +static void path_bar_clicked (GtkPathBar *path_bar, + GFile *file, + GFile *child, + gboolean child_is_hidden, + GtkFileChooserWidget *impl); + +static void update_cell_renderer_attributes (GtkFileChooserWidget *impl); + +static void load_remove_timer (GtkFileChooserWidget *impl, LoadState new_load_state); +static void browse_files_center_selected_row (GtkFileChooserWidget *impl); + +static void location_button_toggled_cb (GtkToggleButton *toggle, + GtkFileChooserWidget *impl); +static void location_switch_to_path_bar (GtkFileChooserWidget *impl); + +static void stop_loading_and_clear_list_model (GtkFileChooserWidget *impl, + gboolean remove_from_treeview); + +static void search_setup_widgets (GtkFileChooserWidget *impl); +static void search_stop_searching (GtkFileChooserWidget *impl, + gboolean remove_query); +static void search_clear_model (GtkFileChooserWidget *impl, + gboolean remove_from_treeview); +static gboolean search_should_respond (GtkFileChooserWidget *impl); +static GSList *search_get_selected_files (GtkFileChooserWidget *impl); +static void search_entry_activate_cb (GtkEntry *entry, + gpointer data); +static void settings_load (GtkFileChooserWidget *impl); + +static void recent_start_loading (GtkFileChooserWidget *impl); +static void recent_stop_loading (GtkFileChooserWidget *impl); +static void recent_clear_model (GtkFileChooserWidget *impl, + gboolean remove_from_treeview); +static gboolean recent_should_respond (GtkFileChooserWidget *impl); +static GSList * recent_get_selected_files (GtkFileChooserWidget *impl); +static void set_file_system_backend (GtkFileChooserWidget *impl); +static void unset_file_system_backend (GtkFileChooserWidget *impl); + + G_DEFINE_TYPE_WITH_CODE (GtkFileChooserWidget, gtk_file_chooser_widget, GTK_TYPE_BOX, G_ADD_PRIVATE (GtkFileChooserWidget) G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_CHOOSER, - _gtk_file_chooser_delegate_iface_init) + gtk_file_chooser_widget_iface_init) G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_CHOOSER_EMBED, - _gtk_file_chooser_embed_delegate_iface_init)) + gtk_file_chooser_embed_default_iface_init)); static void -gtk_file_chooser_widget_class_init (GtkFileChooserWidgetClass *class) +gtk_file_chooser_widget_iface_init (GtkFileChooserIface *iface) { - GObjectClass *gobject_class = G_OBJECT_CLASS (class); + iface->select_file = gtk_file_chooser_widget_select_file; + iface->unselect_file = gtk_file_chooser_widget_unselect_file; + iface->select_all = gtk_file_chooser_widget_select_all; + iface->unselect_all = gtk_file_chooser_widget_unselect_all; + iface->get_files = gtk_file_chooser_widget_get_files; + iface->get_preview_file = gtk_file_chooser_widget_get_preview_file; + iface->get_file_system = gtk_file_chooser_widget_get_file_system; + iface->set_current_folder = gtk_file_chooser_widget_set_current_folder; + iface->get_current_folder = gtk_file_chooser_widget_get_current_folder; + iface->set_current_name = gtk_file_chooser_widget_set_current_name; + iface->get_current_name = gtk_file_chooser_widget_get_current_name; + iface->add_filter = gtk_file_chooser_widget_add_filter; + iface->remove_filter = gtk_file_chooser_widget_remove_filter; + iface->list_filters = gtk_file_chooser_widget_list_filters; + iface->add_shortcut_folder = gtk_file_chooser_widget_add_shortcut_folder; + iface->remove_shortcut_folder = gtk_file_chooser_widget_remove_shortcut_folder; + iface->list_shortcut_folders = gtk_file_chooser_widget_list_shortcut_folders; +} - gobject_class->constructor = gtk_file_chooser_widget_constructor; - gobject_class->set_property = gtk_file_chooser_widget_set_property; - gobject_class->get_property = gtk_file_chooser_widget_get_property; +static void +gtk_file_chooser_embed_default_iface_init (GtkFileChooserEmbedIface *iface) +{ + iface->get_default_size = gtk_file_chooser_widget_get_default_size; + iface->should_respond = gtk_file_chooser_widget_should_respond; + iface->initial_focus = gtk_file_chooser_widget_initial_focus; +} - _gtk_file_chooser_install_properties (gobject_class); +static void +pending_select_files_free (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + g_slist_free_full (priv->pending_select_files, g_object_unref); + priv->pending_select_files = NULL; +} + +static void +pending_select_files_add (GtkFileChooserWidget *impl, + GFile *file) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + priv->pending_select_files = + g_slist_prepend (priv->pending_select_files, g_object_ref (file)); +} + +static void +gtk_file_chooser_widget_finalize (GObject *object) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (object); + GtkFileChooserWidgetPrivate *priv = impl->priv; + GSList *l; + + unset_file_system_backend (impl); + + g_free (priv->browse_files_last_selected_name); + + for (l = priv->filters; l; l = l->next) + { + GtkFileFilter *filter; + + filter = GTK_FILE_FILTER (l->data); + g_object_unref (filter); + } + g_slist_free (priv->filters); + + if (priv->current_filter) + g_object_unref (priv->current_filter); + + if (priv->current_volume_file) + g_object_unref (priv->current_volume_file); + + if (priv->current_folder) + g_object_unref (priv->current_folder); + + if (priv->preview_file) + g_object_unref (priv->preview_file); + + if (priv->browse_path_bar_size_group) + g_object_unref (priv->browse_path_bar_size_group); + + /* Free all the Models we have */ + stop_loading_and_clear_list_model (impl, FALSE); + search_clear_model (impl, FALSE); + recent_clear_model (impl, FALSE); + + /* stopping the load above should have cleared this */ + g_assert (priv->load_timeout_id == 0); + + g_free (priv->preview_display_name); + + g_free (priv->edited_new_text); + + impl->priv = NULL; + + G_OBJECT_CLASS (gtk_file_chooser_widget_parent_class)->finalize (object); +} + +/* Shows an error dialog set as transient for the specified window */ +static void +error_message_with_parent (GtkWindow *parent, + const char *msg, + const char *detail) +{ + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (parent, + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + "%s", + msg); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + "%s", detail); + + if (parent && gtk_window_has_group (parent)) + gtk_window_group_add_window (gtk_window_get_group (parent), + GTK_WINDOW (dialog)); + + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +/* Returns a toplevel GtkWindow, or NULL if none */ +static GtkWindow * +get_toplevel (GtkWidget *widget) +{ + GtkWidget *toplevel; + + toplevel = gtk_widget_get_toplevel (widget); + if (!gtk_widget_is_toplevel (toplevel)) + return NULL; + else + return GTK_WINDOW (toplevel); +} + +/* Shows an error dialog for the file chooser */ +static void +error_message (GtkFileChooserWidget *impl, + const char *msg, + const char *detail) +{ + error_message_with_parent (get_toplevel (GTK_WIDGET (impl)), msg, detail); +} + +/* Shows a simple error dialog relative to a path. Frees the GError as well. */ +static void +error_dialog (GtkFileChooserWidget *impl, + const char *msg, + GFile *file, + GError *error) +{ + if (error) + { + char *uri = NULL; + char *text; + + if (file) + uri = g_file_get_uri (file); + text = g_strdup_printf (msg, uri); + error_message (impl, text, error->message); + g_free (text); + g_free (uri); + g_error_free (error); + } +} + +/* Shows an error dialog about not being able to create a folder */ +static void +error_creating_folder_dialog (GtkFileChooserWidget *impl, + GFile *file, + GError *error) +{ + error_dialog (impl, + _("The folder could not be created"), + file, error); +} + +/* Shows an error about not being able to create a folder because a file with + * the same name is already there. + */ +static void +error_creating_folder_over_existing_file_dialog (GtkFileChooserWidget *impl, + GFile *file, + GError *error) +{ + error_dialog (impl, + _("The folder could not be created, as a file with the same " + "name already exists. Try using a different name for the " + "folder, or rename the file first."), + file, error); +} + +static void +error_with_file_under_nonfolder (GtkFileChooserWidget *impl, + GFile *parent_file) +{ + GError *error; + + error = NULL; + g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY, + _("You need to choose a valid filename.")); + + error_dialog (impl, + _("Cannot create a file under %s as it is not a folder"), + parent_file, error); +} + +static void +error_filename_to_long_dialog (GtkFileChooserWidget *impl) +{ + error_message (impl, + _("Cannot create file as the filename is too long"), + _("Try using a shorter name.")); +} + +/* Shows an error about not being able to select a folder because a file with + * the same name is already there. + */ +static void +error_selecting_folder_over_existing_file_dialog (GtkFileChooserWidget *impl) +{ + error_message (impl, + _("You may only select folders"), + _("The item that you selected is not a folder try using a different item.")); +} + +/* Shows an error dialog about not being able to create a filename */ +static void +error_building_filename_dialog (GtkFileChooserWidget *impl, + GError *error) +{ + error_dialog (impl, _("Invalid file name"), + NULL, error); +} + +/* Shows an error dialog when we cannot switch to a folder */ +static void +error_changing_folder_dialog (GtkFileChooserWidget *impl, + GFile *file, + GError *error) +{ + error_dialog (impl, _("The folder contents could not be displayed"), + file, error); +} + +/* Changes folders, displaying an error dialog if this fails */ +static gboolean +change_folder_and_display_error (GtkFileChooserWidget *impl, + GFile *file, + gboolean clear_entry) +{ + GError *error; + gboolean result; + + g_return_val_if_fail (G_IS_FILE (file), FALSE); + + /* We copy the path because of this case: + * + * list_row_activated() + * fetches path from model; path belongs to the model (*) + * calls change_folder_and_display_error() + * calls gtk_file_chooser_set_current_folder_file() + * changing folders fails, sets model to NULL, thus freeing the path in (*) + */ + + error = NULL; + result = gtk_file_chooser_widget_update_current_folder (GTK_FILE_CHOOSER (impl), file, TRUE, clear_entry, &error); + + if (!result) + error_changing_folder_dialog (impl, file, error); + + return result; +} + +static void +emit_default_size_changed (GtkFileChooserWidget *impl) +{ + profile_msg (" emit default-size-changed start", NULL); + g_signal_emit_by_name (impl, "default-size-changed"); + profile_msg (" emit default-size-changed end", NULL); +} + +static void +update_preview_widget_visibility (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (priv->use_preview_label) + { + if (!priv->preview_label) + { + priv->preview_label = gtk_label_new (priv->preview_display_name); + gtk_box_pack_start (GTK_BOX (priv->preview_box), priv->preview_label, FALSE, FALSE, 0); + gtk_box_reorder_child (GTK_BOX (priv->preview_box), priv->preview_label, 0); + gtk_label_set_ellipsize (GTK_LABEL (priv->preview_label), PANGO_ELLIPSIZE_MIDDLE); + gtk_widget_show (priv->preview_label); + } + } + else + { + if (priv->preview_label) + { + gtk_widget_destroy (priv->preview_label); + priv->preview_label = NULL; + } + } + + if (priv->preview_widget_active && priv->preview_widget) + gtk_widget_show (priv->preview_box); + else + gtk_widget_hide (priv->preview_box); + + if (!gtk_widget_get_mapped (GTK_WIDGET (impl))) + emit_default_size_changed (impl); +} + +static void +set_preview_widget (GtkFileChooserWidget *impl, + GtkWidget *preview_widget) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (preview_widget == priv->preview_widget) + return; + + if (priv->preview_widget) + gtk_container_remove (GTK_CONTAINER (priv->preview_box), + priv->preview_widget); + + priv->preview_widget = preview_widget; + if (priv->preview_widget) + { + gtk_widget_show (priv->preview_widget); + gtk_box_pack_start (GTK_BOX (priv->preview_box), priv->preview_widget, TRUE, TRUE, 0); + gtk_box_reorder_child (GTK_BOX (priv->preview_box), + priv->preview_widget, + (priv->use_preview_label && priv->preview_label) ? 1 : 0); + } + + update_preview_widget_visibility (impl); +} + +/* Callback used when the "New Folder" button is clicked */ +static void +new_folder_button_clicked (GtkButton *button, + GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkTreeIter iter; + GtkTreePath *path; + + if (!priv->browse_files_model) + return; /* FIXME: this sucks. Disable the New Folder button or something. */ + + /* Prevent button from being clicked twice */ + gtk_widget_set_sensitive (priv->browse_new_folder_button, FALSE); + + _gtk_file_system_model_add_editable (priv->browse_files_model, &iter); + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (priv->browse_files_model), &iter); + gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (priv->browse_files_tree_view), + path, priv->list_name_column, + FALSE, 0.0, 0.0); + + g_object_set (priv->list_name_renderer, "editable", TRUE, NULL); + gtk_tree_view_set_cursor (GTK_TREE_VIEW (priv->browse_files_tree_view), + path, + priv->list_name_column, + TRUE); + + gtk_tree_path_free (path); +} + +static GSource * +add_idle_while_impl_is_alive (GtkFileChooserWidget *impl, GCallback callback) +{ + GSource *source; + + source = g_idle_source_new (); + g_source_set_closure (source, + g_cclosure_new_object (callback, G_OBJECT (impl))); + g_source_attach (source, NULL); + + return source; +} + +/* Idle handler for creating a new folder after editing its name cell, or for + * canceling the editing. + */ +static gboolean +edited_idle_cb (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + gdk_threads_enter (); + + g_source_destroy (priv->edited_idle); + priv->edited_idle = NULL; + + _gtk_file_system_model_remove_editable (priv->browse_files_model); + g_object_set (priv->list_name_renderer, "editable", FALSE, NULL); + + gtk_widget_set_sensitive (priv->browse_new_folder_button, TRUE); + + if (priv->edited_new_text /* not cancelled? */ + && (strlen (priv->edited_new_text) != 0) + && (strcmp (priv->edited_new_text, DEFAULT_NEW_FOLDER_NAME) != 0)) /* Don't create folder if name is empty or has not been edited */ + { + GError *error = NULL; + GFile *file; + + file = g_file_get_child_for_display_name (priv->current_folder, + priv->edited_new_text, + &error); + if (file) + { + GError *error = NULL; + + if (g_file_make_directory (file, NULL, &error)) + change_folder_and_display_error (impl, file, FALSE); + else + error_creating_folder_dialog (impl, file, error); + + g_object_unref (file); + } + else + error_creating_folder_dialog (impl, file, error); + + g_free (priv->edited_new_text); + priv->edited_new_text = NULL; + } + + gdk_threads_leave (); + + return FALSE; +} + +static void +queue_edited_idle (GtkFileChooserWidget *impl, + const gchar *new_text) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + /* We create the folder in an idle handler so that we don't modify the tree + * just now. + */ + + if (!priv->edited_idle) + priv->edited_idle = add_idle_while_impl_is_alive (impl, G_CALLBACK (edited_idle_cb)); + + g_free (priv->edited_new_text); + priv->edited_new_text = g_strdup (new_text); +} + +/* Callback used from the text cell renderer when the new folder is named */ +static void +renderer_edited_cb (GtkCellRendererText *cell_renderer_text, + const gchar *path, + const gchar *new_text, + GtkFileChooserWidget *impl) +{ + /* work around bug #154921 */ + g_object_set (cell_renderer_text, + "mode", GTK_CELL_RENDERER_MODE_INERT, NULL); + queue_edited_idle (impl, new_text); +} + +/* Callback used from the text cell renderer when the new folder edition gets + * canceled. + */ +static void +renderer_editing_canceled_cb (GtkCellRendererText *cell_renderer_text, + GtkFileChooserWidget *impl) +{ + /* work around bug #154921 */ + g_object_set (cell_renderer_text, + "mode", GTK_CELL_RENDERER_MODE_INERT, NULL); + queue_edited_idle (impl, NULL); +} + + +struct selection_check_closure { + GtkFileChooserWidget *impl; + int num_selected; + gboolean all_files; + gboolean all_folders; +}; + +/* Used from gtk_tree_selection_selected_foreach() */ +static void +selection_check_foreach_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + struct selection_check_closure *closure; + gboolean is_folder; + GFile *file; + + gtk_tree_model_get (model, iter, + MODEL_COL_FILE, &file, + MODEL_COL_IS_FOLDER, &is_folder, + -1); + + if (file == NULL) + return; + + g_object_unref (file); + + closure = data; + closure->num_selected++; + + closure->all_folders = closure->all_folders && is_folder; + closure->all_files = closure->all_files && !is_folder; +} + +/* Checks whether the selected items in the file list are all files or all folders */ +static void +selection_check (GtkFileChooserWidget *impl, + gint *num_selected, + gboolean *all_files, + gboolean *all_folders) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + struct selection_check_closure closure; + GtkTreeSelection *selection; + + closure.impl = impl; + closure.num_selected = 0; + closure.all_files = TRUE; + closure.all_folders = TRUE; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); + gtk_tree_selection_selected_foreach (selection, + selection_check_foreach_cb, + &closure); + + g_assert (closure.num_selected == 0 || !(closure.all_files && closure.all_folders)); + + if (num_selected) + *num_selected = closure.num_selected; + + if (all_files) + *all_files = closure.all_files; + + if (all_folders) + *all_folders = closure.all_folders; +} + +static gboolean +file_is_recent_uri (GFile *file) +{ + GFile *recent; + gboolean same; + + recent = g_file_new_for_uri ("recent:///"); + same = g_file_equal (file, recent); + g_object_unref (recent); + + return same; +} + +static void +places_sidebar_open_location_cb (GtkPlacesSidebar *sidebar, GFile *location, GtkPlacesOpenFlags open_flags, GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + gboolean clear_entry; + + /* In the Save modes, we want to preserve what the uesr typed in the filename + * entry, so that he may choose another folder without erasing his typed name. + */ + if (priv->location_entry + && !(priv->action == GTK_FILE_CHOOSER_ACTION_SAVE + || priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)) + clear_entry = TRUE; + else + clear_entry = FALSE; + + /* FIXME-places-sidebar: + * + * GtkPlacesSidebar doesn't have a Search item anymore. We should put that function in a toolbar-like button, like + * in Nautilus, and do operation_mode_set (impl, OPERATION_MODE_SEARCH); + */ + + if (file_is_recent_uri (location)) + operation_mode_set (impl, OPERATION_MODE_RECENT); + else + change_folder_and_display_error (impl, location, clear_entry); +} + +/* Callback used when the places sidebar needs us to display an error message */ +static void +places_sidebar_show_error_message_cb (GtkPlacesSidebar *sidebar, + const char *primary, + const char *secondary, + GtkFileChooserWidget *impl) +{ + error_message (impl, primary, secondary); +} + +static gboolean +key_is_left_or_right (GdkEventKey *event) +{ + guint modifiers; + + modifiers = gtk_accelerator_get_default_mod_mask (); + + return ((event->keyval == GDK_KEY_Right + || event->keyval == GDK_KEY_KP_Right + || event->keyval == GDK_KEY_Left + || event->keyval == GDK_KEY_KP_Left) + && (event->state & modifiers) == 0); +} + +/* Handles key press events on the file list, so that we can trap Enter to + * activate the default button on our own. Also, checks to see if '/' has been + * pressed. + */ +static gboolean +browse_files_key_press_event_cb (GtkWidget *widget, + GdkEventKey *event, + gpointer data) +{ + GtkFileChooserWidget *impl = (GtkFileChooserWidget *) data; + GtkFileChooserWidgetPrivate *priv = impl->priv; + GdkModifierType no_text_input_mask; + + no_text_input_mask = + gtk_widget_get_modifier_mask (widget, GDK_MODIFIER_INTENT_NO_TEXT_INPUT); + + if ((event->keyval == GDK_KEY_slash + || event->keyval == GDK_KEY_KP_Divide +#ifdef G_OS_UNIX + || event->keyval == GDK_KEY_asciitilde +#endif + ) && !(event->state & no_text_input_mask)) + { + location_popup_handler (impl, event->string); + return TRUE; + } + + if (key_is_left_or_right (event)) + { + gtk_widget_grab_focus (priv->places_sidebar); + return TRUE; + } + + if ((event->keyval == GDK_KEY_Return + || event->keyval == GDK_KEY_ISO_Enter + || event->keyval == GDK_KEY_KP_Enter + || event->keyval == GDK_KEY_space + || event->keyval == GDK_KEY_KP_Space) + && !(event->state & gtk_accelerator_get_default_mod_mask ()) + && !(priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER || + priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)) + { + GtkWindow *window; + + window = get_toplevel (widget); + if (window) + { + GtkWidget *default_widget, *focus_widget; + + default_widget = gtk_window_get_default_widget (window); + focus_widget = gtk_window_get_focus (window); + + if (widget != default_widget && + !(widget == focus_widget && (!default_widget || !gtk_widget_get_sensitive (default_widget)))) + { + gtk_window_activate_default (window); + + return TRUE; + } + } + } + + return FALSE; +} + +/* Callback used when the file list's popup menu is detached */ +static void +popup_menu_detach_cb (GtkWidget *attach_widget, + GtkMenu *menu) +{ + GtkFileChooserWidget *impl = g_object_get_data (G_OBJECT (attach_widget), "GtkFileChooserWidget"); + GtkFileChooserWidgetPrivate *priv; + + g_assert (GTK_IS_FILE_CHOOSER_WIDGET (impl)); + + priv = impl->priv; + + priv->browse_files_popup_menu = NULL; + priv->browse_files_popup_menu_add_shortcut_item = NULL; + priv->browse_files_popup_menu_hidden_files_item = NULL; + priv->browse_files_popup_menu_copy_file_location_item = NULL; +} + +/* Callback used from gtk_tree_selection_selected_foreach(); adds a bookmark for + * each selected item in the file list. + */ +static void +add_bookmark_foreach_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + GtkFileChooserWidget *impl = (GtkFileChooserWidget *) data; + GtkFileChooserWidgetPrivate *priv = impl->priv; + GFile *file; + + gtk_tree_model_get (model, iter, + MODEL_COL_FILE, &file, + -1); + + _gtk_bookmarks_manager_insert_bookmark (priv->bookmarks_manager, file, 0, NULL); /* NULL-GError */ + + g_object_unref (file); +} + +/* Callback used when the "Add to Bookmarks" menu item is activated */ +static void +add_to_shortcuts_cb (GtkMenuItem *item, + GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkTreeSelection *selection; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); + + gtk_tree_selection_selected_foreach (selection, + add_bookmark_foreach_cb, + impl); +} + +/* callback used to set data to clipboard */ +static void +copy_file_get_cb (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + guint info, + gpointer data) +{ + GSList *selected_files = data; + + if (selected_files) + { + gint num_files = g_slist_length (selected_files); + gchar **uris; + gint i; + GSList *l; + + uris = g_new (gchar *, num_files + 1); + uris[num_files] = NULL; /* null terminator */ + + i = 0; + + for (l = selected_files; l; l = l->next) + { + GFile *file = (GFile *) l->data; + + if (info == SELECTION_URI) + uris[i] = g_file_get_uri (file); + else /* if (info == SELECTION_TEXT) - let this be the fallback */ + uris[i] = g_file_get_parse_name (file); + + i++; + } + + if (info == SELECTION_URI) + gtk_selection_data_set_uris (selection_data, uris); + else /* if (info == SELECTION_TEXT) - let this be the fallback */ + { + char *str = g_strjoinv (" ", uris); + gtk_selection_data_set_text (selection_data, str, -1); + g_free (str); + } + + g_strfreev (uris); + } +} + +/* callback used to clear the clipboard data */ +static void +copy_file_clear_cb (GtkClipboard *clipboard, + gpointer data) +{ + GSList *selected_files = data; + + g_slist_foreach (selected_files, (GFunc) g_object_unref, NULL); + g_slist_free (selected_files); +} + +/* Callback used when the "Copy file’s location" menu item is activated */ +static void +copy_file_location_cb (GtkMenuItem *item, + GtkFileChooserWidget *impl) +{ + GSList *selected_files = NULL; + + selected_files = search_get_selected_files (impl); + + if (selected_files) + { + GtkClipboard *clipboard; + GtkTargetList *target_list; + GtkTargetEntry *targets; + int n_targets; + + clipboard = gtk_widget_get_clipboard (GTK_WIDGET (impl), GDK_SELECTION_CLIPBOARD); + + target_list = gtk_target_list_new (NULL, 0); + gtk_target_list_add_text_targets (target_list, SELECTION_TEXT); + gtk_target_list_add_uri_targets (target_list, SELECTION_URI); + + targets = gtk_target_table_new_from_list (target_list, &n_targets); + gtk_target_list_unref (target_list); + + gtk_clipboard_set_with_data (clipboard, targets, n_targets, + copy_file_get_cb, + copy_file_clear_cb, + selected_files); + + gtk_target_table_free (targets, n_targets); + } +} + +/* Callback used when the "Visit this file" menu item is activated */ +static void +visit_file_cb (GtkMenuItem *item, + GtkFileChooserWidget *impl) +{ + GSList *files; + + files = search_get_selected_files (impl); + + /* Sigh, just use the first one */ + if (files) + { + GFile *file = files->data; + + gtk_file_chooser_widget_select_file (GTK_FILE_CHOOSER (impl), file, NULL); /* NULL-GError */ + } + + g_slist_foreach (files, (GFunc) g_object_unref, NULL); + g_slist_free (files); +} + +/* callback used when the "Show Hidden Files" menu item is toggled */ +static void +show_hidden_toggled_cb (GtkCheckMenuItem *item, + GtkFileChooserWidget *impl) +{ + g_object_set (impl, + "show-hidden", gtk_check_menu_item_get_active (item), + NULL); +} + +/* Callback used when the "Show Size Column" menu item is toggled */ +static void +show_size_column_toggled_cb (GtkCheckMenuItem *item, + GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + priv->show_size_column = gtk_check_menu_item_get_active (item); + + gtk_tree_view_column_set_visible (priv->list_size_column, + priv->show_size_column); +} + +/* Shows an error dialog about not being able to select a dragged file */ +static void +error_selecting_dragged_file_dialog (GtkFileChooserWidget *impl, + GFile *file, + GError *error) +{ + error_dialog (impl, + _("Could not select file"), + file, error); +} + +static void +file_list_drag_data_select_uris (GtkFileChooserWidget *impl, + gchar **uris) +{ + int i; + char *uri; + GtkFileChooser *chooser = GTK_FILE_CHOOSER (impl); + + for (i = 1; uris[i]; i++) + { + GFile *file; + GError *error = NULL; + + uri = uris[i]; + file = g_file_new_for_uri (uri); + + gtk_file_chooser_widget_select_file (chooser, file, &error); + if (error) + error_selecting_dragged_file_dialog (impl, file, error); + + g_object_unref (file); + } +} + +struct FileListDragData +{ + GtkFileChooserWidget *impl; + gchar **uris; + GFile *file; +}; + +static void +file_list_drag_data_received_get_info_cb (GCancellable *cancellable, + GFileInfo *info, + const GError *error, + gpointer user_data) +{ + gboolean cancelled = g_cancellable_is_cancelled (cancellable); + struct FileListDragData *data = user_data; + GtkFileChooser *chooser = GTK_FILE_CHOOSER (data->impl); + GtkFileChooserWidgetPrivate *priv = data->impl->priv; + + if (cancellable != priv->file_list_drag_data_received_cancellable) + goto out; + + priv->file_list_drag_data_received_cancellable = NULL; + + if (cancelled || error) + goto out; + + if ((priv->action == GTK_FILE_CHOOSER_ACTION_OPEN || + priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) && + data->uris[1] == 0 && !error && _gtk_file_info_consider_as_directory (info)) + change_folder_and_display_error (data->impl, data->file, FALSE); + else + { + GError *error = NULL; + + gtk_file_chooser_widget_unselect_all (chooser); + gtk_file_chooser_widget_select_file (chooser, data->file, &error); + if (error) + error_selecting_dragged_file_dialog (data->impl, data->file, error); + else + browse_files_center_selected_row (data->impl); + } + + if (priv->select_multiple) + file_list_drag_data_select_uris (data->impl, data->uris); + +out: + g_object_unref (data->impl); + g_strfreev (data->uris); + g_object_unref (data->file); + g_free (data); + + g_object_unref (cancellable); +} + +static void +file_list_drag_data_received_cb (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection_data, + guint info, + guint time_, + gpointer data) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (data); + GtkFileChooserWidgetPrivate *priv = impl->priv; + gchar **uris; + char *uri; + GFile *file; + + /* Allow only drags from other widgets; see bug #533891. */ + if (gtk_drag_get_source_widget (context) == widget) + { + g_signal_stop_emission_by_name (widget, "drag-data-received"); + return; + } + + /* Parse the text/uri-list string, navigate to the first one */ + uris = gtk_selection_data_get_uris (selection_data); + if (uris && uris[0]) + { + struct FileListDragData *data; + + uri = uris[0]; + file = g_file_new_for_uri (uri); + + data = g_new0 (struct FileListDragData, 1); + data->impl = g_object_ref (impl); + data->uris = uris; + data->file = file; + + if (priv->file_list_drag_data_received_cancellable) + g_cancellable_cancel (priv->file_list_drag_data_received_cancellable); + + priv->file_list_drag_data_received_cancellable = + _gtk_file_system_get_info (priv->file_system, file, + "standard::type", + file_list_drag_data_received_get_info_cb, + data); + } + + g_signal_stop_emission_by_name (widget, "drag-data-received"); +} + +/* Don't do anything with the drag_drop signal */ +static gboolean +file_list_drag_drop_cb (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time_, + GtkFileChooserWidget *impl) +{ + g_signal_stop_emission_by_name (widget, "drag-drop"); + return TRUE; +} + +/* Disable the normal tree drag motion handler, it makes it look like you're + dropping the dragged item onto a tree item */ +static gboolean +file_list_drag_motion_cb (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time_, + GtkFileChooserWidget *impl) +{ + g_signal_stop_emission_by_name (widget, "drag-motion"); + return TRUE; +} + +/* Sensitizes the "Copy file’s location" and other context menu items if there is actually + * a selection active. + */ +static void +check_file_list_menu_sensitivity (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + gint num_selected; + gboolean all_files; + gboolean all_folders; + gboolean active; + + selection_check (impl, &num_selected, &all_files, &all_folders); + + active = (num_selected != 0); + + if (priv->browse_files_popup_menu_copy_file_location_item) + gtk_widget_set_sensitive (priv->browse_files_popup_menu_copy_file_location_item, active); + if (priv->browse_files_popup_menu_add_shortcut_item) + gtk_widget_set_sensitive (priv->browse_files_popup_menu_add_shortcut_item, active && all_folders); + if (priv->browse_files_popup_menu_visit_file_item) + gtk_widget_set_sensitive (priv->browse_files_popup_menu_visit_file_item, active); +} + +static GtkWidget * +file_list_add_menu_item (GtkFileChooserWidget *impl, + const char *mnemonic_label, + GCallback callback) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkWidget *item; + + item = gtk_menu_item_new_with_mnemonic (mnemonic_label); + g_signal_connect (item, "activate", callback, impl); + gtk_widget_show (item); + gtk_menu_shell_append (GTK_MENU_SHELL (priv->browse_files_popup_menu), item); + + return item; +} + +static GtkWidget * +file_list_add_check_menu_item (GtkFileChooserWidget *impl, + const char *mnemonic_label, + GCallback callback) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkWidget *item; + + item = gtk_check_menu_item_new_with_mnemonic (mnemonic_label); + g_signal_connect (item, "toggled", callback, impl); + gtk_widget_show (item); + gtk_menu_shell_append (GTK_MENU_SHELL (priv->browse_files_popup_menu), item); + + return item; +} + +/* Constructs the popup menu for the file list if needed */ +static void +file_list_build_popup_menu (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkWidget *item; + + if (priv->browse_files_popup_menu) + return; + + priv->browse_files_popup_menu = gtk_menu_new (); + gtk_menu_attach_to_widget (GTK_MENU (priv->browse_files_popup_menu), + priv->browse_files_tree_view, + popup_menu_detach_cb); + + priv->browse_files_popup_menu_visit_file_item = file_list_add_menu_item (impl, _("_Visit File"), + G_CALLBACK (visit_file_cb)); + + priv->browse_files_popup_menu_copy_file_location_item = file_list_add_menu_item (impl, _("_Copy Location"), + G_CALLBACK (copy_file_location_cb)); + + priv->browse_files_popup_menu_add_shortcut_item = file_list_add_menu_item (impl, _("_Add to Bookmarks"), + G_CALLBACK (add_to_shortcuts_cb)); + + item = gtk_separator_menu_item_new (); + gtk_widget_show (item); + gtk_menu_shell_append (GTK_MENU_SHELL (priv->browse_files_popup_menu), item); + + priv->browse_files_popup_menu_hidden_files_item = file_list_add_check_menu_item (impl, _("Show _Hidden Files"), + G_CALLBACK (show_hidden_toggled_cb)); + + priv->browse_files_popup_menu_size_column_item = file_list_add_check_menu_item (impl, _("Show _Size Column"), + G_CALLBACK (show_size_column_toggled_cb)); + + check_file_list_menu_sensitivity (impl); +} + +/* Updates the popup menu for the file list, creating it if necessary */ +static void +file_list_update_popup_menu (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + file_list_build_popup_menu (impl); + + /* The sensitivity of the Add to Bookmarks item is set in + * bookmarks_check_add_sensitivity() + */ + + /* 'Visit this file' */ + gtk_widget_set_visible (priv->browse_files_popup_menu_visit_file_item, (priv->operation_mode != OPERATION_MODE_BROWSE)); + + /* 'Show Hidden Files' */ + g_signal_handlers_block_by_func (priv->browse_files_popup_menu_hidden_files_item, + G_CALLBACK (show_hidden_toggled_cb), impl); + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (priv->browse_files_popup_menu_hidden_files_item), + priv->show_hidden); + g_signal_handlers_unblock_by_func (priv->browse_files_popup_menu_hidden_files_item, + G_CALLBACK (show_hidden_toggled_cb), impl); + + /* 'Show Size Column' */ + g_signal_handlers_block_by_func (priv->browse_files_popup_menu_size_column_item, + G_CALLBACK (show_size_column_toggled_cb), impl); + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (priv->browse_files_popup_menu_size_column_item), + priv->show_size_column); + g_signal_handlers_unblock_by_func (priv->browse_files_popup_menu_size_column_item, + G_CALLBACK (show_size_column_toggled_cb), impl); +} + +static void +popup_position_func (GtkMenu *menu, + gint *x, + gint *y, + gboolean *push_in, + gpointer user_data) +{ + GtkAllocation allocation; + GtkWidget *widget = GTK_WIDGET (user_data); + GdkScreen *screen = gtk_widget_get_screen (widget); + GtkRequisition req; + gint monitor_num; + GdkRectangle monitor; + + g_return_if_fail (gtk_widget_get_realized (widget)); + + gdk_window_get_origin (gtk_widget_get_window (widget), x, y); + + gtk_widget_get_preferred_size (GTK_WIDGET (menu), + &req, NULL); + + gtk_widget_get_allocation (widget, &allocation); + *x += (allocation.width - req.width) / 2; + *y += (allocation.height - req.height) / 2; + + monitor_num = gdk_screen_get_monitor_at_point (screen, *x, *y); + gtk_menu_set_monitor (menu, monitor_num); + gdk_screen_get_monitor_workarea (screen, monitor_num, &monitor); + + *x = CLAMP (*x, monitor.x, monitor.x + MAX (0, monitor.width - req.width)); + *y = CLAMP (*y, monitor.y, monitor.y + MAX (0, monitor.height - req.height)); + + *push_in = FALSE; +} + +static void +file_list_popup_menu (GtkFileChooserWidget *impl, + GdkEventButton *event) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + file_list_update_popup_menu (impl); + if (event) + gtk_menu_popup (GTK_MENU (priv->browse_files_popup_menu), + NULL, NULL, NULL, NULL, + event->button, event->time); + else + { + gtk_menu_popup (GTK_MENU (priv->browse_files_popup_menu), + NULL, NULL, + popup_position_func, priv->browse_files_tree_view, + 0, GDK_CURRENT_TIME); + gtk_menu_shell_select_first (GTK_MENU_SHELL (priv->browse_files_popup_menu), + FALSE); + } + +} + +/* Callback used for the GtkWidget::popup-menu signal of the file list */ +static gboolean +list_popup_menu_cb (GtkWidget *widget, + GtkFileChooserWidget *impl) +{ + file_list_popup_menu (impl, NULL); + return TRUE; +} + +/* Callback used when a button is pressed on the file list. We trap button 3 to + * bring up a popup menu. + */ +static gboolean +list_button_press_event_cb (GtkWidget *widget, + GdkEventButton *event, + GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + static gboolean in_press = FALSE; + + if (in_press) + return FALSE; + + if (!gdk_event_triggers_context_menu ((GdkEvent *) event)) + return FALSE; + + in_press = TRUE; + gtk_widget_event (priv->browse_files_tree_view, (GdkEvent *) event); + in_press = FALSE; + + file_list_popup_menu (impl, event); + return TRUE; +} + +typedef struct { + OperationMode operation_mode; + gint general_column; + gint model_column; +} ColumnMap; + +/* Sets the sort column IDs for the file list; needs to be done whenever we + * change the model on the treeview. + */ +static void +file_list_set_sort_column_ids (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + gtk_tree_view_column_set_sort_column_id (priv->list_name_column, MODEL_COL_NAME); + gtk_tree_view_column_set_sort_column_id (priv->list_mtime_column, MODEL_COL_MTIME); + gtk_tree_view_column_set_sort_column_id (priv->list_size_column, MODEL_COL_SIZE); +} + +static gboolean +file_list_query_tooltip_cb (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_tip, + GtkTooltip *tooltip, + gpointer user_data) +{ + GtkFileChooserWidget *impl = user_data; + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + GFile *file; + gchar *filename; + + if (priv->operation_mode == OPERATION_MODE_BROWSE) + return FALSE; + + + if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (priv->browse_files_tree_view), + &x, &y, + keyboard_tip, + &model, &path, &iter)) + return FALSE; + + gtk_tree_model_get (model, &iter, + MODEL_COL_FILE, &file, + -1); + + if (file == NULL) + { + gtk_tree_path_free (path); + return FALSE; + } + + filename = g_file_get_path (file); + gtk_tooltip_set_text (tooltip, filename); + gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (priv->browse_files_tree_view), + tooltip, + path); + + g_free (filename); + g_object_unref (file); + gtk_tree_path_free (path); + + return TRUE; +} + +static void +set_icon_cell_renderer_fixed_size (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + gint xpad, ypad; + + gtk_cell_renderer_get_padding (priv->list_pixbuf_renderer, &xpad, &ypad); + gtk_cell_renderer_set_fixed_size (priv->list_pixbuf_renderer, + xpad * 2 + priv->icon_size, + ypad * 2 + priv->icon_size); +} + + +static void +location_entry_create (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (!priv->location_entry) + priv->location_entry = _gtk_file_chooser_entry_new (TRUE); + + _gtk_file_chooser_entry_set_local_only (GTK_FILE_CHOOSER_ENTRY (priv->location_entry), priv->local_only); + _gtk_file_chooser_entry_set_action (GTK_FILE_CHOOSER_ENTRY (priv->location_entry), priv->action); + gtk_entry_set_width_chars (GTK_ENTRY (priv->location_entry), 45); + gtk_entry_set_activates_default (GTK_ENTRY (priv->location_entry), TRUE); +} + +/* Creates the widgets specific to Save mode */ +static void +save_widgets_create (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkWidget *vbox; + GtkWidget *widget; + + if (priv->save_widgets != NULL) + return; + + location_switch_to_path_bar (impl); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + + priv->save_widgets_table = gtk_grid_new (); + gtk_box_pack_start (GTK_BOX (vbox), priv->save_widgets_table, FALSE, FALSE, 0); + gtk_widget_show (priv->save_widgets_table); + gtk_grid_set_row_spacing (GTK_GRID (priv->save_widgets_table), 12); + gtk_grid_set_column_spacing (GTK_GRID (priv->save_widgets_table), 12); + + /* Label */ + + widget = gtk_label_new_with_mnemonic (_("_Name:")); + gtk_widget_set_halign (widget, GTK_ALIGN_START); + gtk_widget_set_valign (widget, GTK_ALIGN_CENTER); + gtk_grid_attach (GTK_GRID (priv->save_widgets_table), widget, 0, 0, 1, 1); + gtk_widget_show (widget); + + /* Location entry */ + + location_entry_create (impl); + gtk_widget_set_hexpand (priv->location_entry, TRUE); + gtk_grid_attach (GTK_GRID (priv->save_widgets_table), priv->location_entry, 1, 0, 1, 1); + gtk_widget_show (priv->location_entry); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), priv->location_entry); + + /* Folder combo */ + priv->save_folder_label = gtk_label_new (NULL); + gtk_widget_set_halign (priv->save_folder_label, GTK_ALIGN_START); + gtk_widget_set_valign (priv->save_folder_label, GTK_ALIGN_CENTER); + gtk_grid_attach (GTK_GRID (priv->save_widgets_table), priv->save_folder_label, 0, 1, 1, 1); + gtk_widget_show (priv->save_folder_label); + + priv->save_widgets = vbox; + gtk_box_pack_start (GTK_BOX (impl), priv->save_widgets, FALSE, FALSE, 0); + gtk_box_reorder_child (GTK_BOX (impl), priv->save_widgets, 0); + gtk_widget_show (priv->save_widgets); +} + +/* Destroys the widgets specific to Save mode */ +static void +save_widgets_destroy (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (priv->save_widgets == NULL) + return; + + gtk_widget_destroy (priv->save_widgets); + priv->save_widgets = NULL; + priv->save_widgets_table = NULL; + priv->location_entry = NULL; + priv->save_folder_label = NULL; +} + +/* Turns on the path bar widget. Can be called even if we are already in that + * mode. + */ +static void +location_switch_to_path_bar (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (priv->location_entry) + { + gtk_widget_destroy (priv->location_entry); + priv->location_entry = NULL; + } + + gtk_widget_hide (priv->location_entry_box); +} + +/* Turns on the location entry. Can be called even if we are already in that + * mode. + */ +static void +location_switch_to_filename_entry (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + /* when in search or recent files mode, we are not showing the + * location_entry_box container, so there's no point in switching + * to it. + */ + if (priv->operation_mode == OPERATION_MODE_SEARCH || + priv->operation_mode == OPERATION_MODE_RECENT) + return; + + /* Box */ + + gtk_widget_show (priv->location_entry_box); + + /* Entry */ + + if (!priv->location_entry) + { + location_entry_create (impl); + gtk_box_pack_start (GTK_BOX (priv->location_entry_box), priv->location_entry, TRUE, TRUE, 0); + gtk_label_set_mnemonic_widget (GTK_LABEL (priv->location_label), priv->location_entry); + } + + /* Configure the entry */ + + _gtk_file_chooser_entry_set_base_folder (GTK_FILE_CHOOSER_ENTRY (priv->location_entry), priv->current_folder); + + /* Done */ + + gtk_widget_show (priv->location_entry); + gtk_widget_grab_focus (priv->location_entry); +} + +/* Sets a new location mode. set_buttons determines whether the toggle button + * for the mode will also be changed. + */ +static void +location_mode_set (GtkFileChooserWidget *impl, + LocationMode new_mode, + gboolean set_button) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN || + priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) + { + GtkWindow *toplevel; + GtkWidget *current_focus; + gboolean button_active; + gboolean switch_to_file_list; + + switch (new_mode) + { + case LOCATION_MODE_PATH_BAR: + button_active = FALSE; + + /* The location_entry will disappear when we switch to path bar mode. So, + * we'll focus the file list in that case, to avoid having a window with + * no focused widget. + */ + toplevel = get_toplevel (GTK_WIDGET (impl)); + switch_to_file_list = FALSE; + if (toplevel) + { + current_focus = gtk_window_get_focus (toplevel); + if (!current_focus || current_focus == priv->location_entry) + switch_to_file_list = TRUE; + } + + location_switch_to_path_bar (impl); + + if (switch_to_file_list) + gtk_widget_grab_focus (priv->browse_files_tree_view); + + break; + + case LOCATION_MODE_FILENAME_ENTRY: + button_active = TRUE; + location_switch_to_filename_entry (impl); + break; + + default: + g_assert_not_reached (); + return; + } + + if (set_button) + { + g_signal_handlers_block_by_func (priv->location_button, + G_CALLBACK (location_button_toggled_cb), impl); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->location_button), button_active); + + g_signal_handlers_unblock_by_func (priv->location_button, + G_CALLBACK (location_button_toggled_cb), impl); + } + } + + priv->location_mode = new_mode; } static void -gtk_file_chooser_widget_init (GtkFileChooserWidget *self) +location_toggle_popup_handler (GtkFileChooserWidget *impl) { - self->priv = gtk_file_chooser_widget_get_instance_private (self); + GtkFileChooserWidgetPrivate *priv = impl->priv; + + /* when in search or recent files mode, we are not showing the + * location_entry_box container, so there's no point in switching + * to it. + */ + if (priv->operation_mode == OPERATION_MODE_SEARCH || + priv->operation_mode == OPERATION_MODE_RECENT) + return; + + /* If the file entry is not visible, show it. + * If it is visible, turn it off only if it is focused. Otherwise, switch to the entry. + */ + if (priv->location_mode == LOCATION_MODE_PATH_BAR) + { + location_mode_set (impl, LOCATION_MODE_FILENAME_ENTRY, TRUE); + } + else if (priv->location_mode == LOCATION_MODE_FILENAME_ENTRY) + { + if (gtk_widget_has_focus (priv->location_entry)) + { + location_mode_set (impl, LOCATION_MODE_PATH_BAR, TRUE); + } + else + { + gtk_widget_grab_focus (priv->location_entry); + } + } +} + +/* Callback used when one of the location mode buttons is toggled */ +static void +location_button_toggled_cb (GtkToggleButton *toggle, + GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + gboolean is_active; + LocationMode new_mode; + + is_active = gtk_toggle_button_get_active (toggle); + + if (is_active) + { + g_assert (priv->location_mode == LOCATION_MODE_PATH_BAR); + new_mode = LOCATION_MODE_FILENAME_ENTRY; + } + else + { + g_assert (priv->location_mode == LOCATION_MODE_FILENAME_ENTRY); + new_mode = LOCATION_MODE_PATH_BAR; + } + + location_mode_set (impl, new_mode, FALSE); +} + +typedef enum { + PATH_BAR_FOLDER_PATH, + PATH_BAR_SELECT_A_FOLDER, + PATH_BAR_ERROR_NO_FILENAME, + PATH_BAR_ERROR_NO_FOLDER, + PATH_BAR_RECENTLY_USED, + PATH_BAR_SEARCH +} PathBarMode; + +/* Sets the info bar to show the appropriate informational or warning message */ +static void +info_bar_set (GtkFileChooserWidget *impl, PathBarMode mode) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + char *str; + gboolean free_str; + GtkMessageType message_type; + + free_str = FALSE; + + switch (mode) + { + case PATH_BAR_SELECT_A_FOLDER: + str = g_strconcat ("<i>", _("Please select a folder below"), "</i>", NULL); + free_str = TRUE; + message_type = GTK_MESSAGE_OTHER; + break; + + case PATH_BAR_ERROR_NO_FILENAME: + str = _("Please type a file name"); + message_type = GTK_MESSAGE_WARNING; + break; + + case PATH_BAR_ERROR_NO_FOLDER: + str = _("Please select a folder below"); + message_type = GTK_MESSAGE_WARNING; + break; + + default: + g_assert_not_reached (); + return; + } + + gtk_info_bar_set_message_type (GTK_INFO_BAR (priv->browse_select_a_folder_info_bar), message_type); + gtk_image_set_from_icon_name (GTK_IMAGE (priv->browse_select_a_folder_icon), + (message_type == GTK_MESSAGE_WARNING) ? "dialog-warning-symbolic" : "folder-symbolic", + GTK_ICON_SIZE_MENU); + gtk_label_set_markup (GTK_LABEL (priv->browse_select_a_folder_label), str); + + if (free_str) + g_free (str); +} + +/* Sets the path bar's mode to show a label, the actual folder path, or a + * warning message. You may call this function with PATH_BAR_ERROR_* directly + * if the pathbar is already showing the widgets you expect; otherwise, call + * path_bar_update() instead to set the appropriate widgets automatically. + */ +static void +path_bar_set_mode (GtkFileChooserWidget *impl, PathBarMode mode) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + gboolean path_bar_visible = FALSE; + gboolean special_mode_widgets_visible = FALSE; + gboolean info_bar_visible = FALSE; + gboolean create_folder_visible = FALSE; + + char *tmp; + + switch (mode) + { + case PATH_BAR_FOLDER_PATH: + path_bar_visible = TRUE; + break; + + case PATH_BAR_SELECT_A_FOLDER: + case PATH_BAR_ERROR_NO_FILENAME: + case PATH_BAR_ERROR_NO_FOLDER: + info_bar_set (impl, mode); + info_bar_visible = TRUE; + break; + + case PATH_BAR_RECENTLY_USED: + gtk_image_set_from_icon_name (GTK_IMAGE (priv->browse_special_mode_icon), "document-open-recent", GTK_ICON_SIZE_BUTTON); + + tmp = g_strdup_printf ("<b>%s</b>", _("Recently Used")); + gtk_label_set_markup (GTK_LABEL (priv->browse_special_mode_label), tmp); + g_free (tmp); + + special_mode_widgets_visible = TRUE; + break; + + case PATH_BAR_SEARCH: + gtk_image_set_from_icon_name (GTK_IMAGE (priv->browse_special_mode_icon), "edit-find-symbolic", GTK_ICON_SIZE_BUTTON); + + tmp = g_strdup_printf ("<b>%s</b>", _("Search:")); + gtk_label_set_markup (GTK_LABEL (priv->browse_special_mode_label), tmp); + g_free (tmp); + + special_mode_widgets_visible = TRUE; + break; + + default: + g_assert_not_reached (); + } + + gtk_widget_set_visible (priv->browse_path_bar, path_bar_visible); + gtk_widget_set_visible (priv->browse_special_mode_icon, special_mode_widgets_visible); + gtk_widget_set_visible (priv->browse_special_mode_label, special_mode_widgets_visible); + gtk_widget_set_visible (priv->browse_select_a_folder_info_bar, info_bar_visible); - gtk_orientable_set_orientation (GTK_ORIENTABLE (self), - GTK_ORIENTATION_VERTICAL); + if (path_bar_visible) + { + if (priv->create_folders + && priv->action != GTK_FILE_CHOOSER_ACTION_OPEN + && priv->operation_mode != OPERATION_MODE_RECENT) + create_folder_visible = TRUE; + } + + gtk_widget_set_visible (priv->browse_new_folder_button, create_folder_visible); } + static GObject* gtk_file_chooser_widget_constructor (GType type, - guint n_construct_properties, - GObjectConstructParam *construct_params) + guint n_construct_properties, + GObjectConstructParam *construct_params) { + GtkFileChooserWidget *impl; GtkFileChooserWidgetPrivate *priv; GObject *object; - + + profile_start ("start", NULL); + object = G_OBJECT_CLASS (gtk_file_chooser_widget_parent_class)->constructor (type, - n_construct_properties, - construct_params); - priv = gtk_file_chooser_widget_get_instance_private (GTK_FILE_CHOOSER_WIDGET (object)); + n_construct_properties, + construct_params); + impl = GTK_FILE_CHOOSER_WIDGET (object); + priv = impl->priv; - priv->impl = _gtk_file_chooser_default_new (); - - gtk_box_pack_start (GTK_BOX (object), priv->impl, TRUE, TRUE, 0); - gtk_widget_show (priv->impl); + g_assert (priv->file_system); - _gtk_file_chooser_set_delegate (GTK_FILE_CHOOSER (object), - GTK_FILE_CHOOSER (priv->impl)); + update_appearance (impl); - _gtk_file_chooser_embed_set_delegate (GTK_FILE_CHOOSER_EMBED (object), - GTK_FILE_CHOOSER_EMBED (priv->impl)); + profile_end ("end", NULL); return object; } +/* Sets the extra_widget by packing it in the appropriate place */ static void -gtk_file_chooser_widget_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) +set_extra_widget (GtkFileChooserWidget *impl, + GtkWidget *extra_widget) { - GtkFileChooserWidgetPrivate *priv; - - priv = gtk_file_chooser_widget_get_instance_private (GTK_FILE_CHOOSER_WIDGET (object)); + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (extra_widget) + { + g_object_ref (extra_widget); + /* FIXME: is this right ? */ + gtk_widget_show (extra_widget); + } + + if (priv->extra_widget) + { + gtk_container_remove (GTK_CONTAINER (priv->extra_align), priv->extra_widget); + g_object_unref (priv->extra_widget); + } + + priv->extra_widget = extra_widget; + if (priv->extra_widget) + { + gtk_container_add (GTK_CONTAINER (priv->extra_align), priv->extra_widget); + gtk_widget_show (priv->extra_align); + } + else + gtk_widget_hide (priv->extra_align); +} + +static void +switch_to_home_dir (GtkFileChooserWidget *impl) +{ + const gchar *home = g_get_home_dir (); + GFile *home_file; + + if (home == NULL) + return; + + home_file = g_file_new_for_path (home); + + gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (impl), home_file, NULL); /* NULL-GError */ + + g_object_unref (home_file); +} + +static void +set_local_only (GtkFileChooserWidget *impl, + gboolean local_only) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (local_only != priv->local_only) + { + priv->local_only = local_only; + + if (priv->location_entry) + _gtk_file_chooser_entry_set_local_only (GTK_FILE_CHOOSER_ENTRY (priv->location_entry), local_only); + + gtk_places_sidebar_set_local_only (GTK_PLACES_SIDEBAR (priv->places_sidebar), local_only); + + if (local_only && priv->current_folder && + !_gtk_file_has_native_path (priv->current_folder)) + { + /* If we are pointing to a non-local folder, make an effort to change + * back to a local folder, but it's really up to the app to not cause + * such a situation, so we ignore errors. + */ + switch_to_home_dir (impl); + } + } +} + +/* Sets the file chooser to multiple selection mode */ +static void +set_select_multiple (GtkFileChooserWidget *impl, + gboolean select_multiple, + gboolean property_notify) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkTreeSelection *selection; + GtkSelectionMode mode; + + if (select_multiple == priv->select_multiple) + return; + + mode = select_multiple ? GTK_SELECTION_MULTIPLE : GTK_SELECTION_SINGLE; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); + gtk_tree_selection_set_mode (selection, mode); + + gtk_tree_view_set_rubber_banding (GTK_TREE_VIEW (priv->browse_files_tree_view), select_multiple); + + priv->select_multiple = select_multiple; + g_object_notify (G_OBJECT (impl), "select-multiple"); + + check_preview_change (impl); +} + +static void +set_file_system_backend (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + profile_start ("start for backend", "default"); + + priv->file_system = _gtk_file_system_new (); + + profile_end ("end", NULL); +} + +static void +unset_file_system_backend (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + g_object_unref (priv->file_system); + + priv->file_system = NULL; +} + +/* Saves the widgets around the pathbar so they can be reparented later + * in the correct place. This function must be called paired with + * restore_path_bar(). + */ +static void +save_path_bar (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkWidget *parent; + + g_object_ref (priv->browse_path_bar_hbox); + + parent = gtk_widget_get_parent (priv->browse_path_bar_hbox); + if (parent) + gtk_container_remove (GTK_CONTAINER (parent), priv->browse_path_bar_hbox); +} + +/* Reparents the path bar and the "Create folder" button to the right place: + * Above the file list in Open mode, or to the right of the "Save in folder:" + * label in Save mode. The save_path_bar() function must be called before this + * one. + */ +static void +restore_path_bar (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN + || priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) + { + gtk_box_pack_start (GTK_BOX (priv->browse_header_box), priv->browse_path_bar_hbox, FALSE, FALSE, 0); + gtk_box_reorder_child (GTK_BOX (priv->browse_header_box), priv->browse_path_bar_hbox, 0); + } + else if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE + || priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) + { + gtk_widget_set_hexpand (priv->browse_path_bar_hbox, TRUE); + gtk_grid_attach (GTK_GRID (priv->save_widgets_table), priv->browse_path_bar_hbox, 1, 1, 1, 1); + } + else + g_assert_not_reached (); + + g_object_unref (priv->browse_path_bar_hbox); +} + +/* Takes the folder stored in a row in the recent_model, and puts it in the pathbar */ +static void +put_recent_folder_in_pathbar (GtkFileChooserWidget *impl, GtkTreeIter *iter) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GFile *file; + + gtk_tree_model_get (GTK_TREE_MODEL (priv->recent_model), iter, + MODEL_COL_FILE, &file, + -1); + _gtk_path_bar_set_file (GTK_PATH_BAR (priv->browse_path_bar), file, FALSE); + g_object_unref (file); +} + +/* Sets the pathbar in the appropriate mode according to the current operation mode and action. This is the central function for + * dealing with the pathbar's widgets; as long as impl->action and impl->operation_mode are set correctly, then calling this + * function will update all the pathbar's widgets. + */ +static void +path_bar_update (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + PathBarMode mode; + + switch (priv->operation_mode) + { + case OPERATION_MODE_BROWSE: + mode = PATH_BAR_FOLDER_PATH; + break; + + case OPERATION_MODE_RECENT: + if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) + { + GtkTreeSelection *selection; + gboolean have_selected; + GtkTreeIter iter; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); + + /* Save mode means single-selection mode, so the following is valid */ + have_selected = gtk_tree_selection_get_selected (selection, NULL, &iter); + + if (have_selected) + { + mode = PATH_BAR_FOLDER_PATH; + put_recent_folder_in_pathbar (impl, &iter); + } + else + mode = PATH_BAR_SELECT_A_FOLDER; + } + else + mode = PATH_BAR_RECENTLY_USED; + + break; + + case OPERATION_MODE_SEARCH: + mode = PATH_BAR_SEARCH; + break; + + default: + g_assert_not_reached (); + return; + } + + path_bar_set_mode (impl, mode); +} + +static void +operation_mode_discard_search_widgets (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (priv->search_hbox) + { + gtk_widget_destroy (priv->search_hbox); + + priv->search_hbox = NULL; + priv->search_entry = NULL; + } +} + +/* Stops running operations like populating the browse model, searches, and the recent-files model */ +static void +operation_mode_stop (GtkFileChooserWidget *impl, OperationMode mode) +{ + switch (mode) + { + case OPERATION_MODE_BROWSE: + stop_loading_and_clear_list_model (impl, TRUE); + break; + + case OPERATION_MODE_SEARCH: + search_stop_searching (impl, FALSE); + search_clear_model (impl, TRUE); + + operation_mode_discard_search_widgets (impl); + break; + + case OPERATION_MODE_RECENT: + recent_stop_loading (impl); + recent_clear_model (impl, TRUE); + break; + + default: + g_assert_not_reached (); + } +} + +static void +operation_mode_set_browse (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + path_bar_update (impl); + + if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN || + priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) + { + gtk_widget_show (priv->location_button); + location_mode_set (impl, priv->location_mode, TRUE); + + if (priv->location_mode == LOCATION_MODE_FILENAME_ENTRY) + gtk_widget_show (priv->location_entry_box); + } +} + +static void +operation_mode_set_search (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + g_assert (priv->search_hbox == NULL); + g_assert (priv->search_entry == NULL); + g_assert (priv->search_model == NULL); + + search_setup_widgets (impl); +} + +static void +operation_mode_set_recent (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + path_bar_update (impl); + + /* Hide the location widgets temporarily */ + if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN || + priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) + { + gtk_widget_hide (priv->location_button); + gtk_widget_hide (priv->location_entry_box); + } + + recent_start_loading (impl); +} + +static void +operation_mode_set (GtkFileChooserWidget *impl, OperationMode mode) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GFile *file; + + operation_mode_stop (impl, priv->operation_mode); + + priv->operation_mode = mode; + + switch (priv->operation_mode) + { + case OPERATION_MODE_BROWSE: + operation_mode_set_browse (impl); + break; + + case OPERATION_MODE_SEARCH: + operation_mode_set_search (impl); + break; + + case OPERATION_MODE_RECENT: + operation_mode_set_recent (impl); + file = g_file_new_for_uri ("recent:///"); + gtk_places_sidebar_set_location (GTK_PLACES_SIDEBAR (priv->places_sidebar), file); + g_object_unref (file); + break; + + default: + g_assert_not_reached (); + return; + } +} + +/* This function is basically a do_all function. + * + * It sets the visibility on all the widgets based on the current state, and + * moves the custom_widget if needed. + */ +static void +update_appearance (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + save_path_bar (impl); + + if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE || + priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) + { + const char *text; + + gtk_widget_hide (priv->location_button); + save_widgets_create (impl); + + if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) + text = _("Save in folder:"); + else + text = _("Create in folder:"); + + gtk_label_set_text (GTK_LABEL (priv->save_folder_label), text); + + if (priv->select_multiple) + { + g_warning ("Save mode cannot be set in conjunction with multiple selection mode. " + "Re-setting to single selection mode."); + set_select_multiple (impl, FALSE, TRUE); + } + } + else if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN || + priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) + { + gtk_widget_show (priv->location_button); + save_widgets_destroy (impl); + location_mode_set (impl, priv->location_mode, TRUE); + } + + if (priv->location_entry) + _gtk_file_chooser_entry_set_action (GTK_FILE_CHOOSER_ENTRY (priv->location_entry), priv->action); + + restore_path_bar (impl); + path_bar_update (impl); + + /* This *is* needed; we need to redraw the file list because the "sensitivity" + * of files may change depending whether we are in a file or folder-only mode. + */ + gtk_widget_queue_draw (priv->browse_files_tree_view); + + emit_default_size_changed (impl); +} + +static void +gtk_file_chooser_widget_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) + +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (object); + GtkFileChooserWidgetPrivate *priv = impl->priv; switch (prop_id) { + case GTK_FILE_CHOOSER_PROP_ACTION: + { + GtkFileChooserAction action = g_value_get_enum (value); + + if (action != priv->action) + { + gtk_file_chooser_widget_unselect_all (GTK_FILE_CHOOSER (impl)); + + if ((action == GTK_FILE_CHOOSER_ACTION_SAVE || + action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) + && priv->select_multiple) + { + g_warning ("Tried to change the file chooser action to SAVE or CREATE_FOLDER, but " + "this is not allowed in multiple selection mode. Resetting the file chooser " + "to single selection mode."); + set_select_multiple (impl, FALSE, TRUE); + } + priv->action = action; + update_cell_renderer_attributes (impl); + update_appearance (impl); + settings_load (impl); + } + } + break; + + case GTK_FILE_CHOOSER_PROP_FILTER: + set_current_filter (impl, g_value_get_object (value)); + break; + + case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY: + set_local_only (impl, g_value_get_boolean (value)); + break; + + case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET: + set_preview_widget (impl, g_value_get_object (value)); + break; + + case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE: + priv->preview_widget_active = g_value_get_boolean (value); + update_preview_widget_visibility (impl); + break; + + case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL: + priv->use_preview_label = g_value_get_boolean (value); + update_preview_widget_visibility (impl); + break; + + case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET: + set_extra_widget (impl, g_value_get_object (value)); + break; + + case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE: + { + gboolean select_multiple = g_value_get_boolean (value); + if ((priv->action == GTK_FILE_CHOOSER_ACTION_SAVE || + priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) + && select_multiple) + { + g_warning ("Tried to set the file chooser to multiple selection mode, but this is " + "not allowed in SAVE or CREATE_FOLDER modes. Ignoring the change and " + "leaving the file chooser in single selection mode."); + return; + } + + set_select_multiple (impl, select_multiple, FALSE); + } + break; + + case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN: + { + gboolean show_hidden = g_value_get_boolean (value); + if (show_hidden != priv->show_hidden) + { + priv->show_hidden = show_hidden; + + if (priv->browse_files_model) + _gtk_file_system_model_set_show_hidden (priv->browse_files_model, show_hidden); + } + } + break; + + case GTK_FILE_CHOOSER_PROP_DO_OVERWRITE_CONFIRMATION: + { + gboolean do_overwrite_confirmation = g_value_get_boolean (value); + priv->do_overwrite_confirmation = do_overwrite_confirmation; + } + break; + + case GTK_FILE_CHOOSER_PROP_CREATE_FOLDERS: + { + gboolean create_folders = g_value_get_boolean (value); + priv->create_folders = create_folders; + update_appearance (impl); + } + break; + default: - g_object_set_property (G_OBJECT (priv->impl), pspec->name, value); + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void -gtk_file_chooser_widget_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) +gtk_file_chooser_widget_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) { - GtkFileChooserWidgetPrivate *priv; + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (object); + GtkFileChooserWidgetPrivate *priv = impl->priv; + + switch (prop_id) + { + case GTK_FILE_CHOOSER_PROP_ACTION: + g_value_set_enum (value, priv->action); + break; + + case GTK_FILE_CHOOSER_PROP_FILTER: + g_value_set_object (value, priv->current_filter); + break; + + case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY: + g_value_set_boolean (value, priv->local_only); + break; + + case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET: + g_value_set_object (value, priv->preview_widget); + break; + + case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE: + g_value_set_boolean (value, priv->preview_widget_active); + break; + + case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL: + g_value_set_boolean (value, priv->use_preview_label); + break; + + case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET: + g_value_set_object (value, priv->extra_widget); + break; + + case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE: + g_value_set_boolean (value, priv->select_multiple); + break; + + case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN: + g_value_set_boolean (value, priv->show_hidden); + break; + + case GTK_FILE_CHOOSER_PROP_DO_OVERWRITE_CONFIRMATION: + g_value_set_boolean (value, priv->do_overwrite_confirmation); + break; + + case GTK_FILE_CHOOSER_PROP_CREATE_FOLDERS: + g_value_set_boolean (value, priv->create_folders); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/* This cancels everything that may be going on in the background. */ +static void +cancel_all_operations (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GSList *l; + + pending_select_files_free (impl); + + if (priv->reload_icon_cancellables) + { + for (l = priv->reload_icon_cancellables; l; l = l->next) + { + GCancellable *cancellable = G_CANCELLABLE (l->data); + g_cancellable_cancel (cancellable); + } + g_slist_free (priv->reload_icon_cancellables); + priv->reload_icon_cancellables = NULL; + } + + if (priv->loading_shortcuts) + { + for (l = priv->loading_shortcuts; l; l = l->next) + { + GCancellable *cancellable = G_CANCELLABLE (l->data); + g_cancellable_cancel (cancellable); + } + g_slist_free (priv->loading_shortcuts); + priv->loading_shortcuts = NULL; + } + + if (priv->file_list_drag_data_received_cancellable) + { + g_cancellable_cancel (priv->file_list_drag_data_received_cancellable); + priv->file_list_drag_data_received_cancellable = NULL; + } + + if (priv->update_current_folder_cancellable) + { + g_cancellable_cancel (priv->update_current_folder_cancellable); + priv->update_current_folder_cancellable = NULL; + } + + if (priv->should_respond_get_info_cancellable) + { + g_cancellable_cancel (priv->should_respond_get_info_cancellable); + priv->should_respond_get_info_cancellable = NULL; + } + + if (priv->file_exists_get_info_cancellable) + { + g_cancellable_cancel (priv->file_exists_get_info_cancellable); + priv->file_exists_get_info_cancellable = NULL; + } + + if (priv->update_from_entry_cancellable) + { + g_cancellable_cancel (priv->update_from_entry_cancellable); + priv->update_from_entry_cancellable = NULL; + } + + if (priv->shortcuts_activate_iter_cancellable) + { + g_cancellable_cancel (priv->shortcuts_activate_iter_cancellable); + priv->shortcuts_activate_iter_cancellable = NULL; + } + + search_stop_searching (impl, TRUE); + recent_stop_loading (impl); +} + +/* Removes the settings signal handler. It's safe to call multiple times */ +static void +remove_settings_signal (GtkFileChooserWidget *impl, + GdkScreen *screen) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (priv->settings_signal_id) + { + GtkSettings *settings; + + settings = gtk_settings_get_for_screen (screen); + g_signal_handler_disconnect (settings, + priv->settings_signal_id); + priv->settings_signal_id = 0; + } +} + +static void +gtk_file_chooser_widget_dispose (GObject *object) +{ + GtkFileChooserWidget *impl = (GtkFileChooserWidget *) object; + GtkFileChooserWidgetPrivate *priv = impl->priv; + + cancel_all_operations (impl); + + if (priv->extra_widget) + { + g_object_unref (priv->extra_widget); + priv->extra_widget = NULL; + } + + remove_settings_signal (impl, gtk_widget_get_screen (GTK_WIDGET (impl))); + + if (priv->bookmarks_manager) + { + _gtk_bookmarks_manager_free (priv->bookmarks_manager); + priv->bookmarks_manager = NULL; + } + + G_OBJECT_CLASS (gtk_file_chooser_widget_parent_class)->dispose (object); +} + +/* We override show-all since we have internal widgets that + * shouldn't be shown when you call show_all(), like the filter + * combo box. + */ +static void +gtk_file_chooser_widget_show_all (GtkWidget *widget) +{ + GtkFileChooserWidget *impl = (GtkFileChooserWidget *) widget; + GtkFileChooserWidgetPrivate *priv = impl->priv; + + gtk_widget_show (widget); + + if (priv->extra_widget) + gtk_widget_show_all (priv->extra_widget); +} + +/* Handler for GtkWindow::set-focus; this is where we save the last-focused + * widget on our toplevel. See gtk_file_chooser_widget_hierarchy_changed() + */ +static void +toplevel_set_focus_cb (GtkWindow *window, + GtkWidget *focus, + GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + priv->toplevel_last_focus_widget = gtk_window_get_focus (window); +} + +/* We monitor the focus widget on our toplevel to be able to know which widget + * was last focused at the time our "should_respond" method gets called. + */ +static void +gtk_file_chooser_widget_hierarchy_changed (GtkWidget *widget, + GtkWidget *previous_toplevel) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (widget); + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkWidget *toplevel; + + toplevel = gtk_widget_get_toplevel (widget); + + if (previous_toplevel && + priv->toplevel_set_focus_id != 0) + { + g_signal_handler_disconnect (previous_toplevel, + priv->toplevel_set_focus_id); + priv->toplevel_set_focus_id = 0; + priv->toplevel_last_focus_widget = NULL; + } + + if (gtk_widget_is_toplevel (toplevel)) + { + g_assert (priv->toplevel_set_focus_id == 0); + priv->toplevel_set_focus_id = g_signal_connect (toplevel, "set-focus", + G_CALLBACK (toplevel_set_focus_cb), impl); + priv->toplevel_last_focus_widget = gtk_window_get_focus (GTK_WINDOW (toplevel)); + } +} + +/* Changes the icons wherever it is needed */ +static void +change_icon_theme (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + gint width, height; + + profile_start ("start", NULL); + + if (gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, &height)) + priv->icon_size = MAX (width, height); + else + priv->icon_size = FALLBACK_ICON_SIZE; + + /* the first cell in the first column is the icon column, and we have a fixed size there */ + set_icon_cell_renderer_fixed_size (impl); + + if (priv->browse_files_model) + _gtk_file_system_model_clear_cache (priv->browse_files_model, MODEL_COL_SURFACE); + gtk_widget_queue_resize (priv->browse_files_tree_view); + + profile_end ("end", NULL); +} + +/* Callback used when a GtkSettings value changes */ +static void +settings_notify_cb (GObject *object, + GParamSpec *pspec, + GtkFileChooserWidget *impl) +{ + const char *name; + + profile_start ("start", NULL); + + name = g_param_spec_get_name (pspec); + + if (strcmp (name, "gtk-icon-theme-name") == 0) + change_icon_theme (impl); + + profile_end ("end", NULL); +} + +/* Installs a signal handler for GtkSettings so that we can monitor changes in + * the icon theme. + */ +static void +check_icon_theme (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkSettings *settings; + + profile_start ("start", NULL); + + if (priv->settings_signal_id) + { + profile_end ("end", NULL); + return; + } + + if (gtk_widget_has_screen (GTK_WIDGET (impl))) + { + settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (impl))); + priv->settings_signal_id = g_signal_connect (settings, "notify", + G_CALLBACK (settings_notify_cb), impl); + + change_icon_theme (impl); + } + + profile_end ("end", NULL); +} + +static void +gtk_file_chooser_widget_style_updated (GtkWidget *widget) +{ + GtkFileChooserWidget *impl; + + profile_start ("start", NULL); + + impl = GTK_FILE_CHOOSER_WIDGET (widget); + + profile_msg (" parent class style_udpated start", NULL); + GTK_WIDGET_CLASS (gtk_file_chooser_widget_parent_class)->style_updated (widget); + profile_msg (" parent class style_updated end", NULL); + + if (gtk_widget_has_screen (GTK_WIDGET (impl))) + change_icon_theme (impl); + + emit_default_size_changed (impl); + + profile_end ("end", NULL); +} + +static void +gtk_file_chooser_widget_screen_changed (GtkWidget *widget, + GdkScreen *previous_screen) +{ + GtkFileChooserWidget *impl; + + profile_start ("start", NULL); + + impl = GTK_FILE_CHOOSER_WIDGET (widget); + + if (GTK_WIDGET_CLASS (gtk_file_chooser_widget_parent_class)->screen_changed) + GTK_WIDGET_CLASS (gtk_file_chooser_widget_parent_class)->screen_changed (widget, previous_screen); + + remove_settings_signal (impl, previous_screen); + check_icon_theme (impl); + + emit_default_size_changed (impl); + + profile_end ("end", NULL); +} + +static void +set_sort_column (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkTreeSortable *sortable; + + sortable = GTK_TREE_SORTABLE (gtk_tree_view_get_model (GTK_TREE_VIEW (priv->browse_files_tree_view))); + + /* can happen when we're still populating the model */ + if (sortable == NULL) + return; + + gtk_tree_sortable_set_sort_column_id (sortable, + priv->sort_column, + priv->sort_order); +} + +static void +settings_load (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + LocationMode location_mode; + gboolean show_hidden; + gboolean show_size_column; + gboolean sort_directories_first; + gint sort_column; + GtkSortType sort_order; + StartupMode startup_mode; + gint sidebar_width; + GSettings *settings; + + settings = _gtk_file_chooser_get_settings_for_widget (GTK_WIDGET (impl)); + + location_mode = g_settings_get_enum (settings, SETTINGS_KEY_LOCATION_MODE); + show_hidden = g_settings_get_boolean (settings, SETTINGS_KEY_SHOW_HIDDEN); + show_size_column = g_settings_get_boolean (settings, SETTINGS_KEY_SHOW_SIZE_COLUMN); + sort_column = g_settings_get_enum (settings, SETTINGS_KEY_SORT_COLUMN); + sort_order = g_settings_get_enum (settings, SETTINGS_KEY_SORT_ORDER); + sidebar_width = g_settings_get_int (settings, SETTINGS_KEY_SIDEBAR_WIDTH); + startup_mode = g_settings_get_enum (settings, SETTINGS_KEY_STARTUP_MODE); + sort_directories_first = g_settings_get_boolean (settings, SETTINGS_KEY_SORT_DIRECTORIES_FIRST); + + location_mode_set (impl, location_mode, TRUE); + + gtk_file_chooser_set_show_hidden (GTK_FILE_CHOOSER (impl), show_hidden); + + priv->show_size_column = show_size_column; + gtk_tree_view_column_set_visible (priv->list_size_column, show_size_column); + + priv->sort_column = sort_column; + priv->sort_order = sort_order; + priv->startup_mode = startup_mode; + priv->sort_directories_first = sort_directories_first; + + /* We don't call set_sort_column() here as the models may not have been + * created yet. The individual functions that create and set the models will + * call set_sort_column() themselves. + */ + + gtk_paned_set_position (GTK_PANED (priv->browse_widgets_hpaned), sidebar_width); +} + +static void +settings_save (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GSettings *settings; + + settings = _gtk_file_chooser_get_settings_for_widget (GTK_WIDGET (impl)); + + /* All the other state */ + + g_settings_set_enum (settings, SETTINGS_KEY_LOCATION_MODE, priv->location_mode); + g_settings_set_boolean (settings, SETTINGS_KEY_SHOW_HIDDEN, + gtk_file_chooser_get_show_hidden (GTK_FILE_CHOOSER (impl))); + g_settings_set_boolean (settings, SETTINGS_KEY_SHOW_SIZE_COLUMN, priv->show_size_column); + g_settings_set_enum (settings, SETTINGS_KEY_SORT_COLUMN, priv->sort_column); + g_settings_set_enum (settings, SETTINGS_KEY_SORT_ORDER, priv->sort_order); + g_settings_set_int (settings, SETTINGS_KEY_SIDEBAR_WIDTH, + gtk_paned_get_position (GTK_PANED (priv->browse_widgets_hpaned))); + + /* Now apply the settings */ + g_settings_apply (settings); +} + +/* GtkWidget::realize method */ +static void +gtk_file_chooser_widget_realize (GtkWidget *widget) +{ + GtkFileChooserWidget *impl; + + impl = GTK_FILE_CHOOSER_WIDGET (widget); + + GTK_WIDGET_CLASS (gtk_file_chooser_widget_parent_class)->realize (widget); + + emit_default_size_changed (impl); +} + +/* Changes the current folder to $CWD */ +static void +switch_to_cwd (GtkFileChooserWidget *impl) +{ + char *current_working_dir; + + current_working_dir = g_get_current_dir (); + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (impl), current_working_dir); + g_free (current_working_dir); +} + +/* Sets the file chooser to showing Recent Files or $CWD, depending on the + * user's settings. + */ +static void +set_startup_mode (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + switch (priv->startup_mode) + { + case STARTUP_MODE_RECENT: + operation_mode_set (impl, OPERATION_MODE_RECENT); + break; + + case STARTUP_MODE_CWD: + switch_to_cwd (impl); + break; + + default: + g_assert_not_reached (); + } +} + +static gboolean +shortcut_exists (GtkFileChooserWidget *impl, GFile *needle) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GSList *haystack; + GSList *l; + gboolean exists; + + exists = FALSE; + + haystack = gtk_places_sidebar_list_shortcuts (GTK_PLACES_SIDEBAR (priv->places_sidebar)); + for (l = haystack; l; l = l->next) + { + GFile *hay; + + hay = G_FILE (l->data); + if (g_file_equal (hay, needle)) + { + exists = TRUE; + break; + } + } + g_slist_free_full (haystack, g_object_unref); + + return exists; +} + +static void +add_cwd_to_sidebar_if_needed (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + char *cwd; + GFile *cwd_file; + GFile *home_file; + + cwd = g_get_current_dir (); + cwd_file = g_file_new_for_path (cwd); + g_free (cwd); + + if (shortcut_exists (impl, cwd_file)) + goto out; + + home_file = g_file_new_for_path (g_get_home_dir ()); + + /* We only add an item for $CWD if it is different from $HOME. This way, + * applications which get launched from a shell in a terminal (by someone who + * knows what they are doing) will get an item for $CWD in the places sidebar, + * and "normal" applications launched from the desktop shell (whose $CWD is + * $HOME) won't get any extra clutter in the sidebar. + */ + if (!g_file_equal (home_file, cwd_file)) + gtk_places_sidebar_add_shortcut (GTK_PLACES_SIDEBAR (priv->places_sidebar), cwd_file); + + g_object_unref (home_file); + + out: + g_object_unref (cwd_file); +} + +/* GtkWidget::map method */ +static void +gtk_file_chooser_widget_map (GtkWidget *widget) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (widget); + GtkFileChooserWidgetPrivate *priv = impl->priv; + + profile_start ("start", NULL); + + GTK_WIDGET_CLASS (gtk_file_chooser_widget_parent_class)->map (widget); + + settings_load (impl); + + add_cwd_to_sidebar_if_needed (impl); + + if (priv->operation_mode == OPERATION_MODE_BROWSE) + { + switch (priv->reload_state) + { + case RELOAD_EMPTY: + set_startup_mode (impl); + break; + + case RELOAD_HAS_FOLDER: + /* Nothing; we are already loading or loaded, so we + * don't need to reload + */ + break; + + default: + g_assert_not_reached (); + } + } + + profile_end ("end", NULL); +} + +/* GtkWidget::unmap method */ +static void +gtk_file_chooser_widget_unmap (GtkWidget *widget) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (widget); + GtkFileChooserWidgetPrivate *priv = impl->priv; + + settings_save (impl); + + cancel_all_operations (impl); + priv->reload_state = RELOAD_EMPTY; + + GTK_WIDGET_CLASS (gtk_file_chooser_widget_parent_class)->unmap (widget); +} + +#define COMPARE_DIRECTORIES \ + GtkFileChooserWidget *impl = user_data; \ + GtkFileChooserWidgetPrivate *priv = impl->priv; \ + GtkFileSystemModel *fs_model = GTK_FILE_SYSTEM_MODEL (model); \ + gboolean dir_a, dir_b; \ + \ + dir_a = g_value_get_boolean (_gtk_file_system_model_get_value (fs_model, a, MODEL_COL_IS_FOLDER)); \ + dir_b = g_value_get_boolean (_gtk_file_system_model_get_value (fs_model, b, MODEL_COL_IS_FOLDER)); \ + \ + if (priv->sort_directories_first && dir_a != dir_b) \ + return priv->list_sort_ascending ? (dir_a ? -1 : 1) : (dir_a ? 1 : -1) /* Directories *always* go first */ + +/* Sort callback for the filename column */ +static gint +name_sort_func (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gpointer user_data) +{ + COMPARE_DIRECTORIES; + else + { + const char *key_a, *key_b; + gint result; + + key_a = g_value_get_string (_gtk_file_system_model_get_value (fs_model, a, MODEL_COL_NAME_COLLATED)); + key_b = g_value_get_string (_gtk_file_system_model_get_value (fs_model, b, MODEL_COL_NAME_COLLATED)); + + if (key_a && key_b) + result = strcmp (key_a, key_b); + else if (key_a) + result = 1; + else if (key_b) + result = -1; + else + result = 0; + + return result; + } +} + +/* Sort callback for the size column */ +static gint +size_sort_func (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gpointer user_data) +{ + COMPARE_DIRECTORIES; + else + { + gint64 size_a, size_b; + + size_a = g_value_get_int64 (_gtk_file_system_model_get_value (fs_model, a, MODEL_COL_SIZE)); + size_b = g_value_get_int64 (_gtk_file_system_model_get_value (fs_model, b, MODEL_COL_SIZE)); + + return size_a < size_b ? -1 : (size_a == size_b ? 0 : 1); + } +} + +/* Sort callback for the mtime column */ +static gint +mtime_sort_func (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gpointer user_data) +{ + COMPARE_DIRECTORIES; + else + { + glong ta, tb; + + ta = g_value_get_long (_gtk_file_system_model_get_value (fs_model, a, MODEL_COL_MTIME)); + tb = g_value_get_long (_gtk_file_system_model_get_value (fs_model, b, MODEL_COL_MTIME)); + + return ta < tb ? -1 : (ta == tb ? 0 : 1); + } +} + +/* Callback used when the sort column changes. We cache the sort order for use + * in name_sort_func(). + */ +static void +list_sort_column_changed_cb (GtkTreeSortable *sortable, + GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + gint sort_column_id; + GtkSortType sort_type; + + if (gtk_tree_sortable_get_sort_column_id (sortable, &sort_column_id, &sort_type)) + { + priv->list_sort_ascending = (sort_type == GTK_SORT_ASCENDING); + priv->sort_column = sort_column_id; + priv->sort_order = sort_type; + } +} + +static void +set_busy_cursor (GtkFileChooserWidget *impl, + gboolean busy) +{ + GtkWidget *widget; + GtkWindow *toplevel; + GdkDisplay *display; + GdkCursor *cursor; + + toplevel = get_toplevel (GTK_WIDGET (impl)); + widget = GTK_WIDGET (toplevel); + if (!toplevel || !gtk_widget_get_realized (widget)) + return; + + display = gtk_widget_get_display (widget); + + if (busy) + cursor = gdk_cursor_new_for_display (display, GDK_WATCH); + else + cursor = NULL; + + gdk_window_set_cursor (gtk_widget_get_window (widget), cursor); + gdk_display_flush (display); + + if (cursor) + g_object_unref (cursor); +} + +/* Creates a sort model to wrap the file system model and sets it on the tree view */ +static void +load_set_model (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + profile_start ("start", NULL); + + g_assert (priv->browse_files_model != NULL); + + profile_msg (" gtk_tree_view_set_model start", NULL); + gtk_tree_view_set_model (GTK_TREE_VIEW (priv->browse_files_tree_view), + GTK_TREE_MODEL (priv->browse_files_model)); + gtk_tree_view_columns_autosize (GTK_TREE_VIEW (priv->browse_files_tree_view)); + gtk_tree_view_set_search_column (GTK_TREE_VIEW (priv->browse_files_tree_view), + MODEL_COL_NAME); + file_list_set_sort_column_ids (impl); + set_sort_column (impl); + profile_msg (" gtk_tree_view_set_model end", NULL); + priv->list_sort_ascending = TRUE; + + profile_end ("end", NULL); +} + +/* Timeout callback used when the loading timer expires */ +static gboolean +load_timeout_cb (gpointer data) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (data); + GtkFileChooserWidgetPrivate *priv = impl->priv; + + profile_start ("start", NULL); + + g_assert (priv->load_state == LOAD_PRELOAD); + g_assert (priv->load_timeout_id != 0); + g_assert (priv->browse_files_model != NULL); + + priv->load_timeout_id = 0; + priv->load_state = LOAD_LOADING; + + load_set_model (impl); + + profile_end ("end", NULL); + + return FALSE; +} + +/* Sets up a new load timer for the model and switches to the LOAD_PRELOAD state */ +static void +load_setup_timer (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + g_assert (priv->load_timeout_id == 0); + g_assert (priv->load_state != LOAD_PRELOAD); + + priv->load_timeout_id = gdk_threads_add_timeout (MAX_LOADING_TIME, load_timeout_cb, impl); + g_source_set_name_by_id (priv->load_timeout_id, "[gtk+] load_timeout_cb"); + priv->load_state = LOAD_PRELOAD; +} + +/* Removes the load timeout; changes the impl->load_state to the specified value. */ +static void +load_remove_timer (GtkFileChooserWidget *impl, LoadState new_load_state) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (priv->load_timeout_id != 0) + { + g_assert (priv->load_state == LOAD_PRELOAD); + + g_source_remove (priv->load_timeout_id); + priv->load_timeout_id = 0; + } + else + g_assert (priv->load_state == LOAD_EMPTY || + priv->load_state == LOAD_LOADING || + priv->load_state == LOAD_FINISHED); + + g_assert (new_load_state == LOAD_EMPTY || + new_load_state == LOAD_LOADING || + new_load_state == LOAD_FINISHED); + priv->load_state = new_load_state; +} + +/* Selects the first row in the file list */ +static void +browse_files_select_first_row (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkTreePath *path; + GtkTreeIter dummy_iter; + GtkTreeModel *tree_model; + + tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->browse_files_tree_view)); + + if (!tree_model) + return; + + path = gtk_tree_path_new_from_indices (0, -1); + + /* If the list is empty, do nothing. */ + if (gtk_tree_model_get_iter (tree_model, &dummy_iter, path)) + { + /* Although the following call to gtk_tree_view_set_cursor() is intended to + * only change the focus to the first row (not select it), GtkTreeView *will* + * select the row anyway due to bug #492206. So, we'll use a flag to + * keep our own callbacks from changing the location_entry when the selection + * is changed. This entire function, browse_files_select_first_row(), may + * go away when that bug is fixed in GtkTreeView. + */ + priv->auto_selecting_first_row = TRUE; + + gtk_tree_view_set_cursor (GTK_TREE_VIEW (priv->browse_files_tree_view), path, NULL, FALSE); + + priv->auto_selecting_first_row = FALSE; + } + gtk_tree_path_free (path); +} + +struct center_selected_row_closure { + GtkFileChooserWidget *impl; + gboolean already_centered; +}; + +/* Callback used from gtk_tree_selection_selected_foreach(); centers the + * selected row in the tree view. + */ +static void +center_selected_row_foreach_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + struct center_selected_row_closure *closure; + + closure = data; + if (closure->already_centered) + return; + + gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (closure->impl->priv->browse_files_tree_view), path, NULL, TRUE, 0.5, 0.0); + closure->already_centered = TRUE; +} + +/* Centers the selected row in the tree view */ +static void +browse_files_center_selected_row (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + struct center_selected_row_closure closure; + GtkTreeSelection *selection; + + closure.impl = impl; + closure.already_centered = FALSE; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); + gtk_tree_selection_selected_foreach (selection, center_selected_row_foreach_cb, &closure); +} + +static gboolean +show_and_select_files (GtkFileChooserWidget *impl, + GSList *files) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkTreeSelection *selection; + GtkFileSystemModel *fsmodel; + gboolean enabled_hidden, removed_filters; + gboolean selected_a_file; + GSList *walk; + + g_assert (priv->load_state == LOAD_FINISHED); + g_assert (priv->browse_files_model != NULL); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); + fsmodel = GTK_FILE_SYSTEM_MODEL (gtk_tree_view_get_model (GTK_TREE_VIEW (priv->browse_files_tree_view))); + + g_assert (fsmodel == priv->browse_files_model); + + enabled_hidden = priv->show_hidden; + removed_filters = (priv->current_filter == NULL); + + selected_a_file = FALSE; + + for (walk = files; walk; walk = walk->next) + { + GFile *file = walk->data; + GtkTreeIter iter; + + /* Is it a hidden file? */ + + if (!_gtk_file_system_model_get_iter_for_file (fsmodel, &iter, file)) + continue; + + if (!_gtk_file_system_model_iter_is_visible (fsmodel, &iter)) + { + GFileInfo *info = _gtk_file_system_model_get_info (fsmodel, &iter); + + if (!enabled_hidden && + (g_file_info_get_is_hidden (info) || + g_file_info_get_is_backup (info))) + { + g_object_set (impl, "show-hidden", TRUE, NULL); + enabled_hidden = TRUE; + } + } + + /* Is it a filtered file? */ + + if (!_gtk_file_system_model_get_iter_for_file (fsmodel, &iter, file)) + continue; /* re-get the iter as it may change when the model refilters */ + + if (!_gtk_file_system_model_iter_is_visible (fsmodel, &iter)) + { + /* Maybe we should have a way to ask the fsmodel if it had filtered a file */ + if (!removed_filters) + { + set_current_filter (impl, NULL); + removed_filters = TRUE; + } + } + + /* Okay, can we select the file now? */ + + if (!_gtk_file_system_model_get_iter_for_file (fsmodel, &iter, file)) + continue; + + if (_gtk_file_system_model_iter_is_visible (fsmodel, &iter)) + { + GtkTreePath *path; + + gtk_tree_selection_select_iter (selection, &iter); + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (fsmodel), &iter); + gtk_tree_view_set_cursor (GTK_TREE_VIEW (priv->browse_files_tree_view), + path, NULL, FALSE); + gtk_tree_path_free (path); + + selected_a_file = TRUE; + } + } + + browse_files_center_selected_row (impl); + + return selected_a_file; +} + +/* Processes the pending operation when a folder is finished loading */ +static void +pending_select_files_process (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + g_assert (priv->load_state == LOAD_FINISHED); + g_assert (priv->browse_files_model != NULL); + + if (priv->pending_select_files) + { + show_and_select_files (impl, priv->pending_select_files); + pending_select_files_free (impl); + browse_files_center_selected_row (impl); + } + else + { + /* We only select the first row if the chooser is actually mapped --- + * selecting the first row is to help the user when he is interacting with + * the chooser, but sometimes a chooser works not on behalf of the user, + * but rather on behalf of something else like GtkFileChooserButton. In + * that case, the chooser's selection should be what the caller expects, + * as the user can't see that something else got selected. See bug #165264. + */ + if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN && + gtk_widget_get_mapped (GTK_WIDGET (impl))) + browse_files_select_first_row (impl); + } + + g_assert (priv->pending_select_files == NULL); +} + +static void +show_error_on_reading_current_folder (GtkFileChooserWidget *impl, GError *error) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GFileInfo *info; + char *msg; + + info = g_file_query_info (priv->current_folder, + G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, + G_FILE_QUERY_INFO_NONE, + NULL, + NULL); + if (info) + { + msg = g_strdup_printf (_("Could not read the contents of %s"), g_file_info_get_display_name (info)); + g_object_unref (info); + } + else + msg = g_strdup (_("Could not read the contents of the folder")); + + error_message (impl, msg, error->message); + g_free (msg); +} + +/* Callback used when the file system model finishes loading */ +static void +browse_files_model_finished_loading_cb (GtkFileSystemModel *model, + GError *error, + GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + profile_start ("start", NULL); + + if (error) + show_error_on_reading_current_folder (impl, error); + + if (priv->load_state == LOAD_PRELOAD) + { + load_remove_timer (impl, LOAD_FINISHED); + load_set_model (impl); + } + else if (priv->load_state == LOAD_LOADING) + { + /* Nothing */ + } + else + { + /* We can't g_assert_not_reached(), as something other than us may have + * initiated a folder reload. See #165556. + */ + profile_end ("end", NULL); + return; + } + + g_assert (priv->load_timeout_id == 0); + + priv->load_state = LOAD_FINISHED; + + pending_select_files_process (impl); + set_busy_cursor (impl, FALSE); +#ifdef PROFILE_FILE_CHOOSER + access ("MARK: *** FINISHED LOADING", F_OK); +#endif + + profile_end ("end", NULL); +} + +static void +stop_loading_and_clear_list_model (GtkFileChooserWidget *impl, + gboolean remove_from_treeview) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + load_remove_timer (impl, LOAD_EMPTY); + + if (priv->browse_files_model) + { + g_object_unref (priv->browse_files_model); + priv->browse_files_model = NULL; + } + + if (remove_from_treeview) + gtk_tree_view_set_model (GTK_TREE_VIEW (priv->browse_files_tree_view), NULL); +} + +static char * +my_g_format_time_for_display (GtkFileChooserWidget *impl, + glong secs) +{ + GDateTime *now, *time; + GTimeSpan time_diff; + gchar *clock_format; + gboolean use_24 = TRUE; + const gchar *format; + gchar *date_str; + GSettings *settings; + + now = g_date_time_new_now_local (); + time = g_date_time_new_from_unix_local (secs); + time_diff = g_date_time_difference (now, time); + + settings = _gtk_file_chooser_get_settings_for_widget (GTK_WIDGET (impl)); + clock_format = g_settings_get_string (settings, "clock-format"); + use_24 = g_strcmp0 (clock_format, "24h") == 0; + g_free (clock_format); + + /* Translators: see g_date_time_format() for details on the format */ + if (time_diff >= 0 && time_diff < G_TIME_SPAN_DAY) + format = use_24 ? _("%H:%M") : _("%-I:%M %P"); + else if (time_diff >= 0 && time_diff < 2 * G_TIME_SPAN_DAY) + format = use_24 ? _("Yesterday at %H:%M") : _("Yesterday at %-I:%M %P"); + else if (time_diff >= 0 && time_diff < 7 * G_TIME_SPAN_DAY) + format = "%A"; /* Days from last week */ + else + format = "%x"; /* Any other date */ + + date_str = g_date_time_format (time, format); + + g_date_time_unref (time); + g_date_time_unref (now); + + return date_str; +} + +static void +copy_attribute (GFileInfo *to, GFileInfo *from, const char *attribute) +{ + GFileAttributeType type; + gpointer value; + + if (g_file_info_get_attribute_data (from, attribute, &type, &value, NULL)) + g_file_info_set_attribute (to, attribute, type, value); +} + +static void +file_system_model_got_thumbnail (GObject *object, GAsyncResult *res, gpointer data) +{ + GtkFileSystemModel *model = data; /* might be unreffed if operation was cancelled */ + GFile *file = G_FILE (object); + GFileInfo *queried, *info; + GtkTreeIter iter; + + queried = g_file_query_info_finish (file, res, NULL); + if (queried == NULL) + return; + + gdk_threads_enter (); + + /* now we know model is valid */ + + /* file was deleted */ + if (!_gtk_file_system_model_get_iter_for_file (model, &iter, file)) + { + gdk_threads_leave (); + return; + } + + info = g_file_info_dup (_gtk_file_system_model_get_info (model, &iter)); + + copy_attribute (info, queried, G_FILE_ATTRIBUTE_THUMBNAIL_PATH); + copy_attribute (info, queried, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED); + copy_attribute (info, queried, G_FILE_ATTRIBUTE_STANDARD_ICON); + + _gtk_file_system_model_update_file (model, file, info); + + g_object_unref (info); + + gdk_threads_leave (); +} + +static gboolean +file_system_model_set (GtkFileSystemModel *model, + GFile *file, + GFileInfo *info, + int column, + GValue *value, + gpointer data) +{ + GtkFileChooserWidget *impl = data; + GtkFileChooserWidgetPrivate *priv = impl->priv; + + switch (column) + { + case MODEL_COL_FILE: + g_value_set_object (value, file); + break; + case MODEL_COL_NAME: + if (info == NULL) + g_value_set_string (value, DEFAULT_NEW_FOLDER_NAME); + else + g_value_set_string (value, g_file_info_get_display_name (info)); + break; + case MODEL_COL_NAME_COLLATED: + if (info == NULL) + g_value_take_string (value, g_utf8_collate_key_for_filename (DEFAULT_NEW_FOLDER_NAME, -1)); + else + g_value_take_string (value, g_utf8_collate_key_for_filename (g_file_info_get_display_name (info), -1)); + break; + case MODEL_COL_IS_FOLDER: + g_value_set_boolean (value, info == NULL || _gtk_file_info_consider_as_directory (info)); + break; + case MODEL_COL_IS_SENSITIVE: + if (info) + { + gboolean sensitive = TRUE; + + if (!(priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + || priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)) + { + sensitive = TRUE; /* for file modes... */ + } + else if (!_gtk_file_info_consider_as_directory (info)) + { + sensitive = FALSE; /* for folder modes, files are not sensitive... */ + } + else + { + /* ... and for folder modes, folders are sensitive only if the filter says so */ + GtkTreeIter iter; + if (!_gtk_file_system_model_get_iter_for_file (model, &iter, file)) + g_assert_not_reached (); + sensitive = !_gtk_file_system_model_iter_is_filtered_out (model, &iter); + } + + g_value_set_boolean (value, sensitive); + } + else + g_value_set_boolean (value, TRUE); + break; + case MODEL_COL_SURFACE: + if (info) + { + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_ICON)) + { + g_value_take_boxed (value, _gtk_file_info_render_icon (info, GTK_WIDGET (impl), priv->icon_size)); + } + else + { + GtkTreeModel *tree_model; + GtkTreePath *path, *start, *end; + GtkTreeIter iter; + + if (priv->browse_files_tree_view == NULL || + g_file_info_has_attribute (info, "filechooser::queried")) + return FALSE; + + tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->browse_files_tree_view)); + if (tree_model != GTK_TREE_MODEL (model)) + return FALSE; + + if (!_gtk_file_system_model_get_iter_for_file (model, + &iter, + file)) + g_assert_not_reached (); + if (!gtk_tree_view_get_visible_range (GTK_TREE_VIEW (priv->browse_files_tree_view), &start, &end)) + return FALSE; + path = gtk_tree_model_get_path (tree_model, &iter); + if (gtk_tree_path_compare (start, path) != 1 && + gtk_tree_path_compare (path, end) != 1) + { + g_file_info_set_attribute_boolean (info, "filechooser::queried", TRUE); + g_file_query_info_async (file, + G_FILE_ATTRIBUTE_THUMBNAIL_PATH "," + G_FILE_ATTRIBUTE_THUMBNAILING_FAILED "," + G_FILE_ATTRIBUTE_STANDARD_ICON, + G_FILE_QUERY_INFO_NONE, + G_PRIORITY_DEFAULT, + _gtk_file_system_model_get_cancellable (model), + file_system_model_got_thumbnail, + model); + } + gtk_tree_path_free (path); + gtk_tree_path_free (start); + gtk_tree_path_free (end); + return FALSE; + } + } + else + g_value_set_object (value, NULL); + break; + case MODEL_COL_SIZE: + g_value_set_int64 (value, info ? g_file_info_get_size (info) : 0); + break; + case MODEL_COL_SIZE_TEXT: + if (info == NULL || _gtk_file_info_consider_as_directory (info)) + g_value_set_string (value, NULL); + else + g_value_take_string (value, g_format_size (g_file_info_get_size (info))); + break; + case MODEL_COL_MTIME: + case MODEL_COL_MTIME_TEXT: + { + GTimeVal tv; + if (info == NULL) + break; + g_file_info_get_modification_time (info, &tv); + if (column == MODEL_COL_MTIME) + g_value_set_long (value, tv.tv_sec); + else if (tv.tv_sec == 0) + g_value_set_static_string (value, _("Unknown")); + else + g_value_take_string (value, my_g_format_time_for_display (impl, tv.tv_sec)); + break; + } + case MODEL_COL_ELLIPSIZE: + g_value_set_enum (value, info ? PANGO_ELLIPSIZE_END : PANGO_ELLIPSIZE_NONE); + break; + default: + g_assert_not_reached (); + break; + } + + return TRUE; +} + +/* Gets rid of the old list model and creates a new one for the current folder */ +static gboolean +set_list_model (GtkFileChooserWidget *impl, + GError **error) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + g_assert (priv->current_folder != NULL); + + profile_start ("start", NULL); + + stop_loading_and_clear_list_model (impl, TRUE); + + set_busy_cursor (impl, TRUE); + + priv->browse_files_model = + _gtk_file_system_model_new_for_directory (priv->current_folder, + MODEL_ATTRIBUTES, + file_system_model_set, + impl, + MODEL_COLUMN_TYPES); + + _gtk_file_system_model_set_show_hidden (priv->browse_files_model, priv->show_hidden); + + profile_msg (" set sort function", NULL); + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->browse_files_model), MODEL_COL_NAME, name_sort_func, impl, NULL); + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->browse_files_model), MODEL_COL_SIZE, size_sort_func, impl, NULL); + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->browse_files_model), MODEL_COL_MTIME, mtime_sort_func, impl, NULL); + gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (priv->browse_files_model), NULL, NULL, NULL); + set_sort_column (impl); + priv->list_sort_ascending = TRUE; + g_signal_connect (priv->browse_files_model, "sort-column-changed", + G_CALLBACK (list_sort_column_changed_cb), impl); + + load_setup_timer (impl); /* This changes the state to LOAD_PRELOAD */ + + g_signal_connect (priv->browse_files_model, "finished-loading", + G_CALLBACK (browse_files_model_finished_loading_cb), impl); + + _gtk_file_system_model_set_filter (priv->browse_files_model, priv->current_filter); + + profile_end ("end", NULL); + + return TRUE; +} + +struct update_chooser_entry_selected_foreach_closure { + int num_selected; + GtkTreeIter first_selected_iter; +}; + +static gint +compare_utf8_filenames (const gchar *a, + const gchar *b) +{ + gchar *a_folded, *b_folded; + gint retval; + + a_folded = g_utf8_strdown (a, -1); + b_folded = g_utf8_strdown (b, -1); + + retval = strcmp (a_folded, b_folded); + + g_free (a_folded); + g_free (b_folded); + + return retval; +} + +static void +update_chooser_entry_selected_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + struct update_chooser_entry_selected_foreach_closure *closure; + + closure = data; + closure->num_selected++; + + if (closure->num_selected == 1) + closure->first_selected_iter = *iter; +} + +static void +update_chooser_entry (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkTreeSelection *selection; + struct update_chooser_entry_selected_foreach_closure closure; + + /* no need to update the file chooser's entry if there's no entry */ + if (priv->operation_mode == OPERATION_MODE_SEARCH || + !priv->location_entry) + return; + + if (!(priv->action == GTK_FILE_CHOOSER_ACTION_SAVE + || priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER + || ((priv->action == GTK_FILE_CHOOSER_ACTION_OPEN + || priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) + && priv->location_mode == LOCATION_MODE_FILENAME_ENTRY))) + return; + + g_assert (priv->location_entry != NULL); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); + closure.num_selected = 0; + gtk_tree_selection_selected_foreach (selection, update_chooser_entry_selected_foreach, &closure); + + if (closure.num_selected == 0) + { + if (priv->operation_mode == OPERATION_MODE_RECENT) + _gtk_file_chooser_entry_set_base_folder (GTK_FILE_CHOOSER_ENTRY (priv->location_entry), NULL); + else + goto maybe_clear_entry; + } + else if (closure.num_selected == 1) + { + if (priv->operation_mode == OPERATION_MODE_BROWSE) + { + GFileInfo *info; + gboolean change_entry; + + info = _gtk_file_system_model_get_info (priv->browse_files_model, &closure.first_selected_iter); + + /* If the cursor moved to the row of the newly created folder, + * retrieving info will return NULL. + */ + if (!info) + return; + + g_free (priv->browse_files_last_selected_name); + priv->browse_files_last_selected_name = + g_strdup (g_file_info_get_display_name (info)); + + if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN || + priv->action == GTK_FILE_CHOOSER_ACTION_SAVE || + priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) + { + /* Don't change the name when clicking on a folder... */ + change_entry = (! _gtk_file_info_consider_as_directory (info)); + } + else + change_entry = TRUE; /* ... unless we are in SELECT_FOLDER mode */ + + if (change_entry && !priv->auto_selecting_first_row) + { + gtk_entry_set_text (GTK_ENTRY (priv->location_entry), priv->browse_files_last_selected_name); + + if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) + _gtk_file_chooser_entry_select_filename (GTK_FILE_CHOOSER_ENTRY (priv->location_entry)); + } + + return; + } + else if (priv->operation_mode == OPERATION_MODE_RECENT + && priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) + { + GFile *folder; + + /* Set the base folder on the name entry, so it will do completion relative to the correct recent-folder */ + + gtk_tree_model_get (GTK_TREE_MODEL (priv->recent_model), &closure.first_selected_iter, + MODEL_COL_FILE, &folder, + -1); + _gtk_file_chooser_entry_set_base_folder (GTK_FILE_CHOOSER_ENTRY (priv->location_entry), folder); + g_object_unref (folder); + return; + } + } + else + { + g_assert (!(priv->action == GTK_FILE_CHOOSER_ACTION_SAVE || + priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)); + + /* Multiple selection, so just clear the entry. */ + g_free (priv->browse_files_last_selected_name); + priv->browse_files_last_selected_name = NULL; + + gtk_entry_set_text (GTK_ENTRY (priv->location_entry), ""); + return; + } + + maybe_clear_entry: + + if ((priv->action == GTK_FILE_CHOOSER_ACTION_OPEN || priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) + && priv->browse_files_last_selected_name) + { + const char *entry_text; + int len; + gboolean clear_entry; + + entry_text = gtk_entry_get_text (GTK_ENTRY (priv->location_entry)); + len = strlen (entry_text); + if (len != 0) + { + /* The file chooser entry may have appended a "/" to its text. + * So take it out, and compare the result to the old selection. + */ + if (entry_text[len - 1] == G_DIR_SEPARATOR) + { + gchar *tmp; + + tmp = g_strndup (entry_text, len - 1); + clear_entry = (compare_utf8_filenames (priv->browse_files_last_selected_name, tmp) == 0); + g_free (tmp); + } + else + clear_entry = (compare_utf8_filenames (priv->browse_files_last_selected_name, entry_text) == 0); + } + else + clear_entry = FALSE; + + if (clear_entry) + gtk_entry_set_text (GTK_ENTRY (priv->location_entry), ""); + } +} + +static gboolean +gtk_file_chooser_widget_set_current_folder (GtkFileChooser *chooser, + GFile *file, + GError **error) +{ + return gtk_file_chooser_widget_update_current_folder (chooser, file, FALSE, FALSE, error); +} + + +struct UpdateCurrentFolderData +{ + GtkFileChooserWidget *impl; + GFile *file; + gboolean keep_trail; + gboolean clear_entry; + GFile *original_file; + GError *original_error; +}; + +static void +update_current_folder_mount_enclosing_volume_cb (GCancellable *cancellable, + GtkFileSystemVolume *volume, + const GError *error, + gpointer user_data) +{ + struct UpdateCurrentFolderData *data = user_data; + GtkFileChooserWidget *impl = data->impl; + GtkFileChooserWidgetPrivate *priv = impl->priv; + gboolean cancelled = g_cancellable_is_cancelled (cancellable); + + if (cancellable != priv->update_current_folder_cancellable) + goto out; + + priv->update_current_folder_cancellable = NULL; + set_busy_cursor (impl, FALSE); + + if (cancelled) + goto out; + + if (error) + { + error_changing_folder_dialog (data->impl, data->file, g_error_copy (error)); + priv->reload_state = RELOAD_EMPTY; + goto out; + } + + change_folder_and_display_error (impl, data->file, data->clear_entry); + +out: + g_object_unref (data->file); + g_free (data); + + g_object_unref (cancellable); +} + +static void +update_current_folder_get_info_cb (GCancellable *cancellable, + GFileInfo *info, + const GError *error, + gpointer user_data) +{ + gboolean cancelled = g_cancellable_is_cancelled (cancellable); + struct UpdateCurrentFolderData *data = user_data; + GtkFileChooserWidget *impl = data->impl; + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (cancellable != priv->update_current_folder_cancellable) + goto out; + + priv->update_current_folder_cancellable = NULL; + priv->reload_state = RELOAD_EMPTY; + + set_busy_cursor (impl, FALSE); + + if (cancelled) + goto out; + + if (error) + { + GFile *parent_file; + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_MOUNTED)) + { + GMountOperation *mount_operation; + GtkWidget *toplevel; + + g_object_unref (cancellable); + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (impl)); + + mount_operation = gtk_mount_operation_new (GTK_WINDOW (toplevel)); + + set_busy_cursor (impl, TRUE); + + priv->update_current_folder_cancellable = + _gtk_file_system_mount_enclosing_volume (priv->file_system, data->file, + mount_operation, + update_current_folder_mount_enclosing_volume_cb, + data); + + return; + } + + if (!data->original_file) + { + data->original_file = g_object_ref (data->file); + data->original_error = g_error_copy (error); + } + + parent_file = g_file_get_parent (data->file); + + /* get parent path and try to change the folder to that */ + if (parent_file) + { + g_object_unref (data->file); + data->file = parent_file; + + g_object_unref (cancellable); + + /* restart the update current folder operation */ + priv->reload_state = RELOAD_HAS_FOLDER; + + priv->update_current_folder_cancellable = + _gtk_file_system_get_info (priv->file_system, data->file, + "standard::type", + update_current_folder_get_info_cb, + data); + + set_busy_cursor (impl, TRUE); + + return; + } + else + { + /* Error and bail out, ignoring "not found" errors since they're useless: + * they only happen when a program defaults to a folder that has been (re)moved. + */ + if (!g_error_matches (data->original_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + error_changing_folder_dialog (impl, data->original_file, data->original_error); + else + g_error_free (data->original_error); + + g_object_unref (data->original_file); + + goto out; + } + } + + if (data->original_file) + { + /* Error and bail out, ignoring "not found" errors since they're useless: + * they only happen when a program defaults to a folder that has been (re)moved. + */ + if (!g_error_matches (data->original_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + error_changing_folder_dialog (impl, data->original_file, data->original_error); + else + g_error_free (data->original_error); + + g_object_unref (data->original_file); + } + + if (! _gtk_file_info_consider_as_directory (info)) + goto out; + + _gtk_path_bar_set_file (GTK_PATH_BAR (priv->browse_path_bar), data->file, data->keep_trail); + + if (priv->current_folder != data->file) + { + if (priv->current_folder) + g_object_unref (priv->current_folder); + + priv->current_folder = g_object_ref (data->file); + } + + priv->reload_state = RELOAD_HAS_FOLDER; + + /* Set the folder on the save entry */ + + if (priv->location_entry) + { + _gtk_file_chooser_entry_set_base_folder (GTK_FILE_CHOOSER_ENTRY (priv->location_entry), + priv->current_folder); + + if (data->clear_entry) + gtk_entry_set_text (GTK_ENTRY (priv->location_entry), ""); + } + + /* Create a new list model. This is slightly evil; we store the result value + * but perform more actions rather than returning immediately even if it + * generates an error. + */ + set_list_model (impl, NULL); + + /* Refresh controls */ + + gtk_places_sidebar_set_location (GTK_PLACES_SIDEBAR (priv->places_sidebar), priv->current_folder); + + g_signal_emit_by_name (impl, "current-folder-changed", 0); + + check_preview_change (impl); + + g_signal_emit_by_name (impl, "selection-changed", 0); + +out: + g_object_unref (data->file); + g_free (data); + + g_object_unref (cancellable); +} + +static gboolean +gtk_file_chooser_widget_update_current_folder (GtkFileChooser *chooser, + GFile *file, + gboolean keep_trail, + gboolean clear_entry, + GError **error) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser); + GtkFileChooserWidgetPrivate *priv = impl->priv; + struct UpdateCurrentFolderData *data; + + profile_start ("start", NULL); + + g_object_ref (file); + + operation_mode_set (impl, OPERATION_MODE_BROWSE); + + if (priv->local_only && !_gtk_file_has_native_path (file)) + { + g_set_error_literal (error, + GTK_FILE_CHOOSER_ERROR, + GTK_FILE_CHOOSER_ERROR_BAD_FILENAME, + _("Cannot change to folder because it is not local")); + + g_object_unref (file); + profile_end ("end - not local", NULL); + return FALSE; + } + + if (priv->update_current_folder_cancellable) + g_cancellable_cancel (priv->update_current_folder_cancellable); + + /* Test validity of path here. */ + data = g_new0 (struct UpdateCurrentFolderData, 1); + data->impl = impl; + data->file = g_object_ref (file); + data->keep_trail = keep_trail; + data->clear_entry = clear_entry; + + priv->reload_state = RELOAD_HAS_FOLDER; + + priv->update_current_folder_cancellable = + _gtk_file_system_get_info (priv->file_system, file, + "standard::type", + update_current_folder_get_info_cb, + data); + + set_busy_cursor (impl, TRUE); + g_object_unref (file); + + profile_end ("end", NULL); + return TRUE; +} + +static GFile * +gtk_file_chooser_widget_get_current_folder (GtkFileChooser *chooser) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser); + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (priv->operation_mode == OPERATION_MODE_SEARCH || + priv->operation_mode == OPERATION_MODE_RECENT) + return NULL; + + if (priv->current_folder) + return g_object_ref (priv->current_folder); + + return NULL; +} + +static void +gtk_file_chooser_widget_set_current_name (GtkFileChooser *chooser, + const gchar *name) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser); + GtkFileChooserWidgetPrivate *priv = impl->priv; + + g_return_if_fail (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE || + priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER); + + pending_select_files_free (impl); + gtk_entry_set_text (GTK_ENTRY (priv->location_entry), name); +} + +static gchar * +gtk_file_chooser_widget_get_current_name (GtkFileChooser *chooser) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser); + GtkFileChooserWidgetPrivate *priv = impl->priv; + + g_return_val_if_fail (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE || + priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER, + NULL); + + return g_strdup (gtk_entry_get_text (GTK_ENTRY (priv->location_entry))); +} + +static gboolean +gtk_file_chooser_widget_select_file (GtkFileChooser *chooser, + GFile *file, + GError **error) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser); + GtkFileChooserWidgetPrivate *priv = impl->priv; + GFile *parent_file; + gboolean same_path; + + parent_file = g_file_get_parent (file); + + if (!parent_file) + return gtk_file_chooser_set_current_folder_file (chooser, file, error); + + if (priv->operation_mode == OPERATION_MODE_SEARCH || + priv->operation_mode == OPERATION_MODE_RECENT || + priv->load_state == LOAD_EMPTY) + { + same_path = FALSE; + } + else + { + g_assert (priv->current_folder != NULL); + + same_path = g_file_equal (parent_file, priv->current_folder); + } + + if (same_path && priv->load_state == LOAD_FINISHED) + { + gboolean result; + GSList files; + + files.data = (gpointer) file; + files.next = NULL; + + result = show_and_select_files (impl, &files); + g_object_unref (parent_file); + return result; + } + + pending_select_files_add (impl, file); + + if (!same_path) + { + gboolean result; + + result = gtk_file_chooser_set_current_folder_file (chooser, parent_file, error); + g_object_unref (parent_file); + return result; + } + + g_object_unref (parent_file); + return TRUE; +} + +static void +gtk_file_chooser_widget_unselect_file (GtkFileChooser *chooser, + GFile *file) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser); + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkTreeView *tree_view = GTK_TREE_VIEW (priv->browse_files_tree_view); + GtkTreeIter iter; + + if (!priv->browse_files_model) + return; + + if (!_gtk_file_system_model_get_iter_for_file (priv->browse_files_model, + &iter, + file)) + return; + + gtk_tree_selection_unselect_iter (gtk_tree_view_get_selection (tree_view), + &iter); +} + +static gboolean +maybe_select (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (data); + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkTreeSelection *selection; + gboolean is_sensitive; + gboolean is_folder; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); + + gtk_tree_model_get (model, iter, + MODEL_COL_IS_FOLDER, &is_folder, + MODEL_COL_IS_SENSITIVE, &is_sensitive, + -1); + + if (is_sensitive && + ((is_folder && priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) || + (!is_folder && priv->action == GTK_FILE_CHOOSER_ACTION_OPEN))) + gtk_tree_selection_select_iter (selection, iter); + else + gtk_tree_selection_unselect_iter (selection, iter); + + return FALSE; +} + +static void +gtk_file_chooser_widget_select_all (GtkFileChooser *chooser) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser); + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (priv->operation_mode == OPERATION_MODE_SEARCH || + priv->operation_mode == OPERATION_MODE_RECENT) + { + GtkTreeSelection *selection; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); + gtk_tree_selection_select_all (selection); + return; + } + + if (priv->select_multiple) + gtk_tree_model_foreach (GTK_TREE_MODEL (priv->browse_files_model), + maybe_select, impl); +} + +static void +gtk_file_chooser_widget_unselect_all (GtkFileChooser *chooser) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser); + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); + + gtk_tree_selection_unselect_all (selection); + pending_select_files_free (impl); +} + +/* Checks whether the filename entry for the Save modes contains a well-formed filename. + * + * is_well_formed_ret - whether what the user typed passes gkt_file_system_make_path() + * + * is_empty_ret - whether the file entry is totally empty + * + * is_file_part_empty_ret - whether the file part is empty (will be if user types "foobar/", and + * the path will be "$cwd/foobar") + */ +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) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkFileChooserEntry *chooser_entry; + GFile *current_folder; + const char *file_part; + GFile *file; + GError *error; + + g_assert (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE + || priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER + || ((priv->action == GTK_FILE_CHOOSER_ACTION_OPEN + || priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) + && priv->location_mode == LOCATION_MODE_FILENAME_ENTRY)); + + chooser_entry = GTK_FILE_CHOOSER_ENTRY (priv->location_entry); + + if (strlen (gtk_entry_get_text (GTK_ENTRY (chooser_entry))) == 0) + { + *file_ret = NULL; + *is_well_formed_ret = TRUE; + *is_empty_ret = TRUE; + *is_file_part_empty_ret = TRUE; + *is_folder = FALSE; + + return; + } + + *is_empty_ret = FALSE; + + current_folder = _gtk_file_chooser_entry_get_current_folder (chooser_entry); + if (!current_folder) + { + *file_ret = NULL; + *is_well_formed_ret = FALSE; + *is_file_part_empty_ret = FALSE; + *is_folder = FALSE; + + return; + } + + file_part = _gtk_file_chooser_entry_get_file_part (chooser_entry); + + if (!file_part || file_part[0] == '\0') + { + *file_ret = current_folder; + *is_well_formed_ret = TRUE; + *is_file_part_empty_ret = TRUE; + *is_folder = TRUE; + + return; + } + + *is_file_part_empty_ret = FALSE; + + error = NULL; + file = g_file_get_child_for_display_name (current_folder, file_part, &error); + g_object_unref (current_folder); + + if (!file) + { + error_building_filename_dialog (impl, error); + *file_ret = NULL; + *is_well_formed_ret = FALSE; + *is_folder = FALSE; + + return; + } + + *file_ret = file; + *is_well_formed_ret = TRUE; + *is_folder = _gtk_file_chooser_entry_get_is_folder (chooser_entry, file); +} + +struct get_files_closure { + GtkFileChooserWidget *impl; + GSList *result; + GFile *file_from_entry; +}; + +static void +get_files_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + struct get_files_closure *info; + GFile *file; + GtkFileSystemModel *fs_model; + + info = data; + fs_model = info->impl->priv->browse_files_model; + + file = _gtk_file_system_model_get_file (fs_model, iter); + if (!file) + return; /* We are on the editable row */ + + if (!info->file_from_entry || !g_file_equal (info->file_from_entry, file)) + info->result = g_slist_prepend (info->result, g_object_ref (file)); +} + +static GSList * +gtk_file_chooser_widget_get_files (GtkFileChooser *chooser) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser); + GtkFileChooserWidgetPrivate *priv = impl->priv; + struct get_files_closure info; + GtkWindow *toplevel; + GtkWidget *current_focus; + gboolean file_list_seen; + + info.impl = impl; + info.result = NULL; + info.file_from_entry = NULL; + + if (priv->operation_mode == OPERATION_MODE_SEARCH) + return search_get_selected_files (impl); + + if (priv->operation_mode == OPERATION_MODE_RECENT) + { + if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) + { + file_list_seen = TRUE; + goto file_entry; + } + else + return recent_get_selected_files (impl); + } + + toplevel = get_toplevel (GTK_WIDGET (impl)); + if (toplevel) + current_focus = gtk_window_get_focus (toplevel); + else + current_focus = NULL; + + file_list_seen = FALSE; + if (current_focus == priv->browse_files_tree_view) + { + GtkTreeSelection *selection; + + file_list: + + file_list_seen = TRUE; + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); + gtk_tree_selection_selected_foreach (selection, get_files_foreach, &info); + + /* If there is no selection in the file list, we probably have this situation: + * + * 1. The user typed a filename in the SAVE filename entry ("foo.txt"). + * 2. He then double-clicked on a folder ("bar") in the file list + * + * So we want the selection to be "bar/foo.txt". Jump to the case for the + * filename entry to see if that is the case. + */ + if (info.result == NULL && priv->location_entry) + goto file_entry; + } + else if (priv->location_entry && current_focus == priv->location_entry) + { + gboolean is_well_formed, is_empty, is_file_part_empty, is_folder; + + file_entry: + + check_save_entry (impl, &info.file_from_entry, &is_well_formed, &is_empty, &is_file_part_empty, &is_folder); + + if (is_empty) + goto out; + + if (!is_well_formed) + return NULL; + + if (is_file_part_empty && priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) + { + g_object_unref (info.file_from_entry); + return NULL; + } + + if (info.file_from_entry) + info.result = g_slist_prepend (info.result, info.file_from_entry); + else if (!file_list_seen) + goto file_list; + else + return NULL; + } + else if (priv->toplevel_last_focus_widget == priv->browse_files_tree_view) + goto file_list; + else if (priv->location_entry && priv->toplevel_last_focus_widget == priv->location_entry) + goto file_entry; + else + { + /* The focus is on a dialog's action area button or something else */ + if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE || + priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) + goto file_entry; + else + goto file_list; + } + + out: + + /* If there's no folder selected, and we're in SELECT_FOLDER mode, then we + * fall back to the current directory */ + if (priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER && + info.result == NULL) + { + GFile *current_folder; + + current_folder = gtk_file_chooser_get_current_folder_file (chooser); + + if (current_folder) + info.result = g_slist_prepend (info.result, current_folder); + } + + return g_slist_reverse (info.result); +} + +GFile * +gtk_file_chooser_widget_get_preview_file (GtkFileChooser *chooser) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser); + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (priv->preview_file) + return g_object_ref (priv->preview_file); + else + return NULL; +} + +static GtkFileSystem * +gtk_file_chooser_widget_get_file_system (GtkFileChooser *chooser) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser); + GtkFileChooserWidgetPrivate *priv = impl->priv; + + return priv->file_system; +} + +/* Shows or hides the filter widgets */ +static void +show_filters (GtkFileChooserWidget *impl, + gboolean show) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (show) + gtk_widget_show (priv->filter_combo_hbox); + else + gtk_widget_hide (priv->filter_combo_hbox); +} + +static void +gtk_file_chooser_widget_add_filter (GtkFileChooser *chooser, + GtkFileFilter *filter) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser); + GtkFileChooserWidgetPrivate *priv = impl->priv; + const gchar *name; + + if (g_slist_find (priv->filters, filter)) + { + g_warning ("gtk_file_chooser_add_filter() called on filter already in list\n"); + return; + } + + g_object_ref_sink (filter); + priv->filters = g_slist_append (priv->filters, filter); + + name = gtk_file_filter_get_name (filter); + if (!name) + name = "Untitled filter"; /* Place-holder, doesn't need to be marked for translation */ + + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (priv->filter_combo), name); + + if (!g_slist_find (priv->filters, priv->current_filter)) + set_current_filter (impl, filter); + + show_filters (impl, TRUE); +} + +static void +gtk_file_chooser_widget_remove_filter (GtkFileChooser *chooser, + GtkFileFilter *filter) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser); + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkTreeModel *model; + GtkTreeIter iter; + gint filter_index; + + filter_index = g_slist_index (priv->filters, filter); + + if (filter_index < 0) + { + g_warning ("gtk_file_chooser_remove_filter() called on filter not in list\n"); + return; + } + + priv->filters = g_slist_remove (priv->filters, filter); + + if (filter == priv->current_filter) + { + if (priv->filters) + set_current_filter (impl, priv->filters->data); + else + set_current_filter (impl, NULL); + } + + /* Remove row from the combo box */ + model = gtk_combo_box_get_model (GTK_COMBO_BOX (priv->filter_combo)); + if (!gtk_tree_model_iter_nth_child (model, &iter, NULL, filter_index)) + g_assert_not_reached (); + + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); + + g_object_unref (filter); + + if (!priv->filters) + show_filters (impl, FALSE); +} + +static GSList * +gtk_file_chooser_widget_list_filters (GtkFileChooser *chooser) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser); + GtkFileChooserWidgetPrivate *priv = impl->priv; + + return g_slist_copy (priv->filters); +} + +static gboolean +gtk_file_chooser_widget_add_shortcut_folder (GtkFileChooser *chooser, + GFile *file, + GError **error) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser); + GtkFileChooserWidgetPrivate *priv = impl->priv; + + gtk_places_sidebar_add_shortcut (GTK_PLACES_SIDEBAR (priv->places_sidebar), file); + return TRUE; +} + +static gboolean +gtk_file_chooser_widget_remove_shortcut_folder (GtkFileChooser *chooser, + GFile *file, + GError **error) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser); + GtkFileChooserWidgetPrivate *priv = impl->priv; + + gtk_places_sidebar_remove_shortcut (GTK_PLACES_SIDEBAR (priv->places_sidebar), file); + return TRUE; +} + +static GSList * +gtk_file_chooser_widget_list_shortcut_folders (GtkFileChooser *chooser) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser); + GtkFileChooserWidgetPrivate *priv = impl->priv; + + return gtk_places_sidebar_list_shortcuts (GTK_PLACES_SIDEBAR (priv->places_sidebar)); +} + +/* Guesses a size based upon font sizes */ +static void +find_good_size_from_style (GtkWidget *widget, + gint *width, + gint *height) +{ + GtkStyleContext *context; + GtkStateFlags state; + double font_size; + GdkScreen *screen; + double resolution; + + context = gtk_widget_get_style_context (widget); + state = gtk_widget_get_state_flags (widget); + + screen = gtk_widget_get_screen (widget); + if (screen) + { + resolution = gdk_screen_get_resolution (screen); + if (resolution < 0.0) /* will be -1 if the resolution is not defined in the GdkScreen */ + resolution = 96.0; + } + else + resolution = 96.0; /* wheeee */ + + gtk_style_context_get (context, state, "font-size", &font_size, NULL); + font_size = font_size * resolution / 72.0 + 0.5; + + *width = font_size * NUM_CHARS; + *height = font_size * NUM_LINES; +} + +static void +gtk_file_chooser_widget_get_default_size (GtkFileChooserEmbed *chooser_embed, + gint *default_width, + gint *default_height) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser_embed); + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkRequisition req; + int x, y, width, height; + GSettings *settings; + + settings = _gtk_file_chooser_get_settings_for_widget (GTK_WIDGET (impl)); + + g_settings_get (settings, SETTINGS_KEY_WINDOW_POSITION, "(ii)", &x, &y); + g_settings_get (settings, SETTINGS_KEY_WINDOW_SIZE, "(ii)", &width, &height); + + if (x >= 0 && y >= 0 && width > 0 && height > 0) + { + *default_width = width; + *default_height = height; + return; + } + + find_good_size_from_style (GTK_WIDGET (chooser_embed), default_width, default_height); + + if (priv->preview_widget_active && + priv->preview_widget && + gtk_widget_get_visible (priv->preview_widget)) + { + gtk_widget_get_preferred_size (priv->preview_box, + &req, NULL); + *default_width += PREVIEW_HBOX_SPACING + req.width; + } + + if (priv->extra_widget && + gtk_widget_get_visible (priv->extra_widget)) + { + gtk_widget_get_preferred_size (priv->extra_align, + &req, NULL); + *default_height += gtk_box_get_spacing (GTK_BOX (chooser_embed)) + req.height; + } +} + +struct switch_folder_closure { + GtkFileChooserWidget *impl; + GFile *file; + int num_selected; +}; + +/* Used from gtk_tree_selection_selected_foreach() in switch_to_selected_folder() */ +static void +switch_folder_foreach_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + struct switch_folder_closure *closure; + + closure = data; + + closure->file = _gtk_file_system_model_get_file (closure->impl->priv->browse_files_model, iter); + closure->num_selected++; +} + +/* Changes to the selected folder in the list view */ +static void +switch_to_selected_folder (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkTreeSelection *selection; + struct switch_folder_closure closure; + + /* We do this with foreach() rather than get_selected() as we may be in + * multiple selection mode + */ + + closure.impl = impl; + closure.file = NULL; + closure.num_selected = 0; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); + gtk_tree_selection_selected_foreach (selection, switch_folder_foreach_cb, &closure); + + g_assert (closure.file && closure.num_selected == 1); + + change_folder_and_display_error (impl, closure.file, FALSE); +} + +/* Gets the GFileInfo for the selected row in the file list; assumes single + * selection mode. + */ +static GFileInfo * +get_selected_file_info_from_file_list (GtkFileChooserWidget *impl, + gboolean *had_selection) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkTreeSelection *selection; + GtkTreeIter iter; + GFileInfo *info; + + g_assert (!priv->select_multiple); + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); + if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) + { + *had_selection = FALSE; + return NULL; + } + + *had_selection = TRUE; + + info = _gtk_file_system_model_get_info (priv->browse_files_model, &iter); + return info; +} + +/* Gets the display name of the selected file in the file list; assumes single + * selection mode and that something is selected. + */ +static const gchar * +get_display_name_from_file_list (GtkFileChooserWidget *impl) +{ + GFileInfo *info; + gboolean had_selection; + + info = get_selected_file_info_from_file_list (impl, &had_selection); + g_assert (had_selection); + g_assert (info != NULL); + + return g_file_info_get_display_name (info); +} + +static void +add_custom_button_to_dialog (GtkDialog *dialog, + const gchar *mnemonic_label, + gint response_id) +{ + GtkWidget *button; + + button = gtk_button_new_with_mnemonic (mnemonic_label); + gtk_widget_set_can_default (button, TRUE); + gtk_widget_show (button); + + gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, response_id); +} + +/* Presents an overwrite confirmation dialog; returns whether we should accept + * the filename. + */ +static gboolean +confirm_dialog_should_accept_filename (GtkFileChooserWidget *impl, + const gchar *file_part, + const gchar *folder_display_name) +{ + GtkWindow *toplevel; + GtkWidget *dialog; + int response; + + toplevel = get_toplevel (GTK_WIDGET (impl)); + + dialog = gtk_message_dialog_new (toplevel, + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_NONE, + _("A file named “%s” already exists. Do you want to replace it?"), + file_part); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + _("The file already exists in “%s”. Replacing it will " + "overwrite its contents."), + folder_display_name); + + gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Cancel"), GTK_RESPONSE_CANCEL); + add_custom_button_to_dialog (GTK_DIALOG (dialog), _("_Replace"), GTK_RESPONSE_ACCEPT); +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_ACCEPT, + GTK_RESPONSE_CANCEL, + -1); +G_GNUC_END_IGNORE_DEPRECATIONS + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT); + + if (gtk_window_has_group (toplevel)) + gtk_window_group_add_window (gtk_window_get_group (toplevel), + GTK_WINDOW (dialog)); + + response = gtk_dialog_run (GTK_DIALOG (dialog)); + + gtk_widget_destroy (dialog); + + return (response == GTK_RESPONSE_ACCEPT); +} + +struct GetDisplayNameData +{ + GtkFileChooserWidget *impl; + gchar *file_part; +}; + +/* Every time we request a response explicitly, we need to save the selection to the recently-used list, + * as requesting a response means, "the dialog is confirmed". + */ +static void +request_response_and_add_to_recent_list (GtkFileChooserWidget *impl) +{ + g_signal_emit_by_name (impl, "response-requested"); + add_selection_to_recent_list (impl); +} + +static void +confirmation_confirm_get_info_cb (GCancellable *cancellable, + GFileInfo *info, + const GError *error, + gpointer user_data) +{ + gboolean cancelled = g_cancellable_is_cancelled (cancellable); + gboolean should_respond = FALSE; + struct GetDisplayNameData *data = user_data; + GtkFileChooserWidgetPrivate *priv = data->impl->priv; + + if (cancellable != priv->should_respond_get_info_cancellable) + goto out; + + priv->should_respond_get_info_cancellable = NULL; + + if (cancelled) + goto out; + + if (error) + /* Huh? Did the folder disappear? Let the caller deal with it */ + should_respond = TRUE; + else + should_respond = confirm_dialog_should_accept_filename (data->impl, data->file_part, g_file_info_get_display_name (info)); + + set_busy_cursor (data->impl, FALSE); + if (should_respond) + request_response_and_add_to_recent_list (data->impl); + +out: + g_object_unref (data->impl); + g_free (data->file_part); + g_free (data); + + g_object_unref (cancellable); +} + +/* Does overwrite confirmation if appropriate, and returns whether the dialog + * should respond. Can get the file part from the file list or the save entry. + */ +static gboolean +should_respond_after_confirm_overwrite (GtkFileChooserWidget *impl, + const gchar *file_part, + GFile *parent_file) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkFileChooserConfirmation conf; + + if (!priv->do_overwrite_confirmation) + return TRUE; + + conf = GTK_FILE_CHOOSER_CONFIRMATION_CONFIRM; + + g_signal_emit_by_name (impl, "confirm-overwrite", &conf); + + switch (conf) + { + case GTK_FILE_CHOOSER_CONFIRMATION_CONFIRM: + { + struct GetDisplayNameData *data; + + g_assert (file_part != NULL); + + data = g_new0 (struct GetDisplayNameData, 1); + data->impl = g_object_ref (impl); + data->file_part = g_strdup (file_part); + + if (priv->should_respond_get_info_cancellable) + g_cancellable_cancel (priv->should_respond_get_info_cancellable); + + priv->should_respond_get_info_cancellable = + _gtk_file_system_get_info (priv->file_system, parent_file, + "standard::display-name", + confirmation_confirm_get_info_cb, + data); + set_busy_cursor (data->impl, TRUE); + return FALSE; + } + + case GTK_FILE_CHOOSER_CONFIRMATION_ACCEPT_FILENAME: + return TRUE; + + case GTK_FILE_CHOOSER_CONFIRMATION_SELECT_AGAIN: + return FALSE; + + default: + g_assert_not_reached (); + return FALSE; + } +} + +struct FileExistsData +{ + GtkFileChooserWidget *impl; + gboolean file_exists_and_is_not_folder; + GFile *parent_file; + GFile *file; +}; + +static void +name_entry_get_parent_info_cb (GCancellable *cancellable, + GFileInfo *info, + const GError *error, + gpointer user_data) +{ + gboolean parent_is_folder; + gboolean cancelled = g_cancellable_is_cancelled (cancellable); + struct FileExistsData *data = user_data; + GtkFileChooserWidget *impl = data->impl; + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (cancellable != priv->should_respond_get_info_cancellable) + goto out; + + priv->should_respond_get_info_cancellable = NULL; + + set_busy_cursor (impl, FALSE); + + if (cancelled) + goto out; + + if (!info) + parent_is_folder = FALSE; + else + parent_is_folder = _gtk_file_info_consider_as_directory (info); + + if (parent_is_folder) + { + if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN) + { + 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) */ + } + else if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) + { + if (data->file_exists_and_is_not_folder) + { + gboolean retval; + char *file_part; + + /* Dup the string because the string may be modified + * depending on what clients do in the confirm-overwrite + * signal and this corrupts the pointer + */ + file_part = g_strdup (_gtk_file_chooser_entry_get_file_part (GTK_FILE_CHOOSER_ENTRY (priv->location_entry))); + retval = should_respond_after_confirm_overwrite (impl, file_part, data->parent_file); + g_free (file_part); + + if (retval) + request_response_and_add_to_recent_list (impl); + } + else + request_response_and_add_to_recent_list (impl); + } + else if (priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + || priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) + { + GError *mkdir_error = NULL; + + /* In both cases (SELECT_FOLDER and CREATE_FOLDER), if you type + * "/blah/nonexistent" you *will* want a folder created. + */ + + set_busy_cursor (impl, TRUE); + g_file_make_directory (data->file, NULL, &mkdir_error); + set_busy_cursor (impl, FALSE); + + if (!mkdir_error) + request_response_and_add_to_recent_list (impl); + else + error_creating_folder_dialog (impl, data->file, mkdir_error); + } + else + g_assert_not_reached (); + } + else + { + if (info) + { + /* The parent exists, but it's not a folder! Someone probably typed existing_file.txt/subfile.txt */ + error_with_file_under_nonfolder (impl, data->parent_file); + } + else + { + GError *error_copy; + + /* The parent folder is not readable for some reason */ + + error_copy = g_error_copy (error); + error_changing_folder_dialog (impl, data->parent_file, error_copy); + } + } + +out: + g_object_unref (data->impl); + g_object_unref (data->file); + g_object_unref (data->parent_file); + g_free (data); + + g_object_unref (cancellable); +} + +static void +file_exists_get_info_cb (GCancellable *cancellable, + GFileInfo *info, + const GError *error, + gpointer user_data) +{ + gboolean data_ownership_taken = FALSE; + gboolean cancelled = g_cancellable_is_cancelled (cancellable); + gboolean file_exists; + gboolean is_folder; + gboolean needs_parent_check = FALSE; + struct FileExistsData *data = user_data; + GtkFileChooserWidget *impl = data->impl; + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (cancellable != priv->file_exists_get_info_cancellable) + goto out; + + priv->file_exists_get_info_cancellable = NULL; + + set_busy_cursor (impl, FALSE); + + if (cancelled) + goto out; + + file_exists = (info != NULL); + is_folder = (file_exists && _gtk_file_info_consider_as_directory (info)); + + if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN) + { + if (is_folder) + change_folder_and_display_error (impl, data->file, TRUE); + else + { + if (file_exists) + request_response_and_add_to_recent_list (impl); /* user typed an existing filename; we are done */ + else + needs_parent_check = TRUE; /* file doesn't exist; see if its parent exists */ + } + } + else if (priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) + { + if (file_exists && !is_folder) + { + /* Oops, the user typed the name of an existing path which is not + * a folder + */ + error_creating_folder_over_existing_file_dialog (impl, data->file, + g_error_copy (error)); + } + else + { + needs_parent_check = TRUE; + } + } + else if (priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) + { + if (!file_exists) + { + needs_parent_check = TRUE; + } + else + { + if (is_folder) + { + /* User typed a folder; we are done */ + request_response_and_add_to_recent_list (impl); + } + else + error_selecting_folder_over_existing_file_dialog (impl); + } + } + else if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) + { + if (is_folder) + change_folder_and_display_error (impl, data->file, TRUE); + else + if (!file_exists && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_FILENAME_TOO_LONG)) + error_filename_to_long_dialog (data->impl); + else + needs_parent_check = TRUE; + } + else + { + g_assert_not_reached(); + } + + if (needs_parent_check) { + /* check that everything up to the last path component exists (i.e. the parent) */ + + data->file_exists_and_is_not_folder = file_exists && !is_folder; + data_ownership_taken = TRUE; + + if (priv->should_respond_get_info_cancellable) + g_cancellable_cancel (priv->should_respond_get_info_cancellable); + + priv->should_respond_get_info_cancellable = + _gtk_file_system_get_info (priv->file_system, + data->parent_file, + "standard::type", + name_entry_get_parent_info_cb, + data); + set_busy_cursor (impl, TRUE); + } + +out: + if (!data_ownership_taken) + { + g_object_unref (impl); + g_object_unref (data->file); + g_object_unref (data->parent_file); + g_free (data); + } + + g_object_unref (cancellable); +} + +static void +paste_text_received (GtkClipboard *clipboard, + const gchar *text, + GtkFileChooserWidget *impl) +{ + GFile *file; + + if (!text) + return; + + file = g_file_new_for_uri (text); + + if (!gtk_file_chooser_widget_select_file (GTK_FILE_CHOOSER (impl), file, NULL)) + location_popup_handler (impl, text); + + g_object_unref (file); +} + +/* Handler for the "location-popup-on-paste" keybinding signal */ +static void +location_popup_on_paste_handler (GtkFileChooserWidget *impl) +{ + GtkClipboard *clipboard = gtk_widget_get_clipboard (GTK_WIDGET (impl), + GDK_SELECTION_CLIPBOARD); + gtk_clipboard_request_text (clipboard, + (GtkClipboardTextReceivedFunc) paste_text_received, + impl); +} + +/* Implementation for GtkFileChooserEmbed::should_respond() */ +static void +add_selection_to_recent_list (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GSList *files; + GSList *l; + + files = gtk_file_chooser_widget_get_files (GTK_FILE_CHOOSER (impl)); + + for (l = files; l; l = l->next) + { + GFile *file = l->data; + char *uri; + + uri = g_file_get_uri (file); + if (uri) + { + gtk_recent_manager_add_item (priv->recent_manager, uri); + g_free (uri); + } + } + + g_slist_foreach (files, (GFunc) g_object_unref, NULL); + g_slist_free (files); +} + +static gboolean +gtk_file_chooser_widget_should_respond (GtkFileChooserEmbed *chooser_embed) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser_embed); + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkWidget *toplevel; + GtkWidget *current_focus; + gboolean retval; + + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (impl)); + g_assert (GTK_IS_WINDOW (toplevel)); + + retval = FALSE; + + current_focus = gtk_window_get_focus (GTK_WINDOW (toplevel)); + + if (current_focus == priv->browse_files_tree_view) + { + /* The following array encodes what we do based on the priv->action and the + * number of files selected. + */ + typedef enum { + NOOP, /* Do nothing (don't respond) */ + RESPOND, /* Respond immediately */ + RESPOND_OR_SWITCH, /* Respond immediately if the selected item is a file; switch to it if it is a folder */ + ALL_FILES, /* Respond only if everything selected is a file */ + ALL_FOLDERS, /* Respond only if everything selected is a folder */ + SAVE_ENTRY, /* Go to the code for handling the save entry */ + NOT_REACHED /* Sanity check */ + } ActionToTake; + static const ActionToTake what_to_do[4][3] = { + /* 0 selected 1 selected many selected */ + /* ACTION_OPEN */ { NOOP, RESPOND_OR_SWITCH, ALL_FILES }, + /* ACTION_SAVE */ { SAVE_ENTRY, RESPOND_OR_SWITCH, NOT_REACHED }, + /* ACTION_SELECT_FOLDER */ { RESPOND, ALL_FOLDERS, ALL_FOLDERS }, + /* ACTION_CREATE_FOLDER */ { SAVE_ENTRY, ALL_FOLDERS, NOT_REACHED } + }; + + int num_selected; + gboolean all_files, all_folders; + int k; + ActionToTake action; + + file_list: + + g_assert (priv->action >= GTK_FILE_CHOOSER_ACTION_OPEN && priv->action <= GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER); + + if (priv->operation_mode == OPERATION_MODE_SEARCH) + { + retval = search_should_respond (impl); + goto out; + } + + if (priv->operation_mode == OPERATION_MODE_RECENT) + { + if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) + goto save_entry; + else + { + retval = recent_should_respond (impl); + goto out; + } + } + + selection_check (impl, &num_selected, &all_files, &all_folders); + + if (num_selected > 2) + k = 2; + else + k = num_selected; + + action = what_to_do [priv->action] [k]; + + switch (action) + { + case NOOP: + return FALSE; + + case RESPOND: + retval = TRUE; + goto out; + + case RESPOND_OR_SWITCH: + g_assert (num_selected == 1); + + if (all_folders) + { + switch_to_selected_folder (impl); + return FALSE; + } + else if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) + { + retval = should_respond_after_confirm_overwrite (impl, + get_display_name_from_file_list (impl), + priv->current_folder); + goto out; + } + else + { + retval = TRUE; + goto out; + } + + case ALL_FILES: + retval = all_files; + goto out; + + case ALL_FOLDERS: + retval = all_folders; + goto out; + + case SAVE_ENTRY: + goto save_entry; + + default: + g_assert_not_reached (); + } + } + else if ((priv->location_entry != NULL) && (current_focus == priv->location_entry)) + { + GFile *file; + gboolean is_well_formed, is_empty, is_file_part_empty; + gboolean is_folder; + GtkFileChooserEntry *entry; + GError *error; + + save_entry: + + g_assert (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE + || priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER + || ((priv->action == GTK_FILE_CHOOSER_ACTION_OPEN + || priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) + && priv->location_mode == LOCATION_MODE_FILENAME_ENTRY)); + + entry = GTK_FILE_CHOOSER_ENTRY (priv->location_entry); + check_save_entry (impl, &file, &is_well_formed, &is_empty, &is_file_part_empty, &is_folder); + + if (!is_well_formed) + { + if (!is_empty + && priv->action == GTK_FILE_CHOOSER_ACTION_SAVE + && priv->operation_mode == OPERATION_MODE_RECENT) + { + path_bar_set_mode (impl, PATH_BAR_ERROR_NO_FOLDER); +#if 0 + /* We'll #ifdef this out, as the fucking treeview selects its first row, + * thus changing our assumption that no selection is present - setting + * a selection causes the error message from path_bar_set_mode() to go away, + * but we want the user to see that message! + */ + gtk_widget_grab_focus (priv->browse_files_tree_view); +#endif + } + /* FIXME: else show an "invalid filename" error as the pathbar mode? */ + + return FALSE; + } + + if (is_empty) + { + if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE + || priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) + { + path_bar_set_mode (impl, PATH_BAR_ERROR_NO_FILENAME); + gtk_widget_grab_focus (priv->location_entry); + return FALSE; + } + + goto file_list; + } + + g_assert (file != NULL); + + error = NULL; + if (is_folder) + { + if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN || + priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) + { + change_folder_and_display_error (impl, file, TRUE); + } + else if (priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER || + priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) + { + /* The folder already exists, so we do not need to create it. + * Just respond to terminate the dialog. + */ + retval = TRUE; + } + else + { + g_assert_not_reached (); + } + } + else + { + struct FileExistsData *data; + + /* We need to check whether file exists and whether it is a folder - + * the GtkFileChooserEntry *does* report is_folder==FALSE as a false + * negative (it doesn't know yet if your last path component is a + * folder). + */ + + data = g_new0 (struct FileExistsData, 1); + data->impl = g_object_ref (impl); + data->file = g_object_ref (file); + data->parent_file = _gtk_file_chooser_entry_get_current_folder (entry); + + if (priv->file_exists_get_info_cancellable) + g_cancellable_cancel (priv->file_exists_get_info_cancellable); + + priv->file_exists_get_info_cancellable = + _gtk_file_system_get_info (priv->file_system, file, + "standard::type", + file_exists_get_info_cb, + data); + + set_busy_cursor (impl, TRUE); + + if (error != NULL) + g_error_free (error); + } + + g_object_unref (file); + } + else if (priv->toplevel_last_focus_widget == priv->browse_files_tree_view) + { + /* The focus is on a dialog's action area button, *and* the widget that + * was focused immediately before it is the file list. + */ + goto file_list; + } + else if (priv->operation_mode == OPERATION_MODE_SEARCH && priv->toplevel_last_focus_widget == priv->search_entry) + { + search_entry_activate_cb (GTK_ENTRY (priv->search_entry), impl); + return FALSE; + } + else if (priv->location_entry && priv->toplevel_last_focus_widget == priv->location_entry) + { + /* The focus is on a dialog's action area button, *and* the widget that + * was focused immediately before it is the location entry. + */ + goto save_entry; + } + else + /* The focus is on a dialog's action area button or something else */ + if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE + || priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) + goto save_entry; + else + goto file_list; + + out: + + if (retval) + add_selection_to_recent_list (impl); + + return retval; +} + +/* Implementation for GtkFileChooserEmbed::initial_focus() */ +static void +gtk_file_chooser_widget_initial_focus (GtkFileChooserEmbed *chooser_embed) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (chooser_embed); + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkWidget *widget; + + if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN || + priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) + { + if (priv->location_mode == LOCATION_MODE_PATH_BAR + || priv->operation_mode == OPERATION_MODE_RECENT) + widget = priv->browse_files_tree_view; + else + widget = priv->location_entry; + } + else if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE || + priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) + widget = priv->location_entry; + else + { + g_assert_not_reached (); + widget = NULL; + } + + g_assert (widget != NULL); + gtk_widget_grab_focus (widget); +} + +/* Callback used from gtk_tree_selection_selected_foreach(); gets the selected GFiles */ +static void +search_selected_foreach_get_file_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + GSList **list; + GFile *file; + + list = data; + + gtk_tree_model_get (model, iter, MODEL_COL_FILE, &file, -1); + *list = g_slist_prepend (*list, file); /* The file already has a new ref courtesy of gtk_tree_model_get(); this will be unreffed by the caller */ +} + +/* Constructs a list of the selected paths in search mode */ +static GSList * +search_get_selected_files (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GSList *result; + GtkTreeSelection *selection; + + result = NULL; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); + gtk_tree_selection_selected_foreach (selection, search_selected_foreach_get_file_cb, &result); + result = g_slist_reverse (result); + + return result; +} + +/* Called from ::should_respond(). We return whether there are selected files + * in the search list. + */ +static gboolean +search_should_respond (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkTreeSelection *selection; + + g_assert (priv->operation_mode == OPERATION_MODE_SEARCH); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); + return (gtk_tree_selection_count_selected_rows (selection) != 0); +} + +/* Adds one hit from the search engine to the search_model */ +static void +search_add_hit (GtkFileChooserWidget *impl, + gchar *uri) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GFile *file; + + file = g_file_new_for_uri (uri); + if (!file) + return; + + if (!g_file_is_native (file)) + { + g_object_unref (file); + return; + } + + _gtk_file_system_model_add_and_query_file (priv->search_model, + file, + MODEL_ATTRIBUTES); + + g_object_unref (file); +} + +/* Callback used from GtkSearchEngine when we get new hits */ +static void +search_engine_hits_added_cb (GtkSearchEngine *engine, + GList *hits, + gpointer data) +{ + GtkFileChooserWidget *impl; + GList *l; + + impl = GTK_FILE_CHOOSER_WIDGET (data); + + for (l = hits; l; l = l->next) + search_add_hit (impl, (gchar*)l->data); +} + +/* Callback used from GtkSearchEngine when the query is done running */ +static void +search_engine_finished_cb (GtkSearchEngine *engine, + gpointer data) +{ + GtkFileChooserWidget *impl; + + impl = GTK_FILE_CHOOSER_WIDGET (data); + +#if 0 + /* EB: setting the model here will avoid loads of row events, + * but it'll make the search look like blocked. + */ + gtk_tree_view_set_model (GTK_TREE_VIEW (impl->browse_files_tree_view), + GTK_TREE_MODEL (impl->search_model)); + file_list_set_sort_column_ids (impl); +#endif + + /* FMQ: if search was empty, say that we got no hits */ + set_busy_cursor (impl, FALSE); +} + +/* Displays a generic error when we cannot create a GtkSearchEngine. + * It would be better if _gtk_search_engine_new() gave us a GError + * with a better message, but it doesn't do that right now. + */ +static void +search_error_could_not_create_client (GtkFileChooserWidget *impl) +{ + error_message (impl, + _("Could not start the search process"), + _("The program was not able to create a connection to the indexer " + "daemon. Please make sure it is running.")); +} + +static void +search_engine_error_cb (GtkSearchEngine *engine, + const gchar *message, + gpointer data) +{ + GtkFileChooserWidget *impl; + + impl = GTK_FILE_CHOOSER_WIDGET (data); + + search_stop_searching (impl, TRUE); + error_message (impl, _("Could not send the search request"), message); + + set_busy_cursor (impl, FALSE); +} + +/* Frees the data in the search_model */ +static void +search_clear_model (GtkFileChooserWidget *impl, + gboolean remove_from_treeview) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (!priv->search_model) + return; + + g_object_unref (priv->search_model); + priv->search_model = NULL; + + if (remove_from_treeview) + gtk_tree_view_set_model (GTK_TREE_VIEW (priv->browse_files_tree_view), NULL); +} + +/* Stops any ongoing searches; does not touch the search_model */ +static void +search_stop_searching (GtkFileChooserWidget *impl, + gboolean remove_query) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (remove_query && priv->search_query) + { + g_object_unref (priv->search_query); + priv->search_query = NULL; + } + + if (priv->search_engine) + { + _gtk_search_engine_stop (priv->search_engine); + + g_object_unref (priv->search_engine); + priv->search_engine = NULL; + } +} + +/* Creates the search_model and puts it in the tree view */ +static void +search_setup_model (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + g_assert (priv->search_model == NULL); + + priv->search_model = _gtk_file_system_model_new (file_system_model_set, + impl, + MODEL_COLUMN_TYPES); + + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->search_model), + MODEL_COL_NAME, + name_sort_func, + impl, NULL); + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->search_model), + MODEL_COL_MTIME, + mtime_sort_func, + impl, NULL); + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->search_model), + MODEL_COL_SIZE, + size_sort_func, + impl, NULL); + set_sort_column (impl); + + /* EB: setting the model here will make the hits list update feel + * more "alive" than setting the model at the end of the search + * run + */ + gtk_tree_view_set_model (GTK_TREE_VIEW (priv->browse_files_tree_view), + GTK_TREE_MODEL (priv->search_model)); + file_list_set_sort_column_ids (impl); +} + +/* Creates a new query with the specified text and launches it */ +static void +search_start_query (GtkFileChooserWidget *impl, + const gchar *query_text) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + search_stop_searching (impl, FALSE); + search_clear_model (impl, TRUE); + search_setup_model (impl); + set_busy_cursor (impl, TRUE); + + if (priv->search_engine == NULL) + priv->search_engine = _gtk_search_engine_new (); + + if (!priv->search_engine) + { + set_busy_cursor (impl, FALSE); + search_error_could_not_create_client (impl); /* lame; we don't get an error code or anything */ + return; + } + + if (!priv->search_query) + { + priv->search_query = _gtk_query_new (); + _gtk_query_set_text (priv->search_query, query_text); + } + + _gtk_search_engine_set_query (priv->search_engine, priv->search_query); + + g_signal_connect (priv->search_engine, "hits-added", + G_CALLBACK (search_engine_hits_added_cb), impl); + g_signal_connect (priv->search_engine, "finished", + G_CALLBACK (search_engine_finished_cb), impl); + g_signal_connect (priv->search_engine, "error", + G_CALLBACK (search_engine_error_cb), impl); + + _gtk_search_engine_start (priv->search_engine); +} + +/* Callback used when the user presses Enter while typing on the search + * entry; starts the query + */ +static void +search_entry_activate_cb (GtkEntry *entry, + gpointer data) +{ + GtkFileChooserWidget *impl = GTK_FILE_CHOOSER_WIDGET (data); + GtkFileChooserWidgetPrivate *priv = impl->priv; + const char *text; + + text = gtk_entry_get_text (GTK_ENTRY (priv->search_entry)); + if (strlen (text) == 0) + return; + + /* reset any existing query object */ + if (priv->search_query) + { + g_object_unref (priv->search_query); + priv->search_query = NULL; + } + + search_start_query (impl, text); +} + +static gboolean +focus_entry_idle_cb (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + gdk_threads_enter (); + + g_source_destroy (priv->focus_entry_idle); + priv->focus_entry_idle = NULL; + + if (priv->search_entry) + gtk_widget_grab_focus (priv->search_entry); + + gdk_threads_leave (); + + return FALSE; +} + +static void +focus_search_entry_in_idle (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + /* bgo#634558 - When the user clicks on the Search entry in the shortcuts + * pane, we get a selection-changed signal and we set up the search widgets. + * However, gtk_tree_view_button_press() focuses the treeview *after* making + * the change to the selection. So, we need to re-focus the search entry + * after the treeview has finished doing its work; we'll do that in an idle + * handler. + */ + + if (!priv->focus_entry_idle) + priv->focus_entry_idle = add_idle_while_impl_is_alive (impl, G_CALLBACK (focus_entry_idle_cb)); +} + +/* Hides the path bar and creates the search entry */ +static void +search_setup_widgets (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + priv->search_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + + path_bar_update (impl); + + priv->search_entry = gtk_entry_new (); + g_signal_connect (priv->search_entry, "activate", + G_CALLBACK (search_entry_activate_cb), + impl); + gtk_box_pack_start (GTK_BOX (priv->search_hbox), priv->search_entry, TRUE, TRUE, 0); + + /* if there already is a query, restart it */ + if (priv->search_query) + { + gchar *query = _gtk_query_get_text (priv->search_query); + + if (query) + { + gtk_entry_set_text (GTK_ENTRY (priv->search_entry), query); + search_start_query (impl, query); + + g_free (query); + } + else + { + g_object_unref (priv->search_query); + priv->search_query = NULL; + } + } + + /* Box for search widgets */ + gtk_box_pack_start (GTK_BOX (priv->browse_path_bar_hbox), priv->search_hbox, TRUE, TRUE, 0); + gtk_widget_show_all (priv->search_hbox); + gtk_size_group_add_widget (GTK_SIZE_GROUP (priv->browse_path_bar_size_group), priv->search_hbox); + + /* Hide the location widgets temporarily */ + + if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN || + priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) + { + gtk_widget_hide (priv->location_button); + gtk_widget_hide (priv->location_entry_box); + } + + focus_search_entry_in_idle (impl); + + /* FMQ: hide the filter combo? */ +} + +/* + * Recent files support + */ + +/* Frees the data in the recent_model */ +static void +recent_clear_model (GtkFileChooserWidget *impl, + gboolean remove_from_treeview) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (!priv->recent_model) + return; + + if (remove_from_treeview) + gtk_tree_view_set_model (GTK_TREE_VIEW (priv->browse_files_tree_view), NULL); + + g_object_unref (priv->recent_model); + priv->recent_model = NULL; +} + +/* Stops any ongoing loading of the recent files list; does + * not touch the recent_model + */ +static void +recent_stop_loading (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (priv->load_recent_id) + { + g_source_remove (priv->load_recent_id); + priv->load_recent_id = 0; + } +} + +static void +recent_setup_model (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + g_assert (priv->recent_model == NULL); + + priv->recent_model = _gtk_file_system_model_new (file_system_model_set, + impl, + MODEL_COLUMN_TYPES); + + _gtk_file_system_model_set_filter (priv->recent_model, + priv->current_filter); + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->recent_model), + MODEL_COL_NAME, + name_sort_func, + impl, NULL); + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->recent_model), + MODEL_COL_SIZE, + size_sort_func, + impl, NULL); + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->recent_model), + MODEL_COL_MTIME, + mtime_sort_func, + impl, NULL); + set_sort_column (impl); +} + +typedef struct +{ + GtkFileChooserWidget *impl; + GList *items; +} RecentLoadData; + +static void +recent_idle_cleanup (gpointer data) +{ + RecentLoadData *load_data = data; + GtkFileChooserWidget *impl = load_data->impl; + GtkFileChooserWidgetPrivate *priv = impl->priv; + + gtk_tree_view_set_model (GTK_TREE_VIEW (priv->browse_files_tree_view), + GTK_TREE_MODEL (priv->recent_model)); + file_list_set_sort_column_ids (impl); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (priv->recent_model), MODEL_COL_MTIME, GTK_SORT_DESCENDING); + + set_busy_cursor (impl, FALSE); - priv = gtk_file_chooser_widget_get_instance_private (GTK_FILE_CHOOSER_WIDGET (object)); + priv->load_recent_id = 0; + + g_free (load_data); +} + +/* Populates the file system model with the GtkRecentInfo* items in the provided list; frees the items */ +static void +populate_model_with_recent_items (GtkFileChooserWidget *impl, GList *items) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + gint limit; + GList *l; + int n; + + limit = DEFAULT_RECENT_FILES_LIMIT; + + n = 0; + + for (l = items; l; l = l->next) + { + GtkRecentInfo *info = l->data; + GFile *file; + + file = g_file_new_for_uri (gtk_recent_info_get_uri (info)); + _gtk_file_system_model_add_and_query_file (priv->recent_model, + file, + MODEL_ATTRIBUTES); + g_object_unref (file); + + n++; + if (limit != -1 && n >= limit) + break; + } +} + +static void +populate_model_with_folders (GtkFileChooserWidget *impl, GList *items) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GList *folders; + GList *l; + + folders = _gtk_file_chooser_extract_recent_folders (items); + + for (l = folders; l; l = l->next) + { + GFile *folder = l->data; + + _gtk_file_system_model_add_and_query_file (priv->recent_model, + folder, + MODEL_ATTRIBUTES); + } + + g_list_foreach (folders, (GFunc) g_object_unref, NULL); + g_list_free (folders); +} + +static gboolean +recent_idle_load (gpointer data) +{ + RecentLoadData *load_data = data; + GtkFileChooserWidget *impl = load_data->impl; + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (!priv->recent_manager) + return FALSE; + + load_data->items = gtk_recent_manager_get_items (priv->recent_manager); + if (!load_data->items) + return FALSE; + + if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN) + populate_model_with_recent_items (impl, load_data->items); + else + populate_model_with_folders (impl, load_data->items); + + g_list_foreach (load_data->items, (GFunc) gtk_recent_info_unref, NULL); + g_list_free (load_data->items); + load_data->items = NULL; + + return FALSE; +} + +static void +recent_start_loading (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + RecentLoadData *load_data; + + recent_stop_loading (impl); + recent_clear_model (impl, TRUE); + recent_setup_model (impl); + set_busy_cursor (impl, TRUE); + + g_assert (priv->load_recent_id == 0); + + load_data = g_new (RecentLoadData, 1); + load_data->impl = impl; + load_data->items = NULL; + + /* begin lazy loading the recent files into the model */ + priv->load_recent_id = gdk_threads_add_idle_full (G_PRIORITY_HIGH_IDLE + 30, + recent_idle_load, + load_data, + recent_idle_cleanup); +} + +static void +recent_selected_foreach_get_file_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + GSList **list; + GFile *file; + + list = data; + + gtk_tree_model_get (model, iter, MODEL_COL_FILE, &file, -1); + *list = g_slist_prepend (*list, file); +} + +/* Constructs a list of the selected paths in recent files mode */ +static GSList * +recent_get_selected_files (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GSList *result; + GtkTreeSelection *selection; + + result = NULL; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); + gtk_tree_selection_selected_foreach (selection, recent_selected_foreach_get_file_cb, &result); + result = g_slist_reverse (result); + + return result; +} + +/* Called from ::should_respond(). We return whether there are selected + * files in the recent files list. + */ +static gboolean +recent_should_respond (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkTreeSelection *selection; + + g_assert (priv->operation_mode == OPERATION_MODE_RECENT); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view)); + return (gtk_tree_selection_count_selected_rows (selection) != 0); +} + +static void +set_current_filter (GtkFileChooserWidget *impl, + GtkFileFilter *filter) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (priv->current_filter != filter) + { + int filter_index; + + /* NULL filters are allowed to reset to non-filtered status + */ + filter_index = g_slist_index (priv->filters, filter); + if (priv->filters && filter && filter_index < 0) + return; + + if (priv->current_filter) + g_object_unref (priv->current_filter); + priv->current_filter = filter; + if (priv->current_filter) + { + g_object_ref_sink (priv->current_filter); + } + + if (priv->filters) + gtk_combo_box_set_active (GTK_COMBO_BOX (priv->filter_combo), + filter_index); + + if (priv->browse_files_model) + { + _gtk_file_system_model_set_filter (priv->browse_files_model, priv->current_filter); + _gtk_file_system_model_clear_cache (priv->browse_files_model, MODEL_COL_IS_SENSITIVE); + } + + if (priv->search_model) + { + _gtk_file_system_model_set_filter (priv->search_model, filter); + _gtk_file_system_model_clear_cache (priv->search_model, MODEL_COL_IS_SENSITIVE); + } + + if (priv->recent_model) + { + _gtk_file_system_model_set_filter (priv->recent_model, filter); + _gtk_file_system_model_clear_cache (priv->recent_model, MODEL_COL_IS_SENSITIVE); + } + + g_object_notify (G_OBJECT (impl), "filter"); + } +} + +static void +filter_combo_changed (GtkComboBox *combo_box, + GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + gint new_index = gtk_combo_box_get_active (combo_box); + GtkFileFilter *new_filter = g_slist_nth_data (priv->filters, new_index); + + set_current_filter (impl, new_filter); +} + +static void +check_preview_change (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkTreePath *cursor_path; + GFile *new_file; + char *new_display_name; + GtkTreeModel *model; + + gtk_tree_view_get_cursor (GTK_TREE_VIEW (priv->browse_files_tree_view), &cursor_path, NULL); + model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->browse_files_tree_view)); + if (cursor_path) + { + GtkTreeIter iter; + + gtk_tree_model_get_iter (model, &iter, cursor_path); + gtk_tree_model_get (model, &iter, + MODEL_COL_FILE, &new_file, + MODEL_COL_NAME, &new_display_name, + -1); + + gtk_tree_path_free (cursor_path); + } + else + { + new_file = NULL; + new_display_name = NULL; + } + + if (new_file != priv->preview_file && + !(new_file && priv->preview_file && + g_file_equal (new_file, priv->preview_file))) + { + if (priv->preview_file) + { + g_object_unref (priv->preview_file); + g_free (priv->preview_display_name); + } + + if (new_file) + { + priv->preview_file = new_file; + priv->preview_display_name = new_display_name; + } + else + { + priv->preview_file = NULL; + priv->preview_display_name = NULL; + g_free (new_display_name); + } + + if (priv->use_preview_label && priv->preview_label) + gtk_label_set_text (GTK_LABEL (priv->preview_label), priv->preview_display_name); + + g_signal_emit_by_name (impl, "update-preview"); + } + else + { + if (new_file) + g_object_unref (new_file); + + g_free (new_display_name); + } +} + +static gboolean +list_select_func (GtkTreeSelection *selection, + GtkTreeModel *model, + GtkTreePath *path, + gboolean path_currently_selected, + gpointer data) +{ + GtkFileChooserWidget *impl = data; + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER || + priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) + { + GtkTreeIter iter; + gboolean is_sensitive; + gboolean is_folder; + + if (!gtk_tree_model_get_iter (model, &iter, path)) + return FALSE; + gtk_tree_model_get (model, &iter, + MODEL_COL_IS_SENSITIVE, &is_sensitive, + MODEL_COL_IS_FOLDER, &is_folder, + -1); + if (!is_sensitive || !is_folder) + return FALSE; + } + + return TRUE; +} + +static void +list_selection_changed (GtkTreeSelection *selection, + GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + /* See if we are in the new folder editable row for Save mode */ + if (priv->operation_mode == OPERATION_MODE_BROWSE && + priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) + { + GFileInfo *info; + gboolean had_selection; + + info = get_selected_file_info_from_file_list (impl, &had_selection); + if (!had_selection) + goto out; /* normal processing */ + + if (!info) + return; /* We are on the editable row for New Folder */ + } + + out: + + if (priv->location_entry) + update_chooser_entry (impl); + + path_bar_update (impl); + + check_preview_change (impl); + check_file_list_menu_sensitivity (impl); + + g_signal_emit_by_name (impl, "selection-changed", 0); +} + +/* Callback used when a row in the file list is activated */ +static void +list_row_activated (GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GFile *file; + GtkTreeIter iter; + GtkTreeModel *model; + gboolean is_folder; + gboolean is_sensitive; + + model = gtk_tree_view_get_model (tree_view); + + if (!gtk_tree_model_get_iter (model, &iter, path)) + return; + + gtk_tree_model_get (model, &iter, + MODEL_COL_FILE, &file, + MODEL_COL_IS_FOLDER, &is_folder, + MODEL_COL_IS_SENSITIVE, &is_sensitive, + -1); + + if (is_sensitive && is_folder && file) + { + change_folder_and_display_error (impl, file, FALSE); + goto out; + } + + if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN || + priv->action == GTK_FILE_CHOOSER_ACTION_SAVE) + g_signal_emit_by_name (impl, "file-activated"); + + out: + + if (file) + g_object_unref (file); +} + +static void +path_bar_clicked (GtkPathBar *path_bar, + GFile *file, + GFile *child_file, + gboolean child_is_hidden, + GtkFileChooserWidget *impl) +{ + if (child_file) + pending_select_files_add (impl, child_file); + + if (!change_folder_and_display_error (impl, file, FALSE)) + return; + + /* Say we have "/foo/bar/[.baz]" and the user clicks on "bar". We should then + * show hidden files so that ".baz" appears in the file list, as it will still + * be shown in the path bar: "/foo/[bar]/.baz" + */ + if (child_is_hidden) + g_object_set (impl, "show-hidden", TRUE, NULL); +} + +static void +update_cell_renderer_attributes (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GtkTreeViewColumn *column; + GtkCellRenderer *renderer; + GList *walk, *list; + + /* Keep the following column numbers in sync with create_file_list() */ + + /* name */ + column = gtk_tree_view_get_column (GTK_TREE_VIEW (priv->browse_files_tree_view), 0); + list = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column)); + for (walk = list; walk; walk = walk->next) + { + renderer = walk->data; + if (GTK_IS_CELL_RENDERER_PIXBUF (renderer)) + { + gtk_tree_view_column_set_attributes (column, renderer, + "surface", MODEL_COL_SURFACE, + NULL); + } + else + { + gtk_tree_view_column_set_attributes (column, renderer, + "text", MODEL_COL_NAME, + "ellipsize", MODEL_COL_ELLIPSIZE, + NULL); + } + + gtk_tree_view_column_add_attribute (column, renderer, "sensitive", MODEL_COL_IS_SENSITIVE); + } + g_list_free (list); + + /* size */ + column = gtk_tree_view_get_column (GTK_TREE_VIEW (priv->browse_files_tree_view), 1); + list = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column)); + renderer = list->data; + gtk_tree_view_column_set_attributes (column, renderer, + "text", MODEL_COL_SIZE_TEXT, + NULL); + + gtk_tree_view_column_add_attribute (column, renderer, "sensitive", MODEL_COL_IS_SENSITIVE); + g_list_free (list); + + /* mtime */ + column = gtk_tree_view_get_column (GTK_TREE_VIEW (priv->browse_files_tree_view), 2); + list = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column)); + renderer = list->data; + gtk_tree_view_column_set_attributes (column, renderer, + "text", MODEL_COL_MTIME_TEXT, + NULL); + gtk_tree_view_column_add_attribute (column, renderer, "sensitive", MODEL_COL_IS_SENSITIVE); + g_list_free (list); +} + +static void +location_set_user_text (GtkFileChooserWidget *impl, + const gchar *path) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + gtk_entry_set_text (GTK_ENTRY (priv->location_entry), path); + gtk_editable_set_position (GTK_EDITABLE (priv->location_entry), -1); +} + +static void +location_popup_handler (GtkFileChooserWidget *impl, + const gchar *path) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + if (priv->operation_mode != OPERATION_MODE_BROWSE) + { + GtkWidget *widget_to_focus; + + operation_mode_set (impl, OPERATION_MODE_BROWSE); + + if (priv->current_folder) + change_folder_and_display_error (impl, priv->current_folder, FALSE); + + if (priv->location_mode == LOCATION_MODE_PATH_BAR) + widget_to_focus = priv->browse_files_tree_view; + else + widget_to_focus = priv->location_entry; + + gtk_widget_grab_focus (widget_to_focus); + return; + } - g_object_get_property (G_OBJECT (priv->impl), pspec->name, value); + if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN || + priv->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) + { + if (!path) + return; + + location_mode_set (impl, LOCATION_MODE_FILENAME_ENTRY, TRUE); + location_set_user_text (impl, path); + } + else if (priv->action == GTK_FILE_CHOOSER_ACTION_SAVE || + priv->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) + { + gtk_widget_grab_focus (priv->location_entry); + if (path != NULL) + location_set_user_text (impl, path); + } + else + g_assert_not_reached (); +} + +/* Handler for the "up-folder" keybinding signal */ +static void +up_folder_handler (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + _gtk_path_bar_up (GTK_PATH_BAR (priv->browse_path_bar)); +} + +/* Handler for the "down-folder" keybinding signal */ +static void +down_folder_handler (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + _gtk_path_bar_down (GTK_PATH_BAR (priv->browse_path_bar)); +} + +/* Handler for the "home-folder" keybinding signal */ +static void +home_folder_handler (GtkFileChooserWidget *impl) +{ + switch_to_home_dir (impl); +} + +/* Handler for the "desktop-folder" keybinding signal */ +static void +desktop_folder_handler (GtkFileChooserWidget *impl) +{ + const char *name; + + name = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP); + /* "To disable a directory, point it to the homedir." + * See http://freedesktop.org/wiki/Software/xdg-user-dirs + **/ + if (!g_strcmp0 (name, g_get_home_dir ())) { + return; + } + + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (impl), name); +} + +/* Handler for the "search-shortcut" keybinding signal */ +static void +search_shortcut_handler (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + operation_mode_set (impl, OPERATION_MODE_SEARCH); + + /* we want the entry widget to grab the focus the first + * time, not the browse_files_tree_view widget. + */ + if (priv->search_entry) + gtk_widget_grab_focus (priv->search_entry); +} + +/* Handler for the "recent-shortcut" keybinding signal */ +static void +recent_shortcut_handler (GtkFileChooserWidget *impl) +{ + operation_mode_set (impl, OPERATION_MODE_RECENT); +} + +static void +quick_bookmark_handler (GtkFileChooserWidget *impl, + gint bookmark_index) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + GFile *file; + + file = gtk_places_sidebar_get_nth_bookmark (GTK_PLACES_SIDEBAR (priv->places_sidebar), bookmark_index); + + if (file) + { + change_folder_and_display_error (impl, file, FALSE); + g_object_unref (file); + } +} + +static void +show_hidden_handler (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv = impl->priv; + + g_object_set (impl, + "show-hidden", !priv->show_hidden, + NULL); +} + +static void +add_normal_and_shifted_binding (GtkBindingSet *binding_set, + guint keyval, + GdkModifierType modifiers, + const gchar *signal_name) +{ + gtk_binding_entry_add_signal (binding_set, + keyval, modifiers, + signal_name, 0); + + gtk_binding_entry_add_signal (binding_set, + keyval, modifiers | GDK_SHIFT_MASK, + signal_name, 0); +} + +static void +gtk_file_chooser_widget_class_init (GtkFileChooserWidgetClass *class) +{ + static const guint quick_bookmark_keyvals[10] = { + 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 + }; + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + GtkBindingSet *binding_set; + int i; + + gobject_class->finalize = gtk_file_chooser_widget_finalize; + gobject_class->constructor = gtk_file_chooser_widget_constructor; + gobject_class->set_property = gtk_file_chooser_widget_set_property; + gobject_class->get_property = gtk_file_chooser_widget_get_property; + gobject_class->dispose = gtk_file_chooser_widget_dispose; + + widget_class->show_all = gtk_file_chooser_widget_show_all; + widget_class->realize = gtk_file_chooser_widget_realize; + widget_class->map = gtk_file_chooser_widget_map; + widget_class->unmap = gtk_file_chooser_widget_unmap; + widget_class->hierarchy_changed = gtk_file_chooser_widget_hierarchy_changed; + widget_class->style_updated = gtk_file_chooser_widget_style_updated; + widget_class->screen_changed = gtk_file_chooser_widget_screen_changed; + + signals[LOCATION_POPUP] = + g_signal_new_class_handler (I_("location-popup"), + G_OBJECT_CLASS_TYPE (class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_CALLBACK (location_popup_handler), + NULL, NULL, + _gtk_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + + signals[LOCATION_POPUP_ON_PASTE] = + g_signal_new_class_handler (I_("location-popup-on-paste"), + G_OBJECT_CLASS_TYPE (class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_CALLBACK (location_popup_on_paste_handler), + NULL, NULL, + _gtk_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[LOCATION_TOGGLE_POPUP] = + g_signal_new_class_handler (I_("location-toggle-popup"), + G_OBJECT_CLASS_TYPE (class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_CALLBACK (location_toggle_popup_handler), + NULL, NULL, + _gtk_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[UP_FOLDER] = + g_signal_new_class_handler (I_("up-folder"), + G_OBJECT_CLASS_TYPE (class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_CALLBACK (up_folder_handler), + NULL, NULL, + _gtk_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[DOWN_FOLDER] = + g_signal_new_class_handler (I_("down-folder"), + G_OBJECT_CLASS_TYPE (class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_CALLBACK (down_folder_handler), + NULL, NULL, + _gtk_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[HOME_FOLDER] = + g_signal_new_class_handler (I_("home-folder"), + G_OBJECT_CLASS_TYPE (class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_CALLBACK (home_folder_handler), + NULL, NULL, + _gtk_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[DESKTOP_FOLDER] = + g_signal_new_class_handler (I_("desktop-folder"), + G_OBJECT_CLASS_TYPE (class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_CALLBACK (desktop_folder_handler), + NULL, NULL, + _gtk_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[QUICK_BOOKMARK] = + g_signal_new_class_handler (I_("quick-bookmark"), + G_OBJECT_CLASS_TYPE (class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_CALLBACK (quick_bookmark_handler), + NULL, NULL, + _gtk_marshal_VOID__INT, + G_TYPE_NONE, 1, G_TYPE_INT); + + signals[SHOW_HIDDEN] = + g_signal_new_class_handler (I_("show-hidden"), + G_OBJECT_CLASS_TYPE (class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_CALLBACK (show_hidden_handler), + NULL, NULL, + _gtk_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[SEARCH_SHORTCUT] = + g_signal_new_class_handler (I_("search-shortcut"), + G_OBJECT_CLASS_TYPE (class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_CALLBACK (search_shortcut_handler), + NULL, NULL, + _gtk_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[RECENT_SHORTCUT] = + g_signal_new_class_handler (I_("recent-shortcut"), + G_OBJECT_CLASS_TYPE (class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_CALLBACK (recent_shortcut_handler), + NULL, NULL, + _gtk_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + binding_set = gtk_binding_set_by_class (class); + + gtk_binding_entry_add_signal (binding_set, + GDK_KEY_l, GDK_CONTROL_MASK, + "location-toggle-popup", + 0); + + gtk_binding_entry_add_signal (binding_set, + GDK_KEY_slash, 0, + "location-popup", + 1, G_TYPE_STRING, "/"); + gtk_binding_entry_add_signal (binding_set, + GDK_KEY_KP_Divide, 0, + "location-popup", + 1, G_TYPE_STRING, "/"); + +#ifdef G_OS_UNIX + gtk_binding_entry_add_signal (binding_set, + GDK_KEY_asciitilde, 0, + "location-popup", + 1, G_TYPE_STRING, "~"); +#endif + + gtk_binding_entry_add_signal (binding_set, + GDK_KEY_v, GDK_CONTROL_MASK, + "location-popup-on-paste", + 0); + + add_normal_and_shifted_binding (binding_set, + GDK_KEY_Up, GDK_MOD1_MASK, + "up-folder"); + + add_normal_and_shifted_binding (binding_set, + GDK_KEY_KP_Up, GDK_MOD1_MASK, + "up-folder"); + + add_normal_and_shifted_binding (binding_set, + GDK_KEY_Down, GDK_MOD1_MASK, + "down-folder"); + add_normal_and_shifted_binding (binding_set, + GDK_KEY_KP_Down, GDK_MOD1_MASK, + "down-folder"); + + gtk_binding_entry_add_signal (binding_set, + GDK_KEY_Home, GDK_MOD1_MASK, + "home-folder", + 0); + gtk_binding_entry_add_signal (binding_set, + GDK_KEY_KP_Home, GDK_MOD1_MASK, + "home-folder", + 0); + gtk_binding_entry_add_signal (binding_set, + GDK_KEY_d, GDK_MOD1_MASK, + "desktop-folder", + 0); + gtk_binding_entry_add_signal (binding_set, + GDK_KEY_h, GDK_CONTROL_MASK, + "show-hidden", + 0); + gtk_binding_entry_add_signal (binding_set, + GDK_KEY_s, GDK_MOD1_MASK, + "search-shortcut", + 0); + gtk_binding_entry_add_signal (binding_set, + GDK_KEY_r, GDK_MOD1_MASK, + "recent-shortcut", + 0); + + for (i = 0; i < 10; i++) + gtk_binding_entry_add_signal (binding_set, + quick_bookmark_keyvals[i], GDK_MOD1_MASK, + "quick-bookmark", + 1, G_TYPE_INT, i); + + _gtk_file_chooser_install_properties (gobject_class); + + /* Bind class to template */ + gtk_widget_class_set_template_from_resource (widget_class, + "/org/gtk/libgtk/ui/gtkfilechooserwidget.ui"); + + /* A *lot* of widgets that we need to handle .... */ + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, browse_widgets_box); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, browse_widgets_hpaned); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, browse_header_box); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, browse_widgets_box); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, places_sidebar); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, browse_files_tree_view); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, browse_new_folder_button); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, browse_path_bar_hbox); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, browse_path_bar_size_group); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, browse_path_bar); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, browse_special_mode_icon); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, browse_special_mode_label); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, browse_select_a_folder_info_bar); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, browse_select_a_folder_label); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, browse_select_a_folder_icon); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, filter_combo_hbox); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, filter_combo); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, preview_box); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, extra_align); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, location_button); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, location_entry_box); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, location_label); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, list_name_column); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, list_pixbuf_renderer); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, list_name_renderer); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, list_mtime_column); + gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, list_size_column); + + /* And a *lot* of callbacks to bind ... */ + gtk_widget_class_bind_template_callback (widget_class, browse_files_key_press_event_cb); + gtk_widget_class_bind_template_callback (widget_class, file_list_drag_drop_cb); + gtk_widget_class_bind_template_callback (widget_class, file_list_drag_data_received_cb); + gtk_widget_class_bind_template_callback (widget_class, list_popup_menu_cb); + gtk_widget_class_bind_template_callback (widget_class, file_list_query_tooltip_cb); + gtk_widget_class_bind_template_callback (widget_class, list_button_press_event_cb); + gtk_widget_class_bind_template_callback (widget_class, list_row_activated); + gtk_widget_class_bind_template_callback (widget_class, file_list_drag_motion_cb); + gtk_widget_class_bind_template_callback (widget_class, list_selection_changed); + gtk_widget_class_bind_template_callback (widget_class, renderer_editing_canceled_cb); + gtk_widget_class_bind_template_callback (widget_class, renderer_edited_cb); + gtk_widget_class_bind_template_callback (widget_class, filter_combo_changed); + gtk_widget_class_bind_template_callback (widget_class, location_button_toggled_cb); + gtk_widget_class_bind_template_callback (widget_class, new_folder_button_clicked); + gtk_widget_class_bind_template_callback (widget_class, path_bar_clicked); + gtk_widget_class_bind_template_callback (widget_class, places_sidebar_open_location_cb); + gtk_widget_class_bind_template_callback (widget_class, places_sidebar_show_error_message_cb); +} + +static void +post_process_ui (GtkFileChooserWidget *impl) +{ + GtkTreeSelection *selection; + GtkCellRenderer *cell; + GList *cells; + + /* Some qdata, qdata can't be set with GtkBuilder */ + g_object_set_data (G_OBJECT (impl->priv->browse_files_tree_view), "fmq-name", "file_list"); + g_object_set_data (G_OBJECT (impl->priv->browse_files_tree_view), I_("GtkFileChooserWidget"), impl); + + /* Setup file list treeview */ + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->priv->browse_files_tree_view)); + gtk_tree_selection_set_select_function (selection, + list_select_func, + impl, NULL); + gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (impl->priv->browse_files_tree_view), + GDK_BUTTON1_MASK, + NULL, 0, + GDK_ACTION_COPY | GDK_ACTION_MOVE); + gtk_drag_source_add_uri_targets (impl->priv->browse_files_tree_view); + + gtk_drag_dest_set (impl->priv->browse_files_tree_view, + GTK_DEST_DEFAULT_ALL, + NULL, 0, + GDK_ACTION_COPY | GDK_ACTION_MOVE); + gtk_drag_dest_add_uri_targets (impl->priv->browse_files_tree_view); + + /* File browser treemodel columns are shared between GtkFileChooser implementations, + * so we don't set cell renderer attributes in GtkBuilder, but rather keep that + * in code. + */ + file_list_set_sort_column_ids (impl); + update_cell_renderer_attributes (impl); + + /* Get the combo's text renderer and set ellipsize parameters, + * perhaps GtkComboBoxText should declare the cell renderer + * as an 'internal-child', then we could configure it in GtkBuilder + * instead of hard coding it here. + */ + cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (impl->priv->filter_combo)); + g_assert (cells); + cell = cells->data; + g_object_set (G_OBJECT (cell), + "ellipsize", PANGO_ELLIPSIZE_END, + NULL); + + g_list_free (cells); + + /* Set the GtkPathBar file system backend */ + _gtk_path_bar_set_file_system (GTK_PATH_BAR (impl->priv->browse_path_bar), impl->priv->file_system); + + /* Set the fixed size icon renderer, this requires + * that priv->icon_size be already setup. + */ + set_icon_cell_renderer_fixed_size (impl); +} + +static void +gtk_file_chooser_widget_init (GtkFileChooserWidget *impl) +{ + GtkFileChooserWidgetPrivate *priv; + + profile_start ("start", NULL); +#ifdef PROFILE_FILE_CHOOSER + access ("MARK: *** CREATE FILE CHOOSER", F_OK); +#endif + impl->priv = gtk_file_chooser_widget_get_instance_private (impl); + priv = impl->priv; + + priv->local_only = TRUE; + priv->preview_widget_active = TRUE; + priv->use_preview_label = TRUE; + priv->select_multiple = FALSE; + priv->show_hidden = FALSE; + priv->show_size_column = TRUE; + priv->icon_size = FALLBACK_ICON_SIZE; + priv->load_state = LOAD_EMPTY; + priv->reload_state = RELOAD_EMPTY; + priv->pending_select_files = NULL; + priv->location_mode = LOCATION_MODE_PATH_BAR; + priv->operation_mode = OPERATION_MODE_BROWSE; + priv->sort_column = MODEL_COL_NAME; + priv->sort_order = GTK_SORT_ASCENDING; + priv->recent_manager = gtk_recent_manager_get_default (); + priv->create_folders = TRUE; + priv->auto_selecting_first_row = FALSE; + + /* Ensure GTK+ private types used by the template + * definition before calling gtk_widget_init_template() + */ + g_type_ensure (GTK_TYPE_PATH_BAR); + gtk_widget_init_template (GTK_WIDGET (impl)); + gtk_widget_set_size_request (priv->browse_files_tree_view, 280, -1); + + set_file_system_backend (impl); + + if (g_settings_schema_source_lookup (g_settings_schema_source_get_default (), + "org.gnome.desktop.interface", + TRUE) != NULL) + + priv->bookmarks_manager = _gtk_bookmarks_manager_new (NULL, NULL); + + /* Setup various attributes and callbacks in the UI + * which cannot be done with GtkBuilder. + */ + post_process_ui (impl); + + profile_end ("end", NULL); } /** * gtk_file_chooser_widget_new: * @action: Open or save mode for the widget - * + * * Creates a new #GtkFileChooserWidget. This is a file chooser widget that can * be embedded in custom windows, and it is the same widget that is used by * #GtkFileChooserDialog. - * + * * Return value: a new #GtkFileChooserWidget * * Since: 2.4 diff --git a/gtk/gtkrc.key.mac b/gtk/gtkrc.key.mac index 265135f162..943843d881 100644 --- a/gtk/gtkrc.key.mac +++ b/gtk/gtkrc.key.mac @@ -126,7 +126,7 @@ binding "gtk-mac-file-chooser" bind "<meta>Up" { "up-folder" () } } -class "GtkFileChooserDefault" binding "gtk-mac-file-chooser" +class "GtkFileChooserWidget" binding "gtk-mac-file-chooser" binding "gtk-mac-tree-view" diff --git a/gtk/resources/ui/gtkfilechooserdefault.ui b/gtk/resources/ui/gtkfilechooserwidget.ui index 0d86cb5613..80f86d4426 100644 --- a/gtk/resources/ui/gtkfilechooserdefault.ui +++ b/gtk/resources/ui/gtkfilechooserwidget.ui @@ -7,7 +7,7 @@ <property name="can_focus">False</property> <property name="icon_name">gtk-edit</property> </object> - <template class="GtkFileChooserDefault" parent="GtkBox"> + <template class="GtkFileChooserWidget" parent="GtkBox"> <property name="can_focus">False</property> <property name="orientation">vertical</property> <property name="spacing">12</property> diff --git a/testsuite/gtk/filechooser.c b/testsuite/gtk/filechooser.c index 831425ffbc..2584420e0b 100644 --- a/testsuite/gtk/filechooser.c +++ b/testsuite/gtk/filechooser.c @@ -1891,14 +1891,13 @@ has_action (const GtkFileChooserAction *actions, return FALSE; } -static GtkFileChooserDefault * -get_impl_from_dialog (GtkWidget *dialog) +static GtkFileChooserWidgetPrivate * +get_widget_priv_from_dialog (GtkWidget *dialog) { GtkFileChooserDialog *d; GtkFileChooserDialogPrivate *dialog_priv; GtkFileChooserWidget *chooser_widget; GtkFileChooserWidgetPrivate *widget_priv; - GtkFileChooserDefault *impl; d = GTK_FILE_CHOOSER_DIALOG (dialog); dialog_priv = d->priv; @@ -1907,59 +1906,56 @@ get_impl_from_dialog (GtkWidget *dialog) g_error ("BUG: dialog_priv->widget is not a GtkFileChooserWidget"); widget_priv = chooser_widget->priv; - impl = (GtkFileChooserDefault *) (widget_priv->impl); - if (!impl) - g_error ("BUG: widget_priv->impl is not a GtkFileChooserDefault"); - return impl; + return widget_priv; } static gboolean test_widgets_for_current_action (GtkFileChooserDialog *dialog, GtkFileChooserAction expected_action) { - GtkFileChooserDefault *impl; + GtkFileChooserWidgetPrivate *priv; gboolean passed; if (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (dialog)) != expected_action) return FALSE; - impl = get_impl_from_dialog (GTK_WIDGET (dialog)); + priv = get_widget_priv_from_dialog (GTK_WIDGET (dialog)); - g_assert (impl->action == expected_action); + g_assert (priv->action == expected_action); passed = TRUE; /* OPEN implies that the "new folder" button is hidden; otherwise it is shown */ - if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN) - passed = passed && !gtk_widget_get_visible (impl->browse_new_folder_button); + if (priv->action == GTK_FILE_CHOOSER_ACTION_OPEN) + passed = passed && !gtk_widget_get_visible (priv->browse_new_folder_button); else - passed = passed && gtk_widget_get_visible (impl->browse_new_folder_button); + passed = passed && gtk_widget_get_visible (priv->browse_new_folder_button); /* Check that the widgets are present/visible or not */ - if (has_action (open_actions, G_N_ELEMENTS (open_actions), impl->action)) - { - passed = passed && (impl->save_widgets == NULL - && (impl->location_mode == LOCATION_MODE_PATH_BAR - ? impl->location_entry == NULL - : impl->location_entry != NULL) - && impl->save_folder_label == NULL - && impl->save_folder_combo == NULL - && impl->save_expander == NULL - && GTK_IS_CONTAINER (impl->browse_widgets) && gtk_widget_is_drawable (impl->browse_widgets)); + if (has_action (open_actions, G_N_ELEMENTS (open_actions), priv->action)) + { + passed = passed && (priv->save_widgets == NULL + && (priv->location_mode == LOCATION_MODE_PATH_BAR + ? priv->location_entry == NULL + : priv->location_entry != NULL) + && priv->save_folder_label == NULL + && priv->save_folder_combo == NULL + && priv->save_expander == NULL + && GTK_IS_CONTAINER (priv->browse_widgets) && gtk_widget_is_drawable (priv->browse_widgets)); } - else if (has_action (save_actions, G_N_ELEMENTS (save_actions), impl->action)) + else if (has_action (save_actions, G_N_ELEMENTS (save_actions), priv->action)) { /* FIXME: we can't use GTK_IS_FILE_CHOOSER_ENTRY() because it uses * _gtk_file_chooser_entry_get_type(), which is a non-exported symbol. - * So, we just test impl->location_entry for being non-NULL + * So, we just test priv->location_entry for being non-NULL */ - passed = passed && (GTK_IS_CONTAINER (impl->save_widgets) && gtk_widget_is_drawable (impl->save_widgets) - && impl->location_entry != NULL && gtk_widget_is_drawable (impl->location_entry) - && GTK_IS_LABEL (impl->save_folder_label) && gtk_widget_is_drawable (impl->save_folder_label) - && GTK_IS_COMBO_BOX (impl->save_folder_combo) && gtk_widget_is_drawable (impl->save_folder_combo) - && GTK_IS_EXPANDER (impl->save_expander) && gtk_widget_is_drawable (impl->save_expander) - && GTK_IS_CONTAINER (impl->browse_widgets)); + passed = passed && (GTK_IS_CONTAINER (priv->save_widgets) && gtk_widget_is_drawable (priv->save_widgets) + && priv->location_entry != NULL && gtk_widget_is_drawable (priv->location_entry) + && GTK_IS_LABEL (priv->save_folder_label) && gtk_widget_is_drawable (priv->save_folder_label) + && GTK_IS_COMBO_BOX (priv->save_folder_combo) && gtk_widget_is_drawable (priv->save_folder_combo) + && GTK_IS_EXPANDER (priv->save_expander) && gtk_widget_is_drawable (priv->save_expander) + && GTK_IS_CONTAINER (priv->browse_widgets)); /* FIXME: we are in a SAVE mode; test the visibility and sensitivity of * the children that change depending on the state of the expander. @@ -1967,7 +1963,7 @@ test_widgets_for_current_action (GtkFileChooserDialog *dialog, } else { - g_error ("BAD TEST: test_widgets_for_current_action() doesn't know about %s", get_action_name (impl->action)); + g_error ("BAD TEST: test_widgets_for_current_action() doesn't know about %s", get_action_name (priv->action)); passed = FALSE; } @@ -2085,7 +2081,7 @@ static gboolean test_reload_sequence (gboolean set_folder_before_map) { GtkWidget *dialog; - GtkFileChooserDefault *impl; + GtkFileChooserWidgetPrivate *priv; gboolean passed; char *folder; char *current_working_dir; @@ -2102,7 +2098,7 @@ test_reload_sequence (gboolean set_folder_before_map) _("_OK"), GTK_RESPONSE_ACCEPT, NULL); - impl = get_impl_from_dialog (dialog); + priv = get_widget_priv_from_dialog (dialog); if (set_folder_before_map) { @@ -2110,13 +2106,13 @@ test_reload_sequence (gboolean set_folder_before_map) wait_for_idle (); - passed = passed && (impl->current_folder != NULL - && impl->browse_files_model != NULL - && (impl->load_state == LOAD_PRELOAD || impl->load_state == LOAD_LOADING || impl->load_state == LOAD_FINISHED) - && impl->reload_state == RELOAD_HAS_FOLDER - && (impl->load_state == LOAD_PRELOAD ? (impl->load_timeout_id != 0) : TRUE) - && ((impl->load_state == LOAD_LOADING || impl->load_state == LOAD_FINISHED) - ? (impl->load_timeout_id == 0 && impl->sort_model != NULL) + passed = passed && (priv->current_folder != NULL + && priv->browse_files_model != NULL + && (priv->load_state == LOAD_PRELOAD || priv->load_state == LOAD_LOADING || priv->load_state == LOAD_FINISHED) + && priv->reload_state == RELOAD_HAS_FOLDER + && (priv->load_state == LOAD_PRELOAD ? (priv->load_timeout_id != 0) : TRUE) + && ((priv->load_state == LOAD_LOADING || priv->load_state == LOAD_FINISHED) + ? (priv->load_timeout_id == 0 && priv->sort_model != NULL) : TRUE)); wait_for_idle (); @@ -2128,12 +2124,12 @@ test_reload_sequence (gboolean set_folder_before_map) else { /* Initially, no folder is not loaded or pending */ - passed = passed && (impl->current_folder == NULL - && impl->sort_model == NULL - && impl->browse_files_model == NULL - && impl->load_state == LOAD_EMPTY - && impl->reload_state == RELOAD_EMPTY - && impl->load_timeout_id == 0); + passed = passed && (priv->current_folder == NULL + && priv->sort_model == NULL + && priv->browse_files_model == NULL + && priv->load_state == LOAD_EMPTY + && priv->reload_state == RELOAD_EMPTY + && priv->load_timeout_id == 0); wait_for_idle (); @@ -2149,13 +2145,13 @@ test_reload_sequence (gboolean set_folder_before_map) wait_for_idle (); - passed = passed && (impl->current_folder != NULL - && impl->browse_files_model != NULL - && (impl->load_state == LOAD_PRELOAD || impl->load_state == LOAD_LOADING || impl->load_state == LOAD_FINISHED) - && impl->reload_state == RELOAD_HAS_FOLDER - && (impl->load_state == LOAD_PRELOAD ? (impl->load_timeout_id != 0) : TRUE) - && ((impl->load_state == LOAD_LOADING || impl->load_state == LOAD_FINISHED) - ? (impl->load_timeout_id == 0 && impl->sort_model != NULL) + passed = passed && (priv->current_folder != NULL + && priv->browse_files_model != NULL + && (priv->load_state == LOAD_PRELOAD || priv->load_state == LOAD_LOADING || priv->load_state == LOAD_FINISHED) + && priv->reload_state == RELOAD_HAS_FOLDER + && (priv->load_state == LOAD_PRELOAD ? (priv->load_timeout_id != 0) : TRUE) + && ((priv->load_state == LOAD_LOADING || priv->load_state == LOAD_FINISHED) + ? (priv->load_timeout_id == 0 && priv->sort_model != NULL) : TRUE)); folder = gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dialog)); @@ -2174,12 +2170,12 @@ test_reload_sequence (gboolean set_folder_before_map) wait_for_idle (); - passed = passed && (impl->current_folder != NULL - && impl->browse_files_model != NULL - && (impl->load_state == LOAD_PRELOAD || impl->load_state == LOAD_LOADING || impl->load_state == LOAD_FINISHED) - && (impl->load_state == LOAD_PRELOAD ? (impl->load_timeout_id != 0) : TRUE) - && ((impl->load_state == LOAD_LOADING || impl->load_state == LOAD_FINISHED) - ? (impl->load_timeout_id == 0 && impl->sort_model != NULL) + passed = passed && (priv->current_folder != NULL + && priv->browse_files_model != NULL + && (priv->load_state == LOAD_PRELOAD || priv->load_state == LOAD_LOADING || priv->load_state == LOAD_FINISHED) + && (priv->load_state == LOAD_PRELOAD ? (priv->load_timeout_id != 0) : TRUE) + && ((priv->load_state == LOAD_LOADING || priv->load_state == LOAD_FINISHED) + ? (priv->load_timeout_id == 0 && priv->sort_model != NULL) : TRUE)); folder = gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dialog)); @@ -2198,13 +2194,13 @@ test_reload_sequence (gboolean set_folder_before_map) wait_for_idle (); - passed = passed && (impl->current_folder != NULL - && impl->browse_files_model != NULL - && (impl->load_state == LOAD_PRELOAD || impl->load_state == LOAD_LOADING || impl->load_state == LOAD_FINISHED) - && impl->reload_state == RELOAD_HAS_FOLDER - && (impl->load_state == LOAD_PRELOAD ? (impl->load_timeout_id != 0) : TRUE) - && ((impl->load_state == LOAD_LOADING || impl->load_state == LOAD_FINISHED) - ? (impl->load_timeout_id == 0 && impl->sort_model != NULL) + passed = passed && (priv->current_folder != NULL + && priv->browse_files_model != NULL + && (priv->load_state == LOAD_PRELOAD || priv->load_state == LOAD_LOADING || priv->load_state == LOAD_FINISHED) + && priv->reload_state == RELOAD_HAS_FOLDER + && (priv->load_state == LOAD_PRELOAD ? (priv->load_timeout_id != 0) : TRUE) + && ((priv->load_state == LOAD_LOADING || priv->load_state == LOAD_FINISHED) + ? (priv->load_timeout_id == 0 && priv->sort_model != NULL) : TRUE)); folder = gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dialog)); @@ -2390,7 +2386,7 @@ test_folder_switch_and_filters (void) GtkWidget *dialog; GtkFileFilter *all_filter; GtkFileFilter *txt_filter; - GtkFileChooserDefault *impl; + GtkFileChooserWidgetPrivate *priv; passed = TRUE; @@ -2401,7 +2397,7 @@ test_folder_switch_and_filters (void) _("_Cancel"), GTK_RESPONSE_CANCEL, _("_OK"), GTK_RESPONSE_ACCEPT, NULL); - impl = get_impl_from_dialog (dialog); + priv = get_widget_priv_from_dialog (dialog); cwd_file = g_file_new_for_path (cwd); base_dir_file = g_file_new_for_path (base_dir); @@ -2445,7 +2441,7 @@ test_folder_switch_and_filters (void) gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), base_dir); sleep_in_main_loop (); - g_signal_emit_by_name (impl->browse_path_bar, "path-clicked", + g_signal_emit_by_name (priv->browse_path_bar, "path-clicked", cwd_file, base_dir_file, FALSE); diff --git a/testsuite/gtk/templates.c b/testsuite/gtk/templates.c index c53fc61574..c05fc110d9 100644 --- a/testsuite/gtk/templates.c +++ b/testsuite/gtk/templates.c @@ -211,7 +211,7 @@ test_file_chooser_widget_basic (void) /* XXX BUG: * * Spin the mainloop for a bit, this allows the file operations - * to complete, GtkFileChooserDefault has a bug where it leaks + * to complete, GtkFileChooserWidget has a bug where it leaks * GtkTreeRowReferences to the internal shortcuts_model * * Since we assert all automated children are finalized we |