From c6ef4bf7173755c20364d7a29685406c11903c23 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Tue, 8 May 2007 17:03:42 +0100 Subject: Merge recent files support Conflicts: gtk/gtkfilechooserdefault.c Signed-off-by: Emmanuele Bassi --- gtk/gtkfilechooserdefault.c | 668 ++++++++++++++++++++++++++++++++++++++++--- gtk/gtkfilechooserprivate.h | 12 +- 2 files changed, 644 insertions(+), 36 deletions(-) diff --git a/gtk/gtkfilechooserdefault.c b/gtk/gtkfilechooserdefault.c index 65ceec0..db1101d 100644 --- a/gtk/gtkfilechooserdefault.c +++ b/gtk/gtkfilechooserdefault.c @@ -56,6 +56,8 @@ #include "gtkpathbar.h" #include "gtkprivate.h" #include "gtkradiobutton.h" +#include "gtkrecentfilter.h" +#include "gtkrecentmanager.h" #include "gtkscrolledwindow.h" #include "gtkseparatormenuitem.h" #include "gtksizegroup.h" @@ -165,6 +167,7 @@ enum { LOCATION_TOGGLE_POPUP, SHOW_HIDDEN, SEARCH_SHORTCUT, + RECENT_SHORTCUT, LAST_SIGNAL }; @@ -187,7 +190,8 @@ typedef enum { SHORTCUT_TYPE_PATH, SHORTCUT_TYPE_VOLUME, SHORTCUT_TYPE_SEPARATOR, - SHORTCUT_TYPE_SEARCH + SHORTCUT_TYPE_SEARCH, + SHORTCUT_TYPE_RECENT } ShortcutType; /* Column numbers for the file list */ @@ -213,6 +217,13 @@ enum { SEARCH_MODEL_COL_NUM_COLUMNS }; +enum { + RECENT_MODEL_COL_PATH, + RECENT_MODEL_COL_DISPLAY_NAME, + RECENT_MODEL_COL_INFO, + RECENT_MODEL_COL_NUM_COLUMNS +}; + /* Identifiers for target types */ enum { GTK_TREE_MODEL_ROW, @@ -261,6 +272,8 @@ search_is_possible (GtkFileChooserDefault *impl) typedef enum { SHORTCUTS_SEARCH, SHORTCUTS_SEARCH_SEPARATOR, + SHORTCUTS_RECENT, + SHORTCUTS_RECENT_SEPARATOR, SHORTCUTS_HOME, SHORTCUTS_DESKTOP, SHORTCUTS_VOLUMES, @@ -359,6 +372,7 @@ 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 set_current_filter (GtkFileChooserDefault *impl, @@ -466,6 +480,14 @@ static GSList *search_get_selected_paths (GtkFileChooserDefault *impl); static void search_entry_activate_cb (GtkEntry *entry, gpointer data); +static void recent_manager_update (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 void recent_switch_to_browse_mode (GtkFileChooserDefault *impl); +static GSList * recent_get_selected_paths (GtkFileChooserDefault *impl); + /* Drag and drop interface declarations */ @@ -607,6 +629,14 @@ _gtk_file_chooser_default_class_init (GtkFileChooserDefaultClass *class) NULL, NULL, _gtk_marshal_VOID__VOID, G_TYPE_NONE, 0); + signals[RECENT_SHORTCUT] = + _gtk_binding_signal_new ("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); @@ -677,6 +707,10 @@ _gtk_file_chooser_default_class_init (GtkFileChooserDefaultClass *class) GDK_s, GDK_MOD1_MASK, "search-shortcut", 0); + gtk_binding_entry_add_signal (binding_set, + GDK_r, GDK_MOD1_MASK, + "recent-shortcut", + 0); for (i = 0; i < 10; i++) gtk_binding_entry_add_signal (binding_set, @@ -909,7 +943,8 @@ gtk_file_chooser_default_finalize (GObject *object) if (impl->sort_model) g_object_unref (impl->sort_model); - search_clear_model (impl, TRUE); + search_clear_model (impl, FALSE); + recent_clear_model (impl, FALSE); g_free (impl->preview_display_name); @@ -1170,6 +1205,12 @@ render_search_icon (GtkFileChooserDefault *impl) return gtk_widget_render_icon (GTK_WIDGET (impl), GTK_STOCK_FIND, GTK_ICON_SIZE_MENU, NULL); } +static GdkPixbuf * +render_recent_icon (GtkFileChooserDefault *impl) +{ + return gtk_widget_render_icon (GTK_WIDGET (impl), GTK_STOCK_FILE, GTK_ICON_SIZE_MENU, NULL); +} + /* Re-reads all the icons for the shortcuts, used when the theme changes */ struct ReloadIconsData @@ -1316,7 +1357,11 @@ shortcuts_reload_icons (GtkFileChooserDefault *impl) else if (shortcut_type == SHORTCUT_TYPE_SEARCH) { pixbuf = render_search_icon (impl); - } + } + else if (shortcut_type == SHORTCUT_TYPE_RECENT) + { + pixbuf = render_recent_icon (impl); + } } } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (impl->shortcuts_model),&iter)); @@ -1775,6 +1820,30 @@ shortcuts_append_search (GtkFileChooserDefault *impl) impl->has_search = TRUE; } +static void +shortcuts_append_recent (GtkFileChooserDefault *impl) +{ + GdkPixbuf *pixbuf; + GtkTreeIter iter; + + pixbuf = render_recent_icon (impl); + + gtk_list_store_append (impl->shortcuts_model, &iter); + gtk_list_store_set (impl->shortcuts_model, &iter, + SHORTCUTS_COL_PIXBUF, pixbuf, + SHORTCUTS_COL_PIXBUF_VISIBLE, TRUE, + SHORTCUTS_COL_NAME, _("Recently Used"), + SHORTCUTS_COL_DATA, NULL, + SHORTCUTS_COL_TYPE, SHORTCUT_TYPE_RECENT, + SHORTCUTS_COL_REMOVABLE, FALSE, + -1); + + if (pixbuf) + g_object_unref (pixbuf); + + impl->has_recent = TRUE; +} + /* Appends an item for the user's home directory to the shortcuts model */ static void shortcuts_append_home (GtkFileChooserDefault *impl) @@ -1898,6 +1967,16 @@ shortcuts_get_index (GtkFileChooserDefault *impl, n += impl->has_search ? 1 : 0; + if (where == SHORTCUTS_RECENT) + goto out; + + n += impl->has_recent ? 1 : 0; + + if (where == SHORTCUTS_RECENT_SEPARATOR) + goto out; + + n += impl->has_recent ? 1 : 0; + if (where == SHORTCUTS_HOME) goto out; @@ -2020,7 +2099,8 @@ shortcuts_insert_separator (GtkFileChooserDefault *impl, GtkTreeIter iter; g_assert (where == SHORTCUTS_SEARCH_SEPARATOR || - where == SHORTCUTS_BOOKMARKS_SEPARATOR || + where == SHORTCUTS_RECENT_SEPARATOR || + where == SHORTCUTS_BOOKMARKS_SEPARATOR || where == SHORTCUTS_CURRENT_FOLDER_SEPARATOR); gtk_list_store_insert (impl->shortcuts_model, &iter, @@ -2235,6 +2315,12 @@ shortcuts_model_create (GtkFileChooserDefault *impl) shortcuts_append_search (impl); shortcuts_insert_separator (impl, SHORTCUTS_SEARCH_SEPARATOR); } + + if (impl->recent_manager) + { + shortcuts_append_recent (impl); + shortcuts_insert_separator (impl, SHORTCUTS_RECENT_SEPARATOR); + } if (impl->file_system) { @@ -2782,7 +2868,11 @@ bookmarks_check_add_sensitivity (GtkFileChooserDefault *impl) gboolean active; gchar *tip; - if (impl->operation_mode == OPERATION_MODE_SEARCH) + /* FIXME - Find a way to enable bookmarking items returned by + * a search query or inside the recently used files list + */ + if (impl->operation_mode == OPERATION_MODE_SEARCH || + impl->operation_mode == OPERATION_MODE_RECENT) { gtk_widget_set_sensitive (impl->browse_shortcuts_add_button, FALSE); @@ -4112,7 +4202,7 @@ file_list_update_popup_menu (GtkFileChooserDefault *impl) { file_list_build_popup_menu (impl); - /* FMQ: handle OPERATION_MODE_SEARCH */ + /* FIXME - handle OPERATION_MODE_SEARCH and OPERATION_MODE_RECENT */ /* The sensitivity of the Add to Bookmarks item is set in * bookmarks_check_add_sensitivity() @@ -4231,6 +4321,10 @@ file_list_set_sort_column_ids (GtkFileChooserDefault *impl) name_id = SEARCH_MODEL_COL_PATH; mtime_id = SEARCH_MODEL_COL_STAT; break; + case OPERATION_MODE_RECENT: + name_id = RECENT_MODEL_COL_PATH; + mtime_id = RECENT_MODEL_COL_INFO; + break; } gtk_tree_view_column_set_sort_column_id (impl->list_name_column, name_id); @@ -4511,6 +4605,19 @@ shortcuts_combo_filter_func (GtkTreeModel *model, retval = FALSE; } } + + if (impl->has_recent) + { + idx = shortcuts_get_index (impl, SHORTCUTS_RECENT); + if (idx == indices[0]) + retval = FALSE; + else + { + idx = shortcuts_get_index (impl, SHORTCUTS_RECENT_SEPARATOR); + if (idx == indices[0]) + retval = FALSE; + } + } gtk_tree_path_free (tree_path); @@ -4959,8 +5066,10 @@ gtk_file_chooser_default_constructor (GType type, gtk_widget_push_composite_child (); - /* Shortcuts model */ + /* Recent files manager */ + recent_manager_update (impl); + /* Shortcuts model */ shortcuts_model_create (impl); /* The browse widgets */ @@ -5486,6 +5595,7 @@ gtk_file_chooser_default_dispose (GObject *object) } search_stop_searching (impl, TRUE); + recent_stop_loading (impl); remove_settings_signal (impl, gtk_widget_get_screen (GTK_WIDGET (impl))); @@ -5619,6 +5729,24 @@ check_icon_theme (GtkFileChooserDefault *impl) } static void +recent_manager_update (GtkFileChooserDefault *impl) +{ + GtkRecentManager *manager; + + profile_start ("start", NULL); + + if (gtk_widget_has_screen (GTK_WIDGET (impl))) + manager = gtk_recent_manager_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (impl))); + else + manager = gtk_recent_manager_get_default (); + + if (impl->recent_manager != manager) + impl->recent_manager = manager; + + profile_end ("end", NULL); +} + +static void gtk_file_chooser_default_style_set (GtkWidget *widget, GtkStyle *previous_style) { @@ -5658,6 +5786,7 @@ gtk_file_chooser_default_screen_changed (GtkWidget *widget, remove_settings_signal (impl, previous_screen); check_icon_theme (impl); + recent_manager_update (impl); g_signal_emit_by_name (widget, "default-size-changed"); @@ -6415,7 +6544,9 @@ update_chooser_entry (GtkFileChooserDefault *impl) struct update_chooser_entry_selected_foreach_closure closure; const char *file_part; - if (impl->operation_mode == OPERATION_MODE_SEARCH || !impl->location_entry) + if (impl->operation_mode == OPERATION_MODE_SEARCH || + impl->operation_mode == OPERATION_MODE_RECENT || + !impl->location_entry) return; if (!(impl->action == GTK_FILE_CHOOSER_ACTION_SAVE @@ -6472,13 +6603,20 @@ update_chooser_entry (GtkFileChooserDefault *impl) impl->browse_files_last_selected_name); } - else + else if (impl->operation_mode == OPERATION_MODE_SEARCH) { gtk_tree_model_get (GTK_TREE_MODEL (impl->search_model), &closure.first_selected_iter, SEARCH_MODEL_COL_DISPLAY_NAME, &file_part, -1); } + else if (impl->operation_mode == OPERATION_MODE_RECENT) + { + gtk_tree_model_get (GTK_TREE_MODEL (impl->recent_model), + &closure.first_selected_iter, + RECENT_MODEL_COL_DISPLAY_NAME, &file_part, + -1); + } } else { @@ -6692,7 +6830,17 @@ gtk_file_chooser_default_update_current_folder (GtkFileChooser *chooser, profile_start ("start", (char *) path); - search_switch_to_browse_mode (impl); + switch (impl->operation_mode) + { + case OPERATION_MODE_SEARCH: + search_switch_to_browse_mode (impl); + break; + case OPERATION_MODE_RECENT: + recent_switch_to_browse_mode (impl); + break; + case OPERATION_MODE_BROWSE: + break; + } g_assert (path != NULL); @@ -6736,7 +6884,8 @@ gtk_file_chooser_default_get_current_folder (GtkFileChooser *chooser) { GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - if (impl->operation_mode == OPERATION_MODE_SEARCH) + if (impl->operation_mode == OPERATION_MODE_SEARCH || + impl->operation_mode == OPERATION_MODE_RECENT) return NULL; if (impl->reload_state == RELOAD_EMPTY) @@ -6801,8 +6950,12 @@ gtk_file_chooser_default_select_path (GtkFileChooser *chooser, if (!parent_path) return _gtk_file_chooser_set_current_folder_path (chooser, path, error); - if (impl->operation_mode == OPERATION_MODE_SEARCH || impl->load_state == LOAD_EMPTY) - same_path = FALSE; + if (impl->operation_mode == OPERATION_MODE_SEARCH || + impl->operation_mode == OPERATION_MODE_RECENT || + impl->load_state == LOAD_EMPTY) + { + same_path = FALSE; + } else { g_assert (impl->current_folder != NULL); @@ -6898,7 +7051,8 @@ gtk_file_chooser_default_select_all (GtkFileChooser *chooser) { GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - if (impl->operation_mode == OPERATION_MODE_SEARCH) + if (impl->operation_mode == OPERATION_MODE_SEARCH || + impl->operation_mode == OPERATION_MODE_RECENT) { GtkTreeSelection *selection; @@ -7051,6 +7205,9 @@ gtk_file_chooser_default_get_paths (GtkFileChooser *chooser) if (impl->operation_mode == OPERATION_MODE_SEARCH) return search_get_selected_paths (impl); + if (impl->operation_mode == OPERATION_MODE_RECENT) + return recent_get_selected_paths (impl); + info.impl = impl; info.result = NULL; info.path_from_entry = NULL; @@ -8088,6 +8245,9 @@ gtk_file_chooser_default_should_respond (GtkFileChooserEmbed *chooser_embed) if (impl->operation_mode == OPERATION_MODE_SEARCH) return search_should_respond (impl); + if (impl->operation_mode == OPERATION_MODE_RECENT) + return recent_should_respond (impl); + selection_check (impl, &num_selected, &all_files, &all_folders); if (num_selected > 2) @@ -8161,14 +8321,14 @@ gtk_file_chooser_default_should_respond (GtkFileChooserEmbed *chooser_embed) error = NULL; if (is_folder) { - if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN - || impl->action == GTK_FILE_CHOOSER_ACTION_SAVE) + if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN || + impl->action == GTK_FILE_CHOOSER_ACTION_SAVE) { change_folder_and_display_error (impl, path, TRUE); retval = FALSE; } - else if (impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER - || GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) + else if (impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER || + impl->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. @@ -8270,16 +8430,16 @@ gtk_file_chooser_default_initial_focus (GtkFileChooserEmbed *chooser_embed) impl = GTK_FILE_CHOOSER_DEFAULT (chooser_embed); - if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN - || impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) + if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN || + impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) { if (impl->location_mode == LOCATION_MODE_PATH_BAR) widget = impl->browse_files_tree_view; else widget = impl->location_entry; } - else if (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE - || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) + else if (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE || + impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) widget = impl->location_entry; else { @@ -8602,8 +8762,7 @@ search_stop_searching (GtkFileChooserDefault *impl, static void search_switch_to_browse_mode (GtkFileChooserDefault *impl) { - if (impl->operation_mode == OPERATION_MODE_BROWSE) - return; + g_assert (impl->operation_mode != OPERATION_MODE_BROWSE); search_stop_searching (impl, FALSE); search_clear_model (impl, TRUE); @@ -8873,6 +9032,336 @@ search_activate (GtkFileChooserDefault *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 (gtk_tree_model_get_iter_first (model, &iter)) + { + do + { + GtkFilePath *file_path; + 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_INFO, &recent_info, + -1); + + 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; + + if (remove_from_treeview) + gtk_tree_view_set_model (GTK_TREE_VIEW (impl->browse_files_tree_view), 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) +{ + GtkRecentInfo *info_a, *info_b; + + gtk_tree_model_get (model, a, RECENT_MODEL_COL_INFO, &info_a, -1); + gtk_tree_model_get (model, b, RECENT_MODEL_COL_INFO, &info_b, -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 void +recent_setup_model (GtkFileChooserDefault *impl) +{ + g_assert (impl->recent_model == 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; + * + * 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); + + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (impl->recent_model), + RECENT_MODEL_COL_INFO, + recent_column_mtime_sort_func, + impl, + NULL); + + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (impl->recent_model), + RECENT_MODEL_COL_INFO, + GTK_SORT_DESCENDING); + + gtk_tree_view_set_model (GTK_TREE_VIEW (impl->browse_files_tree_view), + GTK_TREE_MODEL (impl->recent_model)); +} + +typedef struct +{ + GtkFileChooserDefault *impl; + GList *items; + gint n_items; + gint n_loaded_items; +} RecentLoadData; + +static void +recent_idle_cleanup (gpointer data) +{ + RecentLoadData *load_data = data; + + set_busy_cursor (load_data->impl, FALSE); + + load_data->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); +} + +static gboolean +recent_idle_load (gpointer data) +{ + RecentLoadData *load_data = data; + GtkFileChooserDefault *impl = load_data->impl; + GtkTreeIter iter; + GtkRecentInfo *info; + const gchar *uri, *display_name; + GtkFilePath *path; + + if (!impl->recent_manager) + return FALSE; + + if (!load_data->items) + { + load_data->items = gtk_recent_manager_get_items (impl->recent_manager); + load_data->n_items = g_list_length (load_data->items); + load_data->n_loaded_items = 0; + + 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); + + gtk_list_store_append (impl->recent_model, &iter); + 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), + -1); + + 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; + + /* 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); + } + + /* EB: hide the filter combo? */ +} + +/* 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) +{ + if (impl->operation_mode == OPERATION_MODE_RECENT) + return; + + impl->operation_mode = OPERATION_MODE_RECENT; + + stop_loading_and_clear_list_model (impl); + recent_hide_entry (impl); + file_list_set_sort_column_ids (impl); + recent_start_loading (impl); +} + + static void set_current_filter (GtkFileChooserDefault *impl, GtkFileFilter *filter) @@ -8947,7 +9436,7 @@ check_preview_change (GtkFileChooserDefault *impl) new_display_name = gtk_file_info_get_display_name (new_info); } } - else + else if (impl->operation_mode == OPERATION_MODE_SEARCH) { GtkTreeIter iter; @@ -8959,6 +9448,18 @@ check_preview_change (GtkFileChooserDefault *impl) 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), &iter, cursor_path); + gtk_tree_path_free (cursor_path); + + gtk_tree_model_get (GTK_TREE_MODEL (impl->recent_model), &iter, + RECENT_MODEL_COL_PATH, &new_path, + RECENT_MODEL_COL_DISPLAY_NAME, &new_display_name, + -1); + } } if (new_path != impl->preview_path && @@ -9045,7 +9546,17 @@ shortcuts_activate_volume (GtkFileChooserDefault *impl, { GtkFilePath *path; - search_switch_to_browse_mode (impl); + 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. @@ -9164,6 +9675,10 @@ shortcuts_activate_iter (GtkFileChooserDefault *impl, { search_activate (impl); } + else if (shortcut_type == SHORTCUT_TYPE_RECENT) + { + recent_activate (impl); + } } /* Callback used when a row in the shortcuts list is activated */ @@ -9329,6 +9844,28 @@ list_row_activated (GtkTreeView *tree_view, g_signal_emit_by_name (impl, "file-activated"); } break; + + case OPERATION_MODE_RECENT: + { + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (impl->recent_model), &iter, path)) + return; + +#if 0 + gtk_tree_model_get (GTK_TREE_MODEL (impl->recent_model), &iter, + RECENT_MODEL_COL_PATH, &file_path, + RECENT_MODEL_COL_MIME_TYPE, &mime_type, + -1); + + if (strcmp (mime_type, "inode/x-folder") == 0) + { + change_folder_and_display_error (impl, file_path, FALSE); + recent_switch_to_browse_mode (impl); + return; + } +#endif + g_signal_emit_by_name (impl, "file-activated"); + } + break; case OPERATION_MODE_BROWSE: { @@ -9416,6 +9953,21 @@ list_icon_data_func (GtkTreeViewColumn *tree_column, -1); sensitive = TRUE; break; + + case OPERATION_MODE_RECENT: + { + GtkRecentInfo *info; + + gtk_tree_model_get (GTK_TREE_MODEL (impl->recent_model), iter, + RECENT_MODEL_COL_INFO, &info, + -1); + + pixbuf = gtk_recent_info_get_icon (info, impl->icon_size); + + if (impl->local_only && !gtk_recent_info_is_local (info)) + sensitive = FALSE; + } + break; case OPERATION_MODE_BROWSE: { @@ -9488,6 +10040,21 @@ list_name_data_func (GtkTreeViewColumn *tree_column, return; } + if (impl->operation_mode == OPERATION_MODE_RECENT) + { + char *display_name; + + gtk_tree_model_get (GTK_TREE_MODEL (impl->recent_model), iter, + RECENT_MODEL_COL_DISPLAY_NAME, &display_name, + -1); + g_object_set (cell, + "text", display_name, + "sensitive", TRUE, + "ellipsize", PANGO_ELLIPSIZE_END, + NULL); + return; + } + info = get_list_file_info (impl, iter); sensitive = TRUE; @@ -9503,8 +10070,8 @@ list_name_data_func (GtkTreeViewColumn *tree_column, } - if (impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER - || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) + if (impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER || + impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) { sensitive = gtk_file_info_get_is_folder (info); } @@ -9591,6 +10158,18 @@ list_mtime_data_func (GtkTreeViewColumn *tree_column, -1); time_mtime = statbuf->st_mtime; } + else if (impl->operation_mode == OPERATION_MODE_RECENT) + { + GtkRecentInfo *info; + + gtk_tree_model_get (GTK_TREE_MODEL (impl->recent_model), iter, + RECENT_MODEL_COL_INFO, &info, + -1); + time_mtime = (GtkFileTime) gtk_recent_info_get_modified (info); + + if (impl->local_only && !gtk_recent_info_is_local (info)) + sensitive = FALSE; + } else { const GtkFileInfo *info; @@ -9667,12 +10246,24 @@ static void location_popup_handler (GtkFileChooserDefault *impl, const gchar *path) { - if (impl->operation_mode == OPERATION_MODE_SEARCH) + if (impl->operation_mode != OPERATION_MODE_BROWSE) { GtkWidget *widget_to_focus; /* This will give us the location widgets back */ - search_switch_to_browse_mode (impl); + switch (impl->operation_mode) + { + case OPERATION_MODE_SEARCH: + search_switch_to_browse_mode (impl); + break; + case OPERATION_MODE_RECENT: + recent_switch_to_browse_mode (impl); + break; + case OPERATION_MODE_BROWSE: + g_assert_not_reached (); + break; + } + if (impl->current_folder) change_folder_and_display_error (impl, impl->current_folder, FALSE); @@ -9684,8 +10275,9 @@ location_popup_handler (GtkFileChooserDefault *impl, gtk_widget_grab_focus (widget_to_focus); return; } - if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN - || impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) + + if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN || + impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) { LocationMode new_mode; @@ -9716,8 +10308,8 @@ location_popup_handler (GtkFileChooserDefault *impl, } } } - else if (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE - || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) + else if (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE || + impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) { gtk_widget_grab_focus (impl->location_entry); if (path != NULL) @@ -9779,6 +10371,14 @@ search_shortcut_handler (GtkFileChooserDefault *impl) switch_to_shortcut (impl, shortcuts_get_index (impl, SHORTCUTS_SEARCH)); } +/* Handler for the "recent-shortcut" keybinding signal */ +static void +recent_shortcut_handler (GtkFileChooserDefault *impl) +{ + if (impl->has_recent) + switch_to_shortcut (impl, shortcuts_get_index (impl, SHORTCUTS_RECENT)); +} + static void quick_bookmark_handler (GtkFileChooserDefault *impl, gint bookmark_index) diff --git a/gtk/gtkfilechooserprivate.h b/gtk/gtkfilechooserprivate.h index 3a7416c..1750627 100644 --- a/gtk/gtkfilechooserprivate.h +++ b/gtk/gtkfilechooserprivate.h @@ -25,6 +25,7 @@ #include "gtkfilesystem.h" #include "gtkfilesystemmodel.h" #include "gtkliststore.h" +#include "gtkrecentmanager.h" #include "gtksearchengine.h" #include "gtkquery.h" #include "gtktooltips.h" @@ -151,7 +152,8 @@ typedef enum { typedef enum { OPERATION_MODE_BROWSE, - OPERATION_MODE_SEARCH + OPERATION_MODE_SEARCH, + OPERATION_MODE_RECENT } OperationMode; struct _GtkFileChooserDefault @@ -188,13 +190,18 @@ struct _GtkFileChooserDefault GtkFileSystemModel *browse_files_model; char *browse_files_last_selected_name; - /* Widgets for searching */ + /* Search */ GtkWidget *search_hbox; GtkWidget *search_entry; GtkSearchEngine *search_engine; GtkQuery *search_query; GtkListStore *search_model; + /* Recently Used */ + GtkRecentManager *recent_manager; + GtkListStore *recent_model; + guint load_recent_id; + GtkWidget *filter_combo_hbox; GtkWidget *filter_combo; GtkWidget *preview_box; @@ -293,6 +300,7 @@ struct _GtkFileChooserDefault guint has_home : 1; guint has_desktop : 1; guint has_search : 1; + guint has_recent : 1; #if 0 guint shortcuts_drag_outside : 1; -- 1.4.4.2