+ /* 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)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ if (!impl->search_model)
+ return;
+
+ model = GTK_TREE_MODEL (impl->search_model);
+
+ if (gtk_tree_model_get_iter_first (model, &iter))
+ do
+ {
+ GtkFilePath *path;
+ gchar *display_name;
+ gchar *collation_key;
+ struct stat *statbuf;
+ GtkFileSystemHandle *handle;
+
+ gtk_tree_model_get (model, &iter,
+ SEARCH_MODEL_COL_PATH, &path,
+ SEARCH_MODEL_COL_DISPLAY_NAME, &display_name,
+ SEARCH_MODEL_COL_COLLATION_KEY, &collation_key,
+ SEARCH_MODEL_COL_STAT, &statbuf,
+ SEARCH_MODEL_COL_HANDLE, &handle,
+ -1);
+
+ if (handle)
+ gtk_file_system_cancel_operation (handle);
+
+ gtk_file_path_free (path);
+ g_free (display_name);
+ g_free (collation_key);
+ g_free (statbuf);
+ }
+ while (gtk_tree_model_iter_next (model, &iter));
+
+ g_object_unref (impl->search_model);
+ impl->search_model = NULL;
+
+ g_object_unref (impl->search_model_filter);
+ impl->search_model_filter = NULL;
+
+ g_object_unref (impl->search_model_sort);
+ impl->search_model_sort = NULL;
+
+ if (remove_from_treeview)
+ gtk_tree_view_set_model (GTK_TREE_VIEW (impl->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)
+{
+ if (remove_query && impl->search_query)
+ {
+ g_object_unref (impl->search_query);
+ impl->search_query = NULL;
+ }
+
+ if (impl->search_engine)
+ {
+ _gtk_search_engine_stop (impl->search_engine);
+
+ g_object_unref (impl->search_engine);
+ impl->search_engine = NULL;
+ }
+}
+
+/* Stops any pending searches, clears the file list, and switches back to OPERATION_MODE_BROWSE */
+static void
+search_switch_to_browse_mode (GtkFileChooserDefault *impl)
+{
+ g_assert (impl->operation_mode != OPERATION_MODE_BROWSE);
+
+ search_stop_searching (impl, FALSE);
+ search_clear_model (impl, TRUE);
+
+ gtk_widget_destroy (impl->search_hbox);
+ impl->search_hbox = NULL;
+ impl->search_entry = NULL;
+
+ gtk_widget_show (impl->browse_path_bar);
+ gtk_widget_show (impl->browse_new_folder_button);
+
+ if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
+ impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
+ {
+ gtk_widget_show (impl->location_button);
+
+ if (impl->location_mode == LOCATION_MODE_FILENAME_ENTRY)
+ gtk_widget_show (impl->location_entry_box);
+ }
+
+ impl->operation_mode = OPERATION_MODE_BROWSE;
+
+ file_list_set_sort_column_ids (impl);
+}
+
+/* Sort callback from the path column */
+static gint
+search_column_path_sort_func (GtkTreeModel *model,
+ GtkTreeIter *a,
+ GtkTreeIter *b,
+ gpointer user_data)
+{
+ GtkFileChooserDefault *impl = user_data;
+ GtkTreeIter child_a, child_b;
+ const char *collation_key_a, *collation_key_b;
+ gboolean is_folder_a, is_folder_b;
+
+ gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), &child_a, a);
+ gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), &child_b, b);
+
+ gtk_tree_model_get (GTK_TREE_MODEL (impl->search_model), &child_a,
+ SEARCH_MODEL_COL_IS_FOLDER, &is_folder_a,
+ SEARCH_MODEL_COL_COLLATION_KEY, &collation_key_a,
+ -1);
+ gtk_tree_model_get (GTK_TREE_MODEL (impl->search_model), &child_b,
+ SEARCH_MODEL_COL_IS_FOLDER, &is_folder_b,
+ SEARCH_MODEL_COL_COLLATION_KEY, &collation_key_b,
+ -1);
+
+ if (!collation_key_a)
+ return 1;
+
+ if (!collation_key_b)
+ return -1;
+
+ /* always show folders first */
+ if (is_folder_a != is_folder_b)
+ return is_folder_a ? 1 : -1;
+
+ return strcmp (collation_key_a, collation_key_b);
+}
+
+/* Sort callback from the modification time column */
+static gint
+search_column_mtime_sort_func (GtkTreeModel *model,
+ GtkTreeIter *a,
+ GtkTreeIter *b,
+ gpointer user_data)
+{
+ GtkFileChooserDefault *impl = user_data;
+ GtkTreeIter child_a, child_b;
+ const struct stat *statbuf_a, *statbuf_b;
+ gboolean is_folder_a, is_folder_b;
+
+ gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), &child_a, a);
+ gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), &child_b, b);
+
+ /* Note that although we store a whole struct stat in the model, we only
+ * compare the mtime here. If we add another column relative to a struct stat
+ * (e.g. a file size column), we'll want another sort callback similar to this
+ * one as well.
+ */
+ gtk_tree_model_get (GTK_TREE_MODEL (impl->search_model), &child_a,
+ SEARCH_MODEL_COL_IS_FOLDER, &is_folder_a,
+ SEARCH_MODEL_COL_STAT, &statbuf_a,
+ -1);
+ gtk_tree_model_get (GTK_TREE_MODEL (impl->search_model), &child_b,
+ SEARCH_MODEL_COL_IS_FOLDER, &is_folder_b,
+ SEARCH_MODEL_COL_STAT, &statbuf_b,
+ -1);
+
+ if (!statbuf_a)
+ return 1;
+
+ if (!statbuf_b)
+ return -1;
+
+ if (is_folder_a != is_folder_b)
+ return is_folder_a ? 1 : -1;
+
+ if (statbuf_a->st_mtime < statbuf_b->st_mtime)
+ return -1;
+ else if (statbuf_a->st_mtime > statbuf_b->st_mtime)
+ return 1;
+ else
+ return 0;
+}
+
+static gboolean
+search_get_is_filtered (GtkFileChooserDefault *impl,
+ const GtkFilePath *path,
+ const gchar *display_name,
+ const gchar *mime_type)
+{
+ GtkFileFilterInfo filter_info;
+ GtkFileFilterFlags needed;
+ gboolean result;
+
+ if (!impl->current_filter)
+ return FALSE;
+
+ filter_info.contains = GTK_FILE_FILTER_DISPLAY_NAME | GTK_FILE_FILTER_MIME_TYPE;
+ needed = gtk_file_filter_get_needed (impl->current_filter);
+
+ filter_info.display_name = display_name;
+ filter_info.mime_type = mime_type;
+
+ if (needed & GTK_FILE_FILTER_FILENAME)
+ {
+ filter_info.filename = gtk_file_system_path_to_filename (impl->file_system, path);
+ if (filter_info.filename)
+ filter_info.contains |= GTK_FILE_FILTER_FILENAME;
+ }
+ else
+ filter_info.filename = NULL;
+
+ if (needed & GTK_FILE_FILTER_URI)
+ {
+ filter_info.uri = gtk_file_system_path_to_uri (impl->file_system, path);
+ if (filter_info.uri)
+ filter_info.contains |= GTK_FILE_FILTER_URI;
+ }
+ else
+ filter_info.uri = NULL;
+
+ result = gtk_file_filter_filter (impl->current_filter, &filter_info);
+
+ if (filter_info.filename)
+ g_free ((gchar *) filter_info.filename);
+ if (filter_info.uri)
+ g_free ((gchar *) filter_info.uri);
+
+ return !result;
+
+}
+
+/* Visibility function for the recent filter model */
+static gboolean
+search_model_visible_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ GtkFileChooserDefault *impl = user_data;
+ GtkFilePath *file_path;
+ gchar *display_name, *mime_type;
+ gboolean is_folder;
+
+ if (!impl->current_filter)
+ return TRUE;
+
+ gtk_tree_model_get (model, iter,
+ SEARCH_MODEL_COL_PATH, &file_path,
+ SEARCH_MODEL_COL_IS_FOLDER, &is_folder,
+ SEARCH_MODEL_COL_DISPLAY_NAME, &display_name,
+ SEARCH_MODEL_COL_MIME_TYPE, &mime_type,
+ -1);
+
+ if (!display_name)
+ return TRUE;
+
+ if (is_folder)
+ return TRUE;
+
+ return !search_get_is_filtered (impl, file_path, display_name, mime_type);
+}
+
+/* Creates the search_model and puts it in the tree view */
+static void
+search_setup_model (GtkFileChooserDefault *impl)
+{
+ g_assert (impl->search_model == NULL);
+ g_assert (impl->search_model_filter == NULL);
+ g_assert (impl->search_model_sort == NULL);
+
+ /* We store these columns in the search model:
+ *
+ * SEARCH_MODEL_COL_PATH - a GtkFilePath for the hit's URI, stored as a
+ * pointer not as a GTK_TYPE_FILE_PATH
+ * SEARCH_MODEL_COL_DISPLAY_NAME - a string with the display name, stored
+ * as a pointer not as a G_TYPE_STRING
+ * SEARCH_MODEL_COL_COLLATION_KEY - collation key for the filename, stored
+ * as a pointer not as a G_TYPE_STRING
+ * SEARCH_MODEL_COL_STAT - pointer to a struct stat
+ * SEARCH_MODEL_COL_HANDLE - handle used when getting the hit's info
+ * SEARCH_MODEL_COL_PIXBUF - GdkPixbuf for the hit's icon
+ * SEARCH_MODEL_COL_MIME_TYPE - a string with the hit's MIME type
+ * SEARCH_MODEL_COL_IS_FOLDER - a boolean flag for folders
+ *
+ * Keep this in sync with the enumeration defined near the beginning
+ * of this file.
+ */
+ impl->search_model = gtk_list_store_new (SEARCH_MODEL_COL_NUM_COLUMNS,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER,
+ GDK_TYPE_PIXBUF,
+ G_TYPE_POINTER,
+ G_TYPE_BOOLEAN);
+
+ impl->search_model_filter =
+ GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (GTK_TREE_MODEL (impl->search_model), NULL));
+ gtk_tree_model_filter_set_visible_func (impl->search_model_filter,
+ search_model_visible_func,
+ impl, NULL);
+
+ impl->search_model_sort =
+ GTK_TREE_MODEL_SORT (search_model_sort_new (impl, GTK_TREE_MODEL (impl->search_model_filter)));
+ gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (impl->search_model_sort),
+ SEARCH_MODEL_COL_PATH,
+ search_column_path_sort_func,
+ impl, NULL);
+ gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (impl->search_model_sort),
+ SEARCH_MODEL_COL_STAT,
+ search_column_mtime_sort_func,
+ impl, NULL);
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (impl->search_model_sort),
+ SEARCH_MODEL_COL_STAT,
+ GTK_SORT_DESCENDING);
+
+ /* 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 (impl->browse_files_tree_view),
+ GTK_TREE_MODEL (impl->search_model_sort));
+}
+
+static void
+search_get_valid_child_iter (GtkFileChooserDefault *impl,
+ GtkTreeIter *child_iter,
+ GtkTreeIter *iter)
+{
+ GtkTreeIter middle;
+
+ if (!impl->search_model)
+ return;
+
+ if (!impl->search_model_filter || !impl->search_model_sort)
+ return;
+
+ /* pass 1: get the iterator in the filter model */
+ gtk_tree_model_sort_convert_iter_to_child_iter (impl->search_model_sort,
+ &middle, iter);
+
+ /* pass 2: get the iterator in the real model */
+ gtk_tree_model_filter_convert_iter_to_child_iter (impl->search_model_filter,
+ child_iter, &middle);
+}
+
+/* Creates a new query with the specified text and launches it */
+static void
+search_start_query (GtkFileChooserDefault *impl,
+ const gchar *query_text)
+{
+ search_stop_searching (impl, FALSE);
+ search_clear_model (impl, TRUE);
+ search_setup_model (impl);
+ set_busy_cursor (impl, TRUE);
+
+ if (impl->search_engine == NULL)
+ impl->search_engine = _gtk_search_engine_new ();
+
+ if (!impl->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 (!impl->search_query)
+ {
+ impl->search_query = _gtk_query_new ();
+ _gtk_query_set_text (impl->search_query, query_text);
+ }
+
+ _gtk_search_engine_set_query (impl->search_engine, impl->search_query);
+
+ g_signal_connect (impl->search_engine, "hits-added",
+ G_CALLBACK (search_engine_hits_added_cb), impl);
+ g_signal_connect (impl->search_engine, "finished",
+ G_CALLBACK (search_engine_finished_cb), impl);
+ g_signal_connect (impl->search_engine, "error",
+ G_CALLBACK (search_engine_error_cb), impl);
+
+ _gtk_search_engine_start (impl->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;
+ const char *text;
+
+ impl = GTK_FILE_CHOOSER_DEFAULT (data);
+
+ text = gtk_entry_get_text (GTK_ENTRY (impl->search_entry));
+ if (strlen (text) == 0)
+ return;
+
+ /* reset any existing query object */
+ if (impl->search_query)
+ {
+ g_object_unref (impl->search_query);
+ impl->search_query = NULL;
+ }
+
+ search_start_query (impl, text);
+}
+
+/* Hides the path bar and creates the search entry */
+static void
+search_setup_widgets (GtkFileChooserDefault *impl)
+{
+ GtkWidget *label;
+
+ impl->search_hbox = gtk_hbox_new (FALSE, 12);
+
+ /* Label */
+
+ label = gtk_label_new_with_mnemonic (_("_Search:"));
+ gtk_box_pack_start (GTK_BOX (impl->search_hbox), label, FALSE, FALSE, 0);
+
+ /* Entry */
+
+ impl->search_entry = gtk_entry_new ();
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), impl->search_entry);
+ g_signal_connect (impl->search_entry, "activate",
+ G_CALLBACK (search_entry_activate_cb),
+ impl);
+ gtk_box_pack_start (GTK_BOX (impl->search_hbox), impl->search_entry, TRUE, TRUE, 0);
+
+ /* if there already is a query, restart it */
+ if (impl->search_query)
+ {
+ gchar *query = _gtk_query_get_text (impl->search_query);
+
+ if (query)
+ {
+ gtk_entry_set_text (GTK_ENTRY (impl->search_entry), query);
+ search_start_query (impl, query);
+
+ g_free (query);
+ }
+ else
+ {
+ g_object_unref (impl->search_query);
+ impl->search_query = NULL;
+ }
+ }
+
+ gtk_widget_hide (impl->browse_path_bar);
+ gtk_widget_hide (impl->browse_new_folder_button);
+
+ /* Box for search widgets */
+ gtk_box_pack_start (GTK_BOX (impl->browse_path_bar_hbox), impl->search_hbox, TRUE, TRUE, 0);
+ gtk_widget_show_all (impl->search_hbox);
+
+ /* Hide the location widgets temporarily */
+
+ if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
+ impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
+ {
+ gtk_widget_hide (impl->location_button);
+ gtk_widget_hide (impl->location_entry_box);
+ }
+
+ gtk_widget_grab_focus (impl->search_entry);
+
+ /* FMQ: hide the filter combo? */
+}
+
+/* Main entry point to the searching functions; this gets called when the user
+ * activates the Search shortcut.
+ */
+static void
+search_activate (GtkFileChooserDefault *impl)
+{
+ OperationMode previous_mode;
+
+ if (impl->operation_mode == OPERATION_MODE_SEARCH)
+ {
+ gtk_widget_grab_focus (impl->search_entry);
+ return;
+ }
+
+ previous_mode = impl->operation_mode;
+ impl->operation_mode = OPERATION_MODE_SEARCH;
+
+ switch (previous_mode)
+ {
+ case OPERATION_MODE_RECENT:
+ recent_stop_loading (impl);
+ recent_clear_model (impl, TRUE);
+ break;
+
+ case OPERATION_MODE_BROWSE:
+ stop_loading_and_clear_list_model (impl);
+ break;
+
+ case OPERATION_MODE_SEARCH:
+ g_assert_not_reached ();
+ break;
+ }
+
+ g_assert (impl->search_hbox == NULL);
+ g_assert (impl->search_entry == NULL);
+ g_assert (impl->search_model == NULL);
+ g_assert (impl->search_model_filter == NULL);
+
+ search_setup_widgets (impl);
+ file_list_set_sort_column_ids (impl);
+}
+
+/*
+ * Recent files support
+ */
+
+/* Frees the data in the recent_model */
+static void
+recent_clear_model (GtkFileChooserDefault *impl,
+ gboolean remove_from_treeview)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ if (!impl->recent_model)
+ return;
+
+ model = GTK_TREE_MODEL (impl->recent_model);
+
+ if (remove_from_treeview)
+ gtk_tree_view_set_model (GTK_TREE_VIEW (impl->browse_files_tree_view), NULL);
+
+ if (gtk_tree_model_get_iter_first (model, &iter))
+ {
+ do
+ {
+ GtkFilePath *file_path;
+ GtkFileSystemHandle *handle;
+ GtkRecentInfo *recent_info;
+ gchar *display_name;
+
+ gtk_tree_model_get (model, &iter,
+ RECENT_MODEL_COL_DISPLAY_NAME, &display_name,
+ RECENT_MODEL_COL_PATH, &file_path,
+ RECENT_MODEL_COL_HANDLE, &handle,
+ RECENT_MODEL_COL_INFO, &recent_info,
+ -1);
+
+ if (handle)
+ gtk_file_system_cancel_operation (handle);
+
+ gtk_file_path_free (file_path);
+ gtk_recent_info_unref (recent_info);
+ g_free (display_name);
+ }
+ while (gtk_tree_model_iter_next (model, &iter));
+ }
+
+ g_object_unref (impl->recent_model);
+ impl->recent_model = NULL;
+
+ g_object_unref (impl->recent_model_filter);
+ impl->recent_model_filter = NULL;
+
+ g_object_unref (impl->recent_model_sort);
+ impl->recent_model_sort = NULL;
+}
+
+/* Stops any ongoing loading of the recent files list; does
+ * not touch the recent_model
+ */
+static void
+recent_stop_loading (GtkFileChooserDefault *impl)
+{
+ if (impl->load_recent_id)
+ {
+ g_source_remove (impl->load_recent_id);
+ impl->load_recent_id = 0;
+ }
+}
+
+/* Stops any pending load, clears the file list, and switches
+ * back to OPERATION_MODE_BROWSE
+ */
+static void
+recent_switch_to_browse_mode (GtkFileChooserDefault *impl)
+{
+ g_assert (impl->operation_mode != OPERATION_MODE_BROWSE);
+
+ recent_stop_loading (impl);
+ recent_clear_model (impl, TRUE);
+
+ gtk_widget_show (impl->browse_path_bar);
+ gtk_widget_show (impl->browse_new_folder_button);
+
+ if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
+ impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
+ {
+ gtk_widget_show (impl->location_button);
+
+ if (impl->location_mode == LOCATION_MODE_FILENAME_ENTRY)
+ gtk_widget_show (impl->location_entry_box);
+ }
+
+ impl->operation_mode = OPERATION_MODE_BROWSE;
+
+ file_list_set_sort_column_ids (impl);
+}
+
+/* Sort callback from the modification time column */
+static gint
+recent_column_mtime_sort_func (GtkTreeModel *model,
+ GtkTreeIter *a,
+ GtkTreeIter *b,
+ gpointer user_data)
+{
+ GtkFileChooserDefault *impl = user_data;
+ GtkTreeIter child_a, child_b;
+ GtkRecentInfo *info_a, *info_b;
+ gboolean is_folder_a, is_folder_b;
+
+ gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), &child_a, a);
+ gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), &child_b, b);
+
+ gtk_tree_model_get (GTK_TREE_MODEL (impl->recent_model), &child_a,
+ RECENT_MODEL_COL_IS_FOLDER, &is_folder_a,
+ RECENT_MODEL_COL_INFO, &info_a,
+ -1);
+ gtk_tree_model_get (GTK_TREE_MODEL (impl->recent_model), &child_b,
+ RECENT_MODEL_COL_IS_FOLDER, &is_folder_b,
+ RECENT_MODEL_COL_INFO, &info_b,
+ -1);
+
+ if (!info_a)
+ return 1;
+
+ if (!info_b)
+ return -1;
+
+ /* folders always go first */
+ if (is_folder_a != is_folder_b)
+ return is_folder_a ? 1 : -1;
+
+ if (gtk_recent_info_get_modified (info_a) < gtk_recent_info_get_modified (info_b))
+ return -1;
+ else if (gtk_recent_info_get_modified (info_a) > gtk_recent_info_get_modified (info_b))
+ return 1;
+ else
+ return 0;
+}
+
+static gint
+recent_column_path_sort_func (GtkTreeModel *model,
+ GtkTreeIter *a,
+ GtkTreeIter *b,
+ gpointer user_data)
+{
+ GtkFileChooserDefault *impl = user_data;
+ GtkTreeIter child_a, child_b;
+ gboolean is_folder_a, is_folder_b;
+ gchar *name_a, *name_b;
+
+ gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), &child_a, a);
+ gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), &child_b, b);
+
+ gtk_tree_model_get (GTK_TREE_MODEL (impl->recent_model), &child_a,
+ RECENT_MODEL_COL_IS_FOLDER, &is_folder_a,
+ RECENT_MODEL_COL_DISPLAY_NAME, &name_a,
+ -1);
+ gtk_tree_model_get (GTK_TREE_MODEL (impl->recent_model), &child_b,
+ RECENT_MODEL_COL_IS_FOLDER, &is_folder_b,
+ RECENT_MODEL_COL_DISPLAY_NAME, &name_b,
+ -1);
+
+ if (!name_a)
+ return 1;
+
+ if (!name_b)
+ return -1;
+
+ if (is_folder_a != is_folder_b)
+ return is_folder_a ? 1 : -1;
+
+ return strcmp (name_a, name_b);
+}
+
+static gboolean
+recent_get_is_filtered (GtkFileChooserDefault *impl,
+ const GtkFilePath *path,
+ GtkRecentInfo *recent_info)
+{
+ GtkFileFilterInfo filter_info;
+ GtkFileFilterFlags needed;
+ gboolean result;
+
+ if (!impl->current_filter)
+ return FALSE;
+
+ filter_info.contains = GTK_FILE_FILTER_DISPLAY_NAME | GTK_FILE_FILTER_MIME_TYPE;
+ needed = gtk_file_filter_get_needed (impl->current_filter);
+
+ filter_info.display_name = gtk_recent_info_get_display_name (recent_info);
+ filter_info.mime_type = gtk_recent_info_get_mime_type (recent_info);
+
+ if (needed & GTK_FILE_FILTER_FILENAME)
+ {
+ filter_info.filename = gtk_file_system_path_to_filename (impl->file_system, path);
+ if (filter_info.filename)
+ filter_info.contains |= GTK_FILE_FILTER_FILENAME;
+ }
+ else
+ filter_info.filename = NULL;
+
+ if (needed & GTK_FILE_FILTER_URI)
+ {
+ filter_info.uri = gtk_file_system_path_to_uri (impl->file_system, path);
+ if (filter_info.uri)
+ filter_info.contains |= GTK_FILE_FILTER_URI;
+ }
+ else
+ filter_info.uri = NULL;
+
+ result = gtk_file_filter_filter (impl->current_filter, &filter_info);
+
+ if (filter_info.filename)
+ g_free ((gchar *) filter_info.filename);
+ if (filter_info.uri)
+ g_free ((gchar *) filter_info.uri);
+
+ return !result;
+}
+
+/* Visibility function for the recent filter model */
+static gboolean
+recent_model_visible_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ GtkFileChooserDefault *impl = user_data;
+ GtkFilePath *file_path;
+ GtkRecentInfo *recent_info;
+ gboolean is_folder;
+
+ if (!impl->current_filter)
+ return TRUE;
+
+ gtk_tree_model_get (model, iter,
+ RECENT_MODEL_COL_INFO, &recent_info,
+ RECENT_MODEL_COL_PATH, &file_path,
+ RECENT_MODEL_COL_IS_FOLDER, &is_folder,
+ -1);
+
+ if (!recent_info)
+ return TRUE;
+
+ if (is_folder)
+ return TRUE;
+
+ return !recent_get_is_filtered (impl, file_path, recent_info);
+}
+
+static void
+recent_setup_model (GtkFileChooserDefault *impl)
+{
+ g_assert (impl->recent_model == NULL);
+ g_assert (impl->recent_model_filter == NULL);
+ g_assert (impl->recent_model_sort == NULL);
+
+ /* We store these columns in the search model:
+ *
+ * RECENT_MODEL_COL_PATH - a pointer to GtkFilePath for the hit's URI,
+ * stored as a pointer and not as a GTK_TYPE_FILE_PATH;
+ * RECENT_MODEL_COL_DISPLAY_NAME - a string with the display name,
+ * stored as a pointer and not as a G_TYPE_STRING;
+ * RECENT_MODEL_COL_INFO - GtkRecentInfo, stored as a pointer and not
+ * as a GTK_TYPE_RECENT_INFO;
+ * RECENT_MODEL_COL_IS_FOLDER - boolean flag;
+ * RECENT_MODEL_COL_HANDLE - GtkFileSystemHandle, stored as a pointer
+ * and not as a GTK_TYPE_FILE_SYSTEM_HANDLE;
+ *
+ * Keep this in sync with the enumeration defined near the beginning of
+ * this file.
+ */
+ impl->recent_model = gtk_list_store_new (RECENT_MODEL_COL_NUM_COLUMNS,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER,
+ G_TYPE_BOOLEAN,
+ G_TYPE_POINTER);
+
+ impl->recent_model_filter =
+ GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (GTK_TREE_MODEL (impl->recent_model), NULL));
+ gtk_tree_model_filter_set_visible_func (impl->recent_model_filter,
+ recent_model_visible_func,
+ impl,
+ NULL);
+
+ /* this is the model that will actually be added to
+ * the browse_files_tree_view widget; remember: we are
+ * stuffing the real model into a filter model and then
+ * into a sort model; this means we'll have to translate
+ * the child iterator *twice* to get from a path or an
+ * iterator coming from the tree view widget to the
+ * real data inside the model.
+ */
+ impl->recent_model_sort =
+ GTK_TREE_MODEL_SORT (recent_model_sort_new (impl, GTK_TREE_MODEL (impl->recent_model_filter)));
+ gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (impl->recent_model_sort),
+ RECENT_MODEL_COL_PATH,
+ recent_column_path_sort_func,
+ impl, NULL);
+ gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (impl->recent_model_sort),
+ RECENT_MODEL_COL_INFO,
+ recent_column_mtime_sort_func,
+ impl, NULL);
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (impl->recent_model_sort),
+ RECENT_MODEL_COL_INFO,
+ GTK_SORT_DESCENDING);
+}
+
+typedef struct
+{
+ GtkFileChooserDefault *impl;
+ GList *items;
+ gint n_items;
+ gint n_loaded_items;
+ guint needs_sorting : 1;
+} RecentLoadData;
+
+static void
+recent_idle_cleanup (gpointer data)
+{
+ RecentLoadData *load_data = data;
+ GtkFileChooserDefault *impl = load_data->impl;
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (impl->browse_files_tree_view),
+ GTK_TREE_MODEL (impl->recent_model_sort));
+
+ set_busy_cursor (impl, FALSE);
+
+ impl->load_recent_id = 0;
+
+ if (load_data->items)
+ {
+ g_list_foreach (load_data->items, (GFunc) gtk_recent_info_unref, NULL);
+ g_list_free (load_data->items);
+ }
+
+ g_free (load_data);
+}
+
+struct RecentItemInsertRequest
+{
+ GtkFileChooserDefault *impl;
+ GtkFilePath *path;
+ GtkTreeRowReference *row_ref;
+};
+
+static void
+recent_item_get_info_cb (GtkFileSystemHandle *handle,
+ const GtkFileInfo *info,
+ const GError *error,
+ gpointer data)
+{
+ gboolean cancelled = handle->cancelled;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ GtkFileSystemHandle *model_handle;
+ gboolean is_folder = FALSE;
+ struct RecentItemInsertRequest *request = data;
+
+ if (!request->impl->recent_model)
+ goto out;
+
+ path = gtk_tree_row_reference_get_path (request->row_ref);
+ if (!path)
+ goto out;
+
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (request->impl->recent_model),
+ &iter, path);
+ gtk_tree_path_free (path);
+
+ gtk_tree_model_get (GTK_TREE_MODEL (request->impl->recent_model), &iter,
+ RECENT_MODEL_COL_HANDLE, &model_handle,
+ -1);
+ if (handle != model_handle)
+ goto out;
+
+ gtk_list_store_set (request->impl->recent_model, &iter,
+ RECENT_MODEL_COL_HANDLE, NULL,
+ -1);
+
+ if (cancelled)
+ goto out;
+
+ if (!info)
+ {
+ gtk_list_store_remove (request->impl->recent_model, &iter);
+ goto out;
+ }
+
+ is_folder = gtk_file_info_get_is_folder (info);
+
+ gtk_list_store_set (request->impl->recent_model, &iter,
+ RECENT_MODEL_COL_IS_FOLDER, is_folder,
+ -1);
+
+out:
+ g_object_unref (request->impl);
+ gtk_file_path_free (request->path);
+ gtk_tree_row_reference_free (request->row_ref);
+ g_free (request);
+
+ g_object_unref (handle);
+}
+
+static gint
+recent_sort_mru (gconstpointer a,
+ gconstpointer b)
+{
+ GtkRecentInfo *info_a = (GtkRecentInfo *) a;
+ GtkRecentInfo *info_b = (GtkRecentInfo *) b;
+
+ return (gtk_recent_info_get_modified (info_b) - gtk_recent_info_get_modified (info_a));
+}
+
+static gint
+get_recent_files_limit (GtkWidget *widget)
+{
+ GtkSettings *settings;
+ gint limit;
+
+ if (gtk_widget_has_screen (widget))
+ settings = gtk_settings_get_for_screen (gtk_widget_get_screen (widget));
+ else
+ settings = gtk_settings_get_default ();
+
+ g_object_get (G_OBJECT (settings), "gtk-recent-files-limit", &limit, NULL);
+
+ return limit;
+}
+
+static gboolean
+recent_idle_load (gpointer data)
+{
+ RecentLoadData *load_data = data;
+ GtkFileChooserDefault *impl = load_data->impl;
+ GtkTreeIter iter;
+ GtkTreePath *p;
+ GtkRecentInfo *info;
+ const gchar *uri, *display_name;
+ GtkFilePath *path;
+ GtkFileSystemHandle *handle;
+ struct RecentItemInsertRequest *request;
+
+ if (!impl->recent_manager)
+ return FALSE;
+
+ /* first iteration: load all the items */
+ if (!load_data->items)
+ {
+ load_data->items = gtk_recent_manager_get_items (impl->recent_manager);
+ if (!load_data->items)
+ return FALSE;
+
+ load_data->needs_sorting = TRUE;
+
+ return TRUE;
+ }
+
+ /* second iteration: preliminary MRU sorting and clamping */
+ if (load_data->needs_sorting)
+ {
+ gint limit;
+
+ load_data->items = g_list_sort (load_data->items, recent_sort_mru);
+ load_data->n_items = g_list_length (load_data->items);
+
+ limit = get_recent_files_limit (GTK_WIDGET (impl));
+
+ if (limit != -1 && (load_data->n_items > limit))
+ {
+ GList *clamp, *l;
+
+ clamp = g_list_nth (load_data->items, limit - 1);
+ if (G_LIKELY (clamp))
+ {
+ l = clamp->next;
+ clamp->next = NULL;
+
+ g_list_foreach (l, (GFunc) gtk_recent_info_unref, NULL);
+ g_list_free (l);
+
+ load_data->n_items = limit;
+ }
+ }
+
+ load_data->n_loaded_items = 0;
+ load_data->needs_sorting = FALSE;
+
+ return TRUE;
+ }
+
+ info = g_list_nth_data (load_data->items, load_data->n_loaded_items);
+ g_assert (info != NULL);
+
+ uri = gtk_recent_info_get_uri (info);
+ display_name = gtk_recent_info_get_display_name (info);
+ path = gtk_file_system_uri_to_path (impl->file_system, uri);
+ if (!path)
+ goto load_next;
+
+ gtk_list_store_append (impl->recent_model, &iter);
+ p = gtk_tree_model_get_path (GTK_TREE_MODEL (impl->recent_model), &iter);
+
+ request = g_new0 (struct RecentItemInsertRequest, 1);
+ request->impl = g_object_ref (impl);
+ request->path = gtk_file_path_copy (path);
+ request->row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (impl->recent_model), p);
+ gtk_tree_path_free (p);
+
+ handle = gtk_file_system_get_info (impl->file_system, path,
+ GTK_FILE_INFO_IS_FOLDER,
+ recent_item_get_info_cb,
+ request);
+
+ gtk_list_store_set (impl->recent_model, &iter,
+ RECENT_MODEL_COL_PATH, path,
+ RECENT_MODEL_COL_DISPLAY_NAME, g_strdup (display_name),
+ RECENT_MODEL_COL_INFO, gtk_recent_info_ref (info),
+ RECENT_MODEL_COL_HANDLE, handle,
+ -1);
+
+load_next:
+
+ load_data->n_loaded_items += 1;
+
+ /* finished loading items */
+ if (load_data->n_loaded_items == load_data->n_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;
+ }
+
+ return TRUE;
+}
+
+static void
+recent_start_loading (GtkFileChooserDefault *impl)
+{
+ RecentLoadData *load_data;
+
+ recent_stop_loading (impl);
+ recent_clear_model (impl, TRUE);
+ recent_setup_model (impl);
+ set_busy_cursor (impl, TRUE);
+
+ if (!impl->recent_manager)
+ recent_manager_update (impl);
+
+ g_assert (impl->load_recent_id == 0);
+
+ load_data = g_new (RecentLoadData, 1);
+ load_data->impl = impl;
+ load_data->items = NULL;
+ load_data->n_items = 0;
+ load_data->n_loaded_items = 0;
+ load_data->needs_sorting = TRUE;
+
+ /* begin lazy loading the recent files into the model */
+ impl->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_path_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ GSList **list;
+ const GtkFilePath *file_path;
+ GtkFilePath *file_path_copy;
+
+ list = data;
+
+ gtk_tree_model_get (model, iter, RECENT_MODEL_COL_PATH, &file_path, -1);
+ file_path_copy = gtk_file_path_copy (file_path);
+ *list = g_slist_prepend (*list, file_path_copy);
+}
+
+/* Constructs a list of the selected paths in recent files mode */
+static GSList *
+recent_get_selected_paths (GtkFileChooserDefault *impl)
+{
+ GSList *result;
+ GtkTreeSelection *selection;
+
+ result = NULL;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view));
+ gtk_tree_selection_selected_foreach (selection, recent_selected_foreach_get_path_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)
+{
+ GtkTreeSelection *selection;
+
+ g_assert (impl->operation_mode == OPERATION_MODE_RECENT);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view));
+ return (gtk_tree_selection_count_selected_rows (selection) != 0);
+}
+
+/* Hide the location widgets temporarily */
+static void
+recent_hide_entry (GtkFileChooserDefault *impl)
+{
+ gtk_widget_hide (impl->browse_path_bar);
+ gtk_widget_hide (impl->browse_new_folder_button);
+
+ if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
+ impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
+ {
+ gtk_widget_hide (impl->location_button);
+ gtk_widget_hide (impl->location_entry_box);
+ }
+}
+
+/* Main entry point to the recent files functions; this gets called when
+ * the user activates the Recently Used shortcut.
+ */
+static void
+recent_activate (GtkFileChooserDefault *impl)
+{
+ OperationMode previous_mode;
+
+ if (impl->operation_mode == OPERATION_MODE_RECENT)
+ return;
+
+ previous_mode = impl->operation_mode;
+ impl->operation_mode = OPERATION_MODE_RECENT;
+
+ switch (previous_mode)
+ {
+ case OPERATION_MODE_SEARCH:
+ search_stop_searching (impl, FALSE);
+ search_clear_model (impl, TRUE);
+
+ gtk_widget_destroy (impl->search_hbox);
+ impl->search_hbox = NULL;
+ impl->search_entry = NULL;
+ break;
+
+ case OPERATION_MODE_BROWSE:
+ stop_loading_and_clear_list_model (impl);
+ break;
+
+ case OPERATION_MODE_RECENT:
+ g_assert_not_reached ();
+ break;
+ }
+
+ recent_hide_entry (impl);
+ file_list_set_sort_column_ids (impl);
+ recent_start_loading (impl);
+}
+
+/* convert an iterator coming from the model bound to
+ * browse_files_tree_view to an interator inside the
+ * real recent_model
+ */
+static void
+recent_get_valid_child_iter (GtkFileChooserDefault *impl,
+ GtkTreeIter *child_iter,
+ GtkTreeIter *iter)
+{
+ GtkTreeIter middle;
+
+ if (!impl->recent_model)
+ return;
+
+ if (!impl->recent_model_filter || !impl->recent_model_sort)
+ return;
+
+ /* pass 1: get the iterator in the filter model */
+ gtk_tree_model_sort_convert_iter_to_child_iter (impl->recent_model_sort,
+ &middle, iter);
+
+ /* pass 2: get the iterator in the real model */
+ gtk_tree_model_filter_convert_iter_to_child_iter (impl->recent_model_filter,
+ child_iter,
+ &middle);
+}
+
+
+static void
+set_current_filter (GtkFileChooserDefault *impl,
+ GtkFileFilter *filter)
+{
+ if (impl->current_filter != filter)
+ {
+ int filter_index;
+
+ /* NULL filters are allowed to reset to non-filtered status
+ */
+ filter_index = g_slist_index (impl->filters, filter);
+ if (impl->filters && filter && filter_index < 0)
+ return;
+
+ if (impl->current_filter)
+ g_object_unref (impl->current_filter);
+ impl->current_filter = filter;
+ if (impl->current_filter)
+ {
+ g_object_ref_sink (impl->current_filter);
+ }
+
+ if (impl->filters)
+ gtk_combo_box_set_active (GTK_COMBO_BOX (impl->filter_combo),
+ filter_index);
+
+ if (impl->browse_files_model)
+ install_list_model_filter (impl);
+
+ if (impl->search_model_filter)
+ gtk_tree_model_filter_refilter (impl->search_model_filter);
+
+ if (impl->recent_model_filter)
+ gtk_tree_model_filter_refilter (impl->recent_model_filter);
+
+ g_object_notify (G_OBJECT (impl), "filter");
+ }
+}
+
+static void
+filter_combo_changed (GtkComboBox *combo_box,
+ GtkFileChooserDefault *impl)
+{
+ gint new_index = gtk_combo_box_get_active (combo_box);
+ GtkFileFilter *new_filter = g_slist_nth_data (impl->filters, new_index);
+
+ set_current_filter (impl, new_filter);
+}
+
+static void
+check_preview_change (GtkFileChooserDefault *impl)
+{
+ GtkTreePath *cursor_path;
+ const GtkFilePath *new_path;
+ const char *new_display_name;
+
+ gtk_tree_view_get_cursor (GTK_TREE_VIEW (impl->browse_files_tree_view), &cursor_path, NULL);
+ new_path = NULL;
+ new_display_name = NULL;
+ if (cursor_path)
+ {
+ GtkTreeIter child_iter;
+
+ if (impl->operation_mode == OPERATION_MODE_BROWSE)
+ {
+ if (impl->sort_model)
+ {
+ GtkTreeIter iter;
+ const GtkFileInfo *new_info;
+
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (impl->sort_model), &iter, cursor_path);
+ gtk_tree_path_free (cursor_path);
+
+ gtk_tree_model_sort_convert_iter_to_child_iter (impl->sort_model, &child_iter, &iter);
+
+ new_path = _gtk_file_system_model_get_path (impl->browse_files_model, &child_iter);
+ new_info = _gtk_file_system_model_get_info (impl->browse_files_model, &child_iter);
+ if (new_info)
+ new_display_name = gtk_file_info_get_display_name (new_info);
+ }
+ }
+ else if (impl->operation_mode == OPERATION_MODE_SEARCH)
+ {
+ GtkTreeIter iter;
+
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (impl->search_model_sort),
+ &iter, cursor_path);
+ gtk_tree_path_free (cursor_path);
+
+ search_get_valid_child_iter (impl, &child_iter, &iter);
+ gtk_tree_model_get (GTK_TREE_MODEL (impl->search_model), &child_iter,
+ SEARCH_MODEL_COL_PATH, &new_path,
+ SEARCH_MODEL_COL_DISPLAY_NAME, &new_display_name,
+ -1);
+ }
+ else if (impl->operation_mode == OPERATION_MODE_RECENT)
+ {
+ GtkTreeIter iter;
+
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (impl->recent_model_sort),
+ &iter, cursor_path);
+ gtk_tree_path_free (cursor_path);
+
+ recent_get_valid_child_iter (impl, &child_iter, &iter);
+ gtk_tree_model_get (GTK_TREE_MODEL (impl->recent_model), &child_iter,
+ RECENT_MODEL_COL_PATH, &new_path,
+ RECENT_MODEL_COL_DISPLAY_NAME, &new_display_name,
+ -1);
+ }
+ }
+
+ if (new_path != impl->preview_path &&
+ !(new_path && impl->preview_path &&
+ gtk_file_path_compare (new_path, impl->preview_path) == 0))
+ {
+ if (impl->preview_path)
+ {
+ gtk_file_path_free (impl->preview_path);
+ g_free (impl->preview_display_name);
+ }
+
+ if (new_path)
+ {
+ impl->preview_path = gtk_file_path_copy (new_path);
+ impl->preview_display_name = g_strdup (new_display_name);
+ }
+ else
+ {
+ impl->preview_path = NULL;
+ impl->preview_display_name = NULL;
+ }
+
+ if (impl->use_preview_label && impl->preview_label)
+ gtk_label_set_text (GTK_LABEL (impl->preview_label), impl->preview_display_name);
+
+ g_signal_emit_by_name (impl, "update-preview");
+ }
+}
+
+static void
+shortcuts_activate_volume_mount_cb (GtkFileSystemHandle *handle,
+ GtkFileSystemVolume *volume,
+ const GError *error,
+ gpointer data)
+{
+ GtkFilePath *path;
+ gboolean cancelled = handle->cancelled;
+ GtkFileChooserDefault *impl = data;
+
+ if (handle != impl->shortcuts_activate_iter_handle)
+ goto out;
+
+ impl->shortcuts_activate_iter_handle = NULL;
+
+ set_busy_cursor (impl, FALSE);
+
+ if (cancelled)
+ goto out;
+
+ if (error)
+ {
+ char *msg;
+
+ msg = g_strdup_printf (_("Could not mount %s"),
+ gtk_file_system_volume_get_display_name (impl->file_system, volume));
+ error_message (impl, msg, error->message);
+ g_free (msg);
+
+ goto out;
+ }
+
+ path = gtk_file_system_volume_get_base_path (impl->file_system, volume);
+ if (path != NULL)
+ {
+ change_folder_and_display_error (impl, path, FALSE);
+ focus_browse_tree_view_if_possible (impl);
+
+ gtk_file_path_free (path);
+ }
+
+out:
+ g_object_unref (impl);
+ g_object_unref (handle);
+}
+
+
+/* Activates a volume by mounting it if necessary and then switching to its
+ * base path.
+ */
+static void
+shortcuts_activate_volume (GtkFileChooserDefault *impl,
+ GtkFileSystemVolume *volume)
+{
+ GtkFilePath *path;
+
+ switch (impl->operation_mode)
+ {
+ case OPERATION_MODE_BROWSE:
+ break;
+ case OPERATION_MODE_SEARCH:
+ search_switch_to_browse_mode (impl);
+ break;
+ case OPERATION_MODE_RECENT:
+ recent_switch_to_browse_mode (impl);
+ break;
+ }
+
+ /* We ref the file chooser since volume_mount() may run a main loop, and the
+ * user could close the file chooser window in the meantime.
+ */
+ g_object_ref (impl);
+
+ if (!gtk_file_system_volume_get_is_mounted (impl->file_system, volume))
+ {
+ set_busy_cursor (impl, TRUE);
+
+ impl->shortcuts_activate_iter_handle =
+ gtk_file_system_volume_mount (impl->file_system, volume,
+ shortcuts_activate_volume_mount_cb,
+ g_object_ref (impl));
+ }
+ else
+ {
+ path = gtk_file_system_volume_get_base_path (impl->file_system, volume);
+ if (path != NULL)
+ {
+ change_folder_and_display_error (impl, path, FALSE);
+ gtk_file_path_free (path);
+ }
+ }
+
+ g_object_unref (impl);
+}
+
+/* Opens the folder or volume at the specified iter in the shortcuts model */
+struct ShortcutsActivateData
+{
+ GtkFileChooserDefault *impl;
+ GtkFilePath *path;
+};
+
+static void
+shortcuts_activate_get_info_cb (GtkFileSystemHandle *handle,
+ const GtkFileInfo *info,
+ const GError *error,
+ gpointer user_data)
+{
+ gboolean cancelled = handle->cancelled;
+ struct ShortcutsActivateData *data = user_data;
+
+ if (handle != data->impl->shortcuts_activate_iter_handle)
+ goto out;
+
+ data->impl->shortcuts_activate_iter_handle = NULL;
+
+ if (cancelled)
+ goto out;
+
+ if (!error && gtk_file_info_get_is_folder (info))
+ {
+ change_folder_and_display_error (data->impl, data->path, FALSE);
+ focus_browse_tree_view_if_possible (data->impl);
+ }
+ else
+ gtk_file_chooser_default_select_path (GTK_FILE_CHOOSER (data->impl),
+ data->path,
+ NULL);
+
+out:
+ g_object_unref (data->impl);
+ gtk_file_path_free (data->path);
+ g_free (data);
+
+ g_object_unref (handle);
+}
+
+static void
+shortcuts_activate_iter (GtkFileChooserDefault *impl,
+ GtkTreeIter *iter)
+{
+ gpointer col_data;
+ ShortcutType shortcut_type;
+
+ if (impl->location_mode == LOCATION_MODE_FILENAME_ENTRY && impl->action != GTK_FILE_CHOOSER_ACTION_SAVE)
+ _gtk_file_chooser_entry_set_file_part (GTK_FILE_CHOOSER_ENTRY (impl->location_entry), "");