2006-02-16 Federico Mena Quintero Add a "Search" shortcut to the file chooser so that we can use Beagle for searching. Fixes https://bugzilla.novell.com/show_bug.cgi?id=151580 * gtk/gtkfilechooserdefault.c (OperationMode): New enumeration with OPERATION_MODE_BROWSE and OPERATION_MODE_SEARCH. (struct _GtkFileChooserDefault): New operation_mode field. (gtk_file_chooser_default_init): Start with OPERATION_MODE_BROWSE. (SHORTCUTS_COL_TYPE): New column number for the shortcuts model; this replaces SHORTCUTS_COL_IS_VOLUME. (ShortcutType): New enumeration for the type of each shortcut. (shortcuts_model_create): Create the SHORTCUT_COL_TYPE column with type G_TYPE_INT so that we can stuff a ShortcutType value in it. (shortcuts_free_row_data): Check the shortcut type. (shortcuts_row_separator_func): Use the SHORTCUTS_COL_TYPE and test its value for SHORTCUT_TYPE_SEPARATOR, instead of checking for a non-null name. (shortcuts_list_create): Don't pass any user_data to gtk_tree_view_set_row_separator_func(); we don't need it anymore. (save_folder_combo_create): Likewise. (ShortcutsIndex): Added SHORTCUTS_SEARCH and SHORTCUTS_SEARCH_SEPARATOR at the beginning. (shortcuts_get_index): Added cases for SHORTCUTS_SEARCH and SHORTCUTS_SEARCH_SEPARATOR. (shortcuts_model_create): Call shortcuts_append_search(). (shortcuts_append_search): New function. (shortcuts_reload_icons): Re-render the Search icon. (shortcuts_insert_separator): Accept SHORTCUTS_SEARCH_SEPARATOR as well. Set the type column to SHORTCUT_TYPE_SEPARATOR. (shortcuts_insert_path): Take a ShortcutType rather than a boolean is_volume argument. (shortcuts_select_func): Consider all separators, not just the bookmarks separator. (shortcuts_activate_iter): Activate searching mode when we come to the SHORTCUT_TYPE_SEARCH item. (search_activate): New function; activates search mode. (stop_loading_and_clear_list_model): New helper function; moved the code over from set_list_model(). (set_list_model): Use stop_loading_and_clear_list_model(). (search_activate): Stop any loading operations and set up the widgets for the search. (search_setup_widgets): New function. (search_setup_model): New function. (search_entry_activate_cb): New callback. (search_start_query): New function. (struct _GtkFileChooserDefault): New field browse_path_bar_hbox. This is the hbox that normally holds the path bar and the Create Folder button, but it was not a stored field before. (file_pane_create): Use impl->browse_path_bar_hbox. (list_select_func): If we are in OPERATION_MODE_SEARCH, always allow selecting files. (list_icon_data_func): Return a null icon for now if we are in OPERATION_MODE_SEARCH. (list_name_data_func): Handle OPERATION_MODE_SEARCH; get the display name from the search_model. (list_mtime_data_func): Handle OPERATION_MODE_SEARCH; get the mtime from the struct stat in the search_model. (shortcuts_row_activated_cb): Don't focus the browse_files_tree_view; do it explicitly when we need it. (focus_browse_tree_view_if_possible): New helper function; focuses the browse list only if it is visible. (gtk_file_chooser_default_should_respond): Use focus_browse_tree_view_if_possible(). (switch_to_shortcut): Give the focus to the browse_files_tree_view by using focus_browse_tree_view_if_possible(). (shortcuts_activate_volume): Give the focus to the browse_files_tree_view by using focus_browse_tree_view_if_possible(). (shortcuts_activate_iter): Give the focus to the browse_files_tree_view when activating a path by using focus_browse_tree_view_if_possible(). (search_switch_to_browse_mode): New function. (shortcuts_activate_volume): Call search_switch_to_browse_mode(). (gtk_file_chooser_default_update_current_folder): Likewise. (create_file_list): Set the vertical scrollbar policy to ALWAYS to avoid flickering when we clear/unset/re-set the model. (create_file_list): Don't set the ellipsization of the cell renderer for the Name column here. (list_name_data_func): Set the ellipsization here: START for search mode, NONE for type-a-folder-name, END for normal files. (list_row_activated): Just emit "file-activated" if we are in OPERATION_MODE_SEARCH. (search_should_respond): New function; sees whether there are selected files in search mode. (gtk_file_chooser_default_should_respond): If we are in the case for the file list and the operation mode is OPERATION_MODE_SEARCH, call search_should_respond(). If we are in OPERATION_MODE_SEARCH and the toplevel_last_focus_widget was the search_entry, start the search process. (search_get_selected_paths): New function. (gtk_file_chooser_default_get_paths): If we are in OPERATION_MODE_SEARCH, just call search_get_selected_paths(). (gtk_file_chooser_default_map): Only do the reload_state actions if we are in OPERATION_MODE_SEARCH. (gtk_file_chooser_default_finalize): Call search_clear_model(). (gtk_file_chooser_default_dispose): Stop searching if we are in OPERATION_MODE_SEARCH. (gtk_file_chooser_default_select_path): Unconditionally queue a path change request if we are in OPERATION_MODE_SEARCH. (gtk_file_chooser_default_select_all): Handle OPERATION_MODE_SEARCH by selecting all the rows. (gtk_file_chooser_default_get_current_folder): Return NULL if we are in OPERATION_MODE_SEARCH. (list_selection_changed): Don't check the editable row unless we are in OPERATION_MODE_BROWSE. (update_chooser_entry): If we are in OPERATION_MODE_SEARCH, set the entry from the display name of the selected search hit. (check_preview_change): Update the preview data from the search_model if we are in OPERATION_MODE_SEARCH. (bookmarks_check_add_sensitivity): Make the items insensitive if we are in OPERATION_MODE_SEARCH. (remove_selected_bookmarks): Assert that the data in the bookmark is not NULL only after we have checked that the bookmark is removable. (struct _GtkFileChooserDefault): New list_mtime_column field. (file_list_set_sort_column_ids): New function. (create_file_list): Use the impl->list_mtime_column field instead of a temporary variable. Call file_list_set_sort_column_ids() instead of setting the ids here. * gtk/gtkfilechooserdialog.c (gtk_file_chooser_dialog_map): Call ::initial_focus() on the child before calling ::map() on our parent class. This will prevent the shortcuts treeview from highlighting its first row as a result of getting assigned focus by gtk_dialog_map(). Fixes https://bugzilla.novell.com/show_bug.cgi?id=148896 * gtkfilechooserdefault.c (struct _GtkFileChooserDefault): Renamed the shortcuts_filter_model field to shortcuts_pane_filter_model. (struct _GtkFileChooserDefault): Added a shortcuts_combo_filter_model field. (shortcuts_pane_filter_cb): Renamed from shortcuts_filter_cb(). (shortcuts_combo_filter_func): New function; we use it to filter out the "Search" row and its separator, so that they don't show up in the "Save in folder" combo. (save_folder_combo_create): Create a GtkTreeModelFilter with shortcuts_combo_filter_func() for the "Save in folder" combo. (gtk_file_chooser_default_finalize): Unref the shortcuts_combo_filter_model. (shortcuts_add_bookmarks): Use the shortcuts_combo_filter_model instead of the plain shortcuts_model. (shortcuts_add_bookmarks): Take into account the Search item and its separator when computing the position of the selected row in the save folder combo. (shortcuts_add_current_folder): Likewise. (save_folder_combo_changed_cb): Convert the iter from the combo into a child iter from the filter model. (gtk_file_chooser_default_add_shortcut_folder): Refilter the combo filter model as well. (shortcuts_add_volumes): Likewise. (ShortcutsModelFilter): Renamed to ShortcutsPaneModelFilter; renamed its method functions accordingly. --- gtk+-2.8.10/gtk/gtkfilechooserdialog.c 2005-05-02 23:47:13.000000000 -0500 +++ gtk+-2.8.10.beagle/gtk/gtkfilechooserdialog.c 2006-02-16 11:59:47.000000000 -0600 @@ -499,9 +499,9 @@ gtk_file_chooser_dialog_map (GtkWidget * if (!GTK_WIDGET_MAPPED (priv->widget)) gtk_widget_map (priv->widget); - GTK_WIDGET_CLASS (parent_class)->map (widget); - _gtk_file_chooser_embed_initial_focus (GTK_FILE_CHOOSER_EMBED (priv->widget)); + + GTK_WIDGET_CLASS (parent_class)->map (widget); } /* GtkWidget::unmap handler */ --- gtk+-2.8.10/gtk/gtkfilechooserdefault.c 2006-03-23 20:25:07.000000000 -0600 +++ gtk+-2.8.10.beagle/gtk/gtkfilechooserdefault.c 2006-03-23 20:24:02.000000000 -0600 @@ -19,6 +19,9 @@ */ #include +#include +#include +#include #include "gdk/gdkkeysyms.h" #include "gtkalignment.h" #include "gtkbindings.h" @@ -158,8 +161,23 @@ typedef enum { RELOAD_WAS_UNMAPPED /* We had a folder but got unmapped; reload is needed */ } ReloadState; +typedef enum { + OPERATION_MODE_BROWSE, + OPERATION_MODE_SEARCH +} OperationMode; + #define MAX_LOADING_TIME 500 +/* #include */ +typedef struct _BeagleHit BeagleHit; +typedef struct _BeagleQuery BeagleQuery; +typedef struct _BeagleClient BeagleClient; +typedef struct _BeagleRequest BeagleRequest; +typedef struct _BeagleFinishedResponse BeagleFinishedResponse; +typedef struct _BeagleHitsAddedResponse BeagleHitsAddedResponse; +typedef struct _BeagleQueryPartProperty BeagleQueryPartProperty; +typedef struct _BeagleQueryPart BeagleQueryPart; + struct _GtkFileChooserDefaultClass { GtkVBoxClass parent_class; @@ -194,8 +212,16 @@ struct _GtkFileChooserDefault GtkWidget *browse_files_popup_menu_add_shortcut_item; GtkWidget *browse_files_popup_menu_hidden_files_item; GtkWidget *browse_new_folder_button; + GtkWidget *browse_path_bar_hbox; GtkWidget *browse_path_bar; + /* Widgets for searching */ + GtkWidget *search_hbox; + GtkWidget *search_entry; + BeagleClient *search_client; + BeagleQuery *search_query; + GtkListStore *search_model; + GtkFileSystemModel *browse_files_model; GtkWidget *filter_combo_hbox; @@ -207,7 +233,16 @@ struct _GtkFileChooserDefault GtkWidget *extra_widget; GtkListStore *shortcuts_model; - GtkTreeModel *shortcuts_filter_model; + + /* Filter for the shortcuts pane. We filter out the "current folder" row and + * the separator that we use for the "Save in folder" combo. + */ + GtkTreeModel *shortcuts_pane_filter_model; + + /* Filter for the "Save in folder" combo. We filter out the Search row and + * its separator. + */ + GtkTreeModel *shortcuts_combo_filter_model; GtkTreeModelSort *sort_model; @@ -215,6 +250,8 @@ struct _GtkFileChooserDefault ReloadState reload_state; guint load_timeout_id; + OperationMode operation_mode; + GSList *pending_select_paths; GtkFileFilter *current_filter; @@ -239,6 +276,7 @@ struct _GtkFileChooserDefault GtkTreeViewColumn *list_name_column; GtkCellRenderer *list_name_renderer; + GtkTreeViewColumn *list_mtime_column; GSource *edited_idle; char *edited_new_text; @@ -288,12 +326,19 @@ enum { SHORTCUTS_COL_PIXBUF, SHORTCUTS_COL_NAME, SHORTCUTS_COL_DATA, - SHORTCUTS_COL_IS_VOLUME, + SHORTCUTS_COL_TYPE, SHORTCUTS_COL_REMOVABLE, SHORTCUTS_COL_PIXBUF_VISIBLE, SHORTCUTS_COL_NUM_COLUMNS }; +typedef enum { + SHORTCUT_TYPE_PATH, + SHORTCUT_TYPE_VOLUME, + SHORTCUT_TYPE_SEPARATOR, + SHORTCUT_TYPE_SEARCH +} ShortcutType; + /* Column numbers for the file list */ enum { FILE_LIST_COL_NAME, @@ -302,6 +347,14 @@ enum { FILE_LIST_COL_NUM_COLUMNS }; +/* Column numbers for the search model. Keep this in sync with search_setup_model() */ +enum { + SEARCH_MODEL_COL_PATH, + SEARCH_MODEL_COL_DISPLAY_NAME, + SEARCH_MODEL_COL_COLLATION_KEY, + SEARCH_MODEL_COL_STAT +}; + /* Identifiers for target types */ enum { GTK_TREE_MODEL_ROW, @@ -341,9 +394,106 @@ static const GtkTargetEntry file_list_de static const int num_file_list_dest_targets = (sizeof (file_list_dest_targets) / sizeof (file_list_dest_targets[0])); +/* We dlopen() all the following from libbeagle at runtime */ +#define BEAGLE_HIT(x) ((BeagleHit *)(x)) +#define BEAGLE_REQUEST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), beagle_request_get_type(), BeagleRequest)) +#define BEAGLE_QUERY_PART(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), beagle_query_part_get_type(), BeagleQueryPart)) + +typedef enum { + BEAGLE_QUERY_PART_LOGIC_REQUIRED = 1, + BEAGLE_QUERY_PART_LOGIC_PROHIBITED = 2 +} BeagleQueryPartLogic; + +typedef enum { + BEAGLE_PROPERTY_TYPE_UNKNOWN = 0, + BEAGLE_PROPERTY_TYPE_TEXT = 1, + BEAGLE_PROPERTY_TYPE_KEYWORD = 2, + BEAGLE_PROPERTY_TYPE_DATE = 3, + BEAGLE_PROPERTY_TYPE_LAST = 4 +} BeaglePropertyType; + +/* *static* wrapper function pointers */ +static gboolean (*beagle_client_send_request_async) (BeagleClient *client, + BeagleRequest *request, + GError **err) = NULL; +static G_CONST_RETURN char *(*beagle_hit_get_uri) (BeagleHit *hit) = NULL; +static GSList *(*beagle_hits_added_response_get_hits) (BeagleHitsAddedResponse *response) = NULL; +static BeagleQuery *(*beagle_query_new) (void) = NULL; +static void (*beagle_query_add_text) (BeagleQuery *query, + const char *str) = NULL; +static void (*beagle_query_add_hit_type) (BeagleQuery *query, + const char *hit_type) = NULL; +static BeagleQueryPartProperty *(*beagle_query_part_property_new) (void) = NULL; +static void (*beagle_query_part_set_logic) (BeagleQueryPart *part, BeagleQueryPartLogic logic) = NULL; +static void (*beagle_query_part_property_set_key) (BeagleQueryPartProperty *part, const char *key) = NULL; +static void (*beagle_query_part_property_set_value) (BeagleQueryPartProperty *part, const char *value) = NULL; +static void (*beagle_query_part_property_set_property_type) (BeagleQueryPartProperty *part, BeaglePropertyType prop_type) = NULL; +static void (*beagle_query_add_part) (BeagleQuery *query, BeagleQueryPart *part) = NULL; +static GType (*beagle_request_get_type) (void) = NULL; +static GType (*beagle_query_part_get_type) (void) = NULL; +static BeagleClient *(*beagle_client_new_real) (const char *client_name) = NULL; + +static struct BeagleDlMapping +{ + const char *fn_name; + gpointer *fn_ptr_ref; +} beagle_dl_mapping[] = +{ +#define MAP(a) { #a, (gpointer *)&a } + MAP (beagle_client_send_request_async), + MAP (beagle_hit_get_uri), + MAP (beagle_hits_added_response_get_hits), + MAP (beagle_query_new), + MAP (beagle_query_add_text), + MAP (beagle_query_add_hit_type), + MAP (beagle_query_part_property_new), + MAP (beagle_query_part_set_logic), + MAP (beagle_query_part_property_set_key), + MAP (beagle_query_part_property_set_value), + MAP (beagle_query_part_property_set_property_type), + MAP (beagle_query_add_part), + MAP (beagle_request_get_type), + MAP (beagle_query_part_get_type), +#undef MAP + { "beagle_client_new", (gpointer *)&beagle_client_new_real }, +}; + +static BeagleClient * +beagle_client_new (const char *client_name) +{ + if (!beagle_client_new_real) + { + int i; + GModule *beagle; + + beagle = g_module_open ("libbeagle.so.0", G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL); + if (!beagle) + { + g_warning ("Can't open libbeagle '%s'\n", g_module_error ()); + return NULL; + } + + for (i = 0; i < G_N_ELEMENTS (beagle_dl_mapping); i++) + { + if (!g_module_symbol (beagle, beagle_dl_mapping[i].fn_name, + beagle_dl_mapping[i].fn_ptr_ref)) + { + g_warning ("Missing symbol '%s' in libbeagle\n", + beagle_dl_mapping[i].fn_name); + g_module_close (beagle); + return NULL; + } + } + } + if (beagle_client_new_real) + return beagle_client_new_real (client_name); + return NULL; +} /* Interesting places in the shortcuts bar */ typedef enum { + SHORTCUTS_SEARCH, + SHORTCUTS_SEARCH_SEPARATOR, SHORTCUTS_HOME, SHORTCUTS_DESKTOP, SHORTCUTS_VOLUMES, @@ -528,6 +678,13 @@ static const GtkFileInfo *get_list_file_ static void load_remove_timer (GtkFileChooserDefault *impl); static void browse_files_center_selected_row (GtkFileChooserDefault *impl); +static void search_stop_searching (GtkFileChooserDefault *impl); +static void search_clear_model (GtkFileChooserDefault *impl, gboolean remove_from_treeview); +static gboolean search_should_respond (GtkFileChooserDefault *impl); +static void search_switch_to_browse_mode (GtkFileChooserDefault *impl); +static GSList *search_get_selected_paths (GtkFileChooserDefault *impl); +static void search_entry_activate_cb (GtkEntry *entry, gpointer data); + static GObjectClass *parent_class; @@ -538,26 +695,26 @@ typedef struct { GtkTreeModelFilter parent; GtkFileChooserDefault *impl; -} ShortcutsModelFilter; +} ShortcutsPaneModelFilter; typedef struct { GtkTreeModelFilterClass parent_class; -} ShortcutsModelFilterClass; +} ShortcutsPaneModelFilterClass; -#define SHORTCUTS_MODEL_FILTER_TYPE (_shortcuts_model_filter_get_type ()) -#define SHORTCUTS_MODEL_FILTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHORTCUTS_MODEL_FILTER_TYPE, ShortcutsModelFilter)) +#define SHORTCUTS_PANE_MODEL_FILTER_TYPE (_shortcuts_pane_model_filter_get_type ()) +#define SHORTCUTS_PANE_MODEL_FILTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHORTCUTS_PANE_MODEL_FILTER_TYPE, ShortcutsPaneModelFilter)) -static void shortcuts_model_filter_drag_source_iface_init (GtkTreeDragSourceIface *iface); +static void shortcuts_pane_model_filter_drag_source_iface_init (GtkTreeDragSourceIface *iface); -G_DEFINE_TYPE_WITH_CODE (ShortcutsModelFilter, - _shortcuts_model_filter, +G_DEFINE_TYPE_WITH_CODE (ShortcutsPaneModelFilter, + _shortcuts_pane_model_filter, GTK_TYPE_TREE_MODEL_FILTER, G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_SOURCE, - shortcuts_model_filter_drag_source_iface_init)); + shortcuts_pane_model_filter_drag_source_iface_init)); -static GtkTreeModel *shortcuts_model_filter_new (GtkFileChooserDefault *impl, - GtkTreeModel *child_model, - GtkTreePath *root); +static GtkTreeModel *shortcuts_pane_model_filter_new (GtkFileChooserDefault *impl, + GtkTreeModel *child_model, + GtkTreePath *root); @@ -782,6 +939,7 @@ gtk_file_chooser_default_init (GtkFileCh impl->load_state = LOAD_EMPTY; impl->reload_state = RELOAD_EMPTY; impl->pending_select_paths = NULL; + impl->operation_mode = OPERATION_MODE_BROWSE; gtk_box_set_spacing (GTK_BOX (impl), 12); @@ -798,16 +956,16 @@ shortcuts_free_row_data (GtkFileChooserD GtkTreeIter *iter) { gpointer col_data; - gboolean is_volume; + ShortcutType shortcut_type; gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), iter, SHORTCUTS_COL_DATA, &col_data, - SHORTCUTS_COL_IS_VOLUME, &is_volume, + SHORTCUTS_COL_TYPE, &shortcut_type, -1); - if (!col_data) + if (!(shortcut_type == SHORTCUT_TYPE_PATH || shortcut_type == SHORTCUT_TYPE_VOLUME) || !col_data) return; - if (is_volume) + if (shortcut_type == SHORTCUT_TYPE_VOLUME) { GtkFileSystemVolume *volume; @@ -818,6 +976,8 @@ shortcuts_free_row_data (GtkFileChooserD { GtkFilePath *path; + g_assert (shortcut_type == SHORTCUT_TYPE_PATH); + path = col_data; gtk_file_path_free (path); } @@ -904,8 +1064,11 @@ gtk_file_chooser_default_finalize (GObje GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (object); GSList *l; - if (impl->shortcuts_filter_model) - g_object_unref (impl->shortcuts_filter_model); + if (impl->shortcuts_pane_filter_model) + g_object_unref (impl->shortcuts_pane_filter_model); + + if (impl->shortcuts_combo_filter_model) + g_object_unref (impl->shortcuts_combo_filter_model); shortcuts_free (impl); @@ -947,6 +1110,8 @@ gtk_file_chooser_default_finalize (GObje if (impl->sort_model) g_object_unref (impl->sort_model); + search_clear_model (impl, FALSE); + g_free (impl->preview_display_name); g_free (impl->edited_new_text); @@ -1198,6 +1363,13 @@ set_preview_widget (GtkFileChooserDefaul update_preview_widget_visibility (impl); } +/* Renders a "Search" icon at an appropriate size for a tree view */ +static GdkPixbuf * +render_search_icon (GtkFileChooserDefault *impl) +{ + return gtk_widget_render_icon (GTK_WIDGET (impl), GTK_STOCK_FIND, GTK_ICON_SIZE_SMALL_TOOLBAR, NULL); +} + /* Re-reads all the icons for the shortcuts, used when the theme changes */ static void shortcuts_reload_icons (GtkFileChooserDefault *impl) @@ -1211,19 +1383,20 @@ shortcuts_reload_icons (GtkFileChooserDe do { gpointer data; - gboolean is_volume; + ShortcutType shortcut_type; gboolean pixbuf_visible; GdkPixbuf *pixbuf; gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter, SHORTCUTS_COL_DATA, &data, - SHORTCUTS_COL_IS_VOLUME, &is_volume, + SHORTCUTS_COL_TYPE, &shortcut_type, SHORTCUTS_COL_PIXBUF_VISIBLE, &pixbuf_visible, -1); - if (pixbuf_visible && data) + pixbuf = NULL; + if (pixbuf_visible) { - if (is_volume) + if (shortcut_type == SHORTCUT_TYPE_VOLUME) { GtkFileSystemVolume *volume; @@ -1231,7 +1404,7 @@ shortcuts_reload_icons (GtkFileChooserDe pixbuf = gtk_file_system_volume_render_icon (impl->file_system, volume, GTK_WIDGET (impl), impl->icon_size, NULL); } - else + else if (shortcut_type == SHORTCUT_TYPE_PATH) { const GtkFilePath *path; @@ -1239,6 +1412,10 @@ shortcuts_reload_icons (GtkFileChooserDe pixbuf = gtk_file_system_render_icon (impl->file_system, path, GTK_WIDGET (impl), impl->icon_size, NULL); } + else if (shortcut_type == SHORTCUT_TYPE_SEARCH) + { + pixbuf = render_search_icon (impl); + } gtk_list_store_set (impl->shortcuts_model, &iter, SHORTCUTS_COL_PIXBUF, pixbuf, @@ -1362,7 +1539,7 @@ check_is_folder (GtkFileSystem *fil static gboolean shortcuts_insert_path (GtkFileChooserDefault *impl, int pos, - gboolean is_volume, + ShortcutType shortcut_type, GtkFileSystemVolume *volume, const GtkFilePath *path, const char *label, @@ -1374,16 +1551,17 @@ shortcuts_insert_path (GtkFileChooserDef gpointer data; GtkTreeIter iter; - profile_start ("start", is_volume ? "volume" : (char *) path); + profile_start ("start", ((shortcut_type == SHORTCUT_TYPE_VOLUME) ? "volume" + : ((shortcut_type == SHORTCUT_TYPE_PATH) ? (char *) path : NULL))); - if (is_volume) + if (shortcut_type == SHORTCUT_TYPE_VOLUME) { data = volume; label_copy = gtk_file_system_volume_get_display_name (impl->file_system, volume); pixbuf = gtk_file_system_volume_render_icon (impl->file_system, volume, GTK_WIDGET (impl), impl->icon_size, NULL); } - else + else if (shortcut_type == SHORTCUT_TYPE_PATH) { if (!check_is_folder (impl->file_system, path, error)) { @@ -1411,6 +1589,11 @@ shortcuts_insert_path (GtkFileChooserDef pixbuf = gtk_file_system_render_icon (impl->file_system, path, GTK_WIDGET (impl), impl->icon_size, NULL); } + else + { + g_assert_not_reached (); + return FALSE; + } if (pos == -1) gtk_list_store_append (impl->shortcuts_model, &iter); @@ -1422,7 +1605,7 @@ shortcuts_insert_path (GtkFileChooserDef SHORTCUTS_COL_PIXBUF_VISIBLE, TRUE, SHORTCUTS_COL_NAME, label_copy, SHORTCUTS_COL_DATA, data, - SHORTCUTS_COL_IS_VOLUME, is_volume, + SHORTCUTS_COL_TYPE, shortcut_type, SHORTCUTS_COL_REMOVABLE, removable, -1); @@ -1436,6 +1619,28 @@ shortcuts_insert_path (GtkFileChooserDef return TRUE; } +static void +shortcuts_append_search (GtkFileChooserDefault *impl) +{ + GdkPixbuf *pixbuf; + GtkTreeIter iter; + + pixbuf = render_search_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, _("Search"), + SHORTCUTS_COL_DATA, NULL, + SHORTCUTS_COL_TYPE, SHORTCUT_TYPE_SEARCH, + SHORTCUTS_COL_REMOVABLE, FALSE, + -1); + + if (pixbuf) + g_object_unref (pixbuf); +} + /* Appends an item for the user's home directory to the shortcuts model */ static void shortcuts_append_home (GtkFileChooserDefault *impl) @@ -1456,7 +1661,7 @@ shortcuts_append_home (GtkFileChooserDef home_path = gtk_file_system_filename_to_path (impl->file_system, home); error = NULL; - impl->has_home = shortcuts_insert_path (impl, -1, FALSE, NULL, home_path, NULL, FALSE, &error); + impl->has_home = shortcuts_insert_path (impl, -1, SHORTCUT_TYPE_PATH, NULL, home_path, NULL, FALSE, &error); if (!impl->has_home) error_getting_info_dialog (impl, home_path, error); @@ -1491,7 +1696,7 @@ shortcuts_append_desktop (GtkFileChooser path = gtk_file_system_filename_to_path (impl->file_system, name); g_free (name); - impl->has_desktop = shortcuts_insert_path (impl, -1, FALSE, NULL, path, _("Desktop"), FALSE, NULL); + impl->has_desktop = shortcuts_insert_path (impl, -1, SHORTCUT_TYPE_PATH, NULL, path, _("Desktop"), FALSE, NULL); /* We do not actually pop up an error dialog if there is no desktop directory * because some people may really not want to have one. */ @@ -1532,7 +1737,7 @@ shortcuts_append_paths (GtkFileChooserDe label = gtk_file_system_get_bookmark_label (impl->file_system, path); /* NULL GError, but we don't really want to show error boxes here */ - if (shortcuts_insert_path (impl, start_row + num_inserted, FALSE, NULL, path, label, TRUE, NULL)) + if (shortcuts_insert_path (impl, start_row + num_inserted, SHORTCUT_TYPE_PATH, NULL, path, label, TRUE, NULL)) num_inserted++; g_free (label); @@ -1552,6 +1757,16 @@ shortcuts_get_index (GtkFileChooserDefau n = 0; + if (where == SHORTCUTS_SEARCH) + goto out; + + n++; + + if (where == SHORTCUTS_SEARCH_SEPARATOR) + goto out; + + n++; + if (where == SHORTCUTS_HOME) goto out; @@ -1671,7 +1886,7 @@ shortcuts_add_volumes (GtkFileChooserDef } } - if (shortcuts_insert_path (impl, start_row + n, TRUE, volume, NULL, NULL, FALSE, NULL)) + if (shortcuts_insert_path (impl, start_row + n, SHORTCUT_TYPE_VOLUME, volume, NULL, NULL, FALSE, NULL)) n++; else gtk_file_system_volume_free (impl->file_system, volume); @@ -1680,8 +1895,11 @@ shortcuts_add_volumes (GtkFileChooserDef impl->num_volumes = n; g_slist_free (list); - if (impl->shortcuts_filter_model) - gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->shortcuts_filter_model)); + if (impl->shortcuts_pane_filter_model) + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->shortcuts_pane_filter_model)); + + if (impl->shortcuts_combo_filter_model) + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->shortcuts_combo_filter_model)); impl->changing_folder = old_changing_folders; @@ -1695,7 +1913,9 @@ shortcuts_insert_separator (GtkFileChoos { GtkTreeIter iter; - g_assert (where == SHORTCUTS_BOOKMARKS_SEPARATOR || where == SHORTCUTS_CURRENT_FOLDER_SEPARATOR); + g_assert (where == SHORTCUTS_SEARCH_SEPARATOR + || where == SHORTCUTS_BOOKMARKS_SEPARATOR + || where == SHORTCUTS_CURRENT_FOLDER_SEPARATOR); gtk_list_store_insert (impl->shortcuts_model, &iter, shortcuts_get_index (impl, where)); @@ -1704,6 +1924,7 @@ shortcuts_insert_separator (GtkFileChoos SHORTCUTS_COL_PIXBUF_VISIBLE, FALSE, SHORTCUTS_COL_NAME, NULL, SHORTCUTS_COL_DATA, NULL, + SHORTCUTS_COL_TYPE, SHORTCUT_TYPE_SEPARATOR, -1); } @@ -1716,7 +1937,7 @@ shortcuts_add_bookmarks (GtkFileChooserD GtkTreeIter iter; GtkFilePath *list_selected = NULL; GtkFilePath *combo_selected = NULL; - gboolean is_volume; + ShortcutType shortcut_type; gpointer col_data; profile_start ("start", NULL); @@ -1729,10 +1950,10 @@ shortcuts_add_bookmarks (GtkFileChooserD gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter, SHORTCUTS_COL_DATA, &col_data, - SHORTCUTS_COL_IS_VOLUME, &is_volume, + SHORTCUTS_COL_TYPE, &shortcut_type, -1); - if (col_data && !is_volume) + if (col_data && shortcut_type == SHORTCUT_TYPE_PATH) list_selected = gtk_file_path_copy (col_data); } @@ -1740,13 +1961,13 @@ shortcuts_add_bookmarks (GtkFileChooserD gtk_combo_box_get_active_iter (GTK_COMBO_BOX (impl->save_folder_combo), &iter)) { - gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), + gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_combo_filter_model), &iter, SHORTCUTS_COL_DATA, &col_data, - SHORTCUTS_COL_IS_VOLUME, &is_volume, + SHORTCUTS_COL_TYPE, &shortcut_type, -1); - if (col_data && !is_volume) + if (col_data && shortcut_type == SHORTCUT_TYPE_PATH) combo_selected = gtk_file_path_copy (col_data); } @@ -1762,8 +1983,11 @@ shortcuts_add_bookmarks (GtkFileChooserD if (impl->num_bookmarks > 0) shortcuts_insert_separator (impl, SHORTCUTS_BOOKMARKS_SEPARATOR); - if (impl->shortcuts_filter_model) - gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->shortcuts_filter_model)); + if (impl->shortcuts_pane_filter_model) + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->shortcuts_pane_filter_model)); + + if (impl->shortcuts_combo_filter_model) + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->shortcuts_combo_filter_model)); if (list_selected) { @@ -1777,8 +2001,14 @@ shortcuts_add_bookmarks (GtkFileChooserD pos = shortcut_find_position (impl, combo_selected); if (pos != -1) - gtk_combo_box_set_active (GTK_COMBO_BOX (impl->save_folder_combo), - pos); + { + int search_separator_pos; + + search_separator_pos = shortcuts_get_index (impl, SHORTCUTS_SEARCH_SEPARATOR); + g_assert (pos >= search_separator_pos + 1); + gtk_combo_box_set_active (GTK_COMBO_BOX (impl->save_folder_combo), + pos - (search_separator_pos + 1)); + } gtk_file_path_free (combo_selected); } @@ -1823,12 +2053,12 @@ shortcuts_add_current_folder (GtkFileCho if (base_path && strcmp (gtk_file_path_get_string (base_path), gtk_file_path_get_string (impl->current_folder)) == 0) { - success = shortcuts_insert_path (impl, pos, TRUE, volume, NULL, NULL, FALSE, NULL); + success = shortcuts_insert_path (impl, pos, SHORTCUT_TYPE_VOLUME, volume, NULL, NULL, FALSE, NULL); if (success) volume = NULL; } else - success = shortcuts_insert_path (impl, pos, FALSE, NULL, impl->current_folder, NULL, FALSE, NULL); + success = shortcuts_insert_path (impl, pos, SHORTCUT_TYPE_PATH, NULL, impl->current_folder, NULL, FALSE, NULL); if (volume) gtk_file_system_volume_free (impl->file_system, volume); @@ -1843,7 +2073,13 @@ shortcuts_add_current_folder (GtkFileCho } if (success && impl->save_folder_combo != NULL) - gtk_combo_box_set_active (GTK_COMBO_BOX (impl->save_folder_combo), pos); + { + int search_separator_pos; + + search_separator_pos = shortcuts_get_index (impl, SHORTCUTS_SEARCH_SEPARATOR); + g_assert (pos >= search_separator_pos + 1); + gtk_combo_box_set_active (GTK_COMBO_BOX (impl->save_folder_combo), pos - (search_separator_pos + 1)); + } } /* Updates the current folder row in the shortcuts model */ @@ -1865,9 +2101,9 @@ shortcuts_update_current_folder (GtkFile /* Filter function used for the shortcuts filter model */ static gboolean -shortcuts_filter_cb (GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) +shortcuts_pane_filter_cb (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) { GtkFileChooserDefault *impl; GtkTreePath *path; @@ -1894,10 +2130,13 @@ shortcuts_model_create (GtkFileChooserDe GDK_TYPE_PIXBUF, /* pixbuf */ G_TYPE_STRING, /* name */ G_TYPE_POINTER, /* path or volume */ - G_TYPE_BOOLEAN, /* is the previous column a volume? */ + G_TYPE_INT, /* ShortcutType */ G_TYPE_BOOLEAN, /* removable */ G_TYPE_BOOLEAN); /* pixbuf cell visibility */ + shortcuts_append_search (impl); + shortcuts_insert_separator (impl, SHORTCUTS_SEARCH_SEPARATOR); + if (impl->file_system) { shortcuts_append_home (impl); @@ -1905,12 +2144,12 @@ shortcuts_model_create (GtkFileChooserDe shortcuts_add_volumes (impl); } - impl->shortcuts_filter_model = shortcuts_model_filter_new (impl, - GTK_TREE_MODEL (impl->shortcuts_model), - NULL); + impl->shortcuts_pane_filter_model = shortcuts_pane_model_filter_new (impl, + GTK_TREE_MODEL (impl->shortcuts_model), + NULL); - gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (impl->shortcuts_filter_model), - shortcuts_filter_cb, + gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (impl->shortcuts_pane_filter_model), + shortcuts_pane_filter_cb, impl, NULL); } @@ -2092,16 +2331,16 @@ shortcut_find_position (GtkFileChooserDe for (i = 0; i < current_folder_separator_idx; i++) { gpointer col_data; - gboolean is_volume; + ShortcutType shortcut_type; gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter, SHORTCUTS_COL_DATA, &col_data, - SHORTCUTS_COL_IS_VOLUME, &is_volume, + SHORTCUTS_COL_TYPE, &shortcut_type, -1); if (col_data) { - if (is_volume) + if (shortcut_type == SHORTCUT_TYPE_VOLUME) { GtkFileSystemVolume *volume; GtkFilePath *base_path; @@ -2117,7 +2356,7 @@ shortcut_find_position (GtkFileChooserDe if (exists) return i; } - else + else if (shortcut_type == SHORTCUT_TYPE_PATH) { GtkFilePath *model_path; @@ -2227,7 +2466,7 @@ shortcuts_get_selected (GtkFileChooserDe if (!gtk_tree_selection_get_selected (selection, NULL, &parent_iter)) return FALSE; - gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (impl->shortcuts_filter_model), + gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (impl->shortcuts_pane_filter_model), iter, &parent_iter); return TRUE; @@ -2250,11 +2489,11 @@ remove_selected_bookmarks (GtkFileChoose SHORTCUTS_COL_DATA, &col_data, SHORTCUTS_COL_REMOVABLE, &removable, -1); - g_assert (col_data != NULL); - if (!removable) return; + g_assert (col_data != NULL); + path = col_data; error = NULL; @@ -2411,6 +2650,16 @@ bookmarks_check_add_sensitivity (GtkFile gboolean active; gchar *tip; + if (impl->operation_mode == OPERATION_MODE_SEARCH) + { + gtk_widget_set_sensitive (impl->browse_shortcuts_add_button, FALSE); + + if (impl->browse_files_popup_menu_add_shortcut_item) + gtk_widget_set_sensitive (impl->browse_files_popup_menu_add_shortcut_item, FALSE); + + return; + } + selection_check (impl, &num_selected, NULL, &all_folders); if (num_selected == 0) @@ -2924,7 +3173,7 @@ shortcuts_reorder (GtkFileChooserDefault { GtkTreeIter iter; gpointer col_data; - gboolean is_volume; + ShortcutType shortcut_type; GtkTreePath *path; int old_position; int bookmarks_index; @@ -2949,10 +3198,10 @@ shortcuts_reorder (GtkFileChooserDefault gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter, SHORTCUTS_COL_NAME, &name, SHORTCUTS_COL_DATA, &col_data, - SHORTCUTS_COL_IS_VOLUME, &is_volume, + SHORTCUTS_COL_TYPE, &shortcut_type, -1); g_assert (col_data != NULL); - g_assert (!is_volume); + g_assert (shortcut_type == SHORTCUT_TYPE_PATH); file_path = col_data; file_path_copy = gtk_file_path_copy (file_path); /* removal below will free file_path, so we need a copy */ @@ -3015,7 +3264,7 @@ shortcuts_drag_data_received_cb (GtkWidg position -= bookmarks_index; if (selection_data->target == gdk_atom_intern ("text/uri-list", FALSE)) - shortcuts_drop_uris (impl, selection_data->data, position); + shortcuts_drop_uris (impl, (char *) selection_data->data, position); else if (selection_data->target == gdk_atom_intern ("GTK_TREE_MODEL_ROW", FALSE)) shortcuts_reorder (impl, position); @@ -3036,17 +3285,11 @@ shortcuts_row_separator_func (GtkTreeMod GtkTreeIter *iter, gpointer data) { - gint column = GPOINTER_TO_INT (data); - gchar *text; + ShortcutType shortcut_type; - gtk_tree_model_get (model, iter, column, &text, -1); + gtk_tree_model_get (model, iter, SHORTCUTS_COL_TYPE, &shortcut_type, -1); - if (!text) - return TRUE; - - g_free (text); - - return FALSE; + return (shortcut_type == SHORTCUT_TYPE_SEPARATOR); } /* Since GtkTreeView has a keybinding attached to '/', we need to catch @@ -3273,7 +3516,7 @@ shortcuts_list_create (GtkFileChooserDef atk_object_set_name (gtk_widget_get_accessible (impl->browse_shortcuts_tree_view), _("Shortcuts")); gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view), FALSE); - gtk_tree_view_set_model (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view), impl->shortcuts_filter_model); + gtk_tree_view_set_model (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view), impl->shortcuts_pane_filter_model); gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view), GDK_BUTTON1_MASK, @@ -3345,8 +3588,7 @@ shortcuts_list_create (GtkFileChooserDef gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view), shortcuts_row_separator_func, - GINT_TO_POINTER (SHORTCUTS_COL_NAME), - NULL); + NULL, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view), column); @@ -3529,7 +3771,7 @@ file_list_drag_data_received_cb (GtkWidg chooser = GTK_FILE_CHOOSER (data); /* Parse the text/uri-list string, navigate to the first one */ - uris = g_uri_list_extract_uris (selection_data->data); + uris = g_uri_list_extract_uris ((char *) selection_data->data); if (uris[0]) { uri = uris[0]; @@ -3667,6 +3909,8 @@ file_list_update_popup_menu (GtkFileChoo { file_list_build_popup_menu (impl); + /* FMQ: handle OPERATION_MODE_SEARCH */ + /* The sensitivity of the Add to Bookmarks item is set in * bookmarks_check_add_sensitivity() */ @@ -3756,20 +4000,40 @@ list_button_press_event_cb (GtkWidget return TRUE; } +/* Sets the sort column IDs for the file list based on the operation mode */ +static void +file_list_set_sort_column_ids (GtkFileChooserDefault *impl) +{ + int name_id, mtime_id; + + if (impl->operation_mode == OPERATION_MODE_BROWSE) + { + name_id = FILE_LIST_COL_NAME; + mtime_id = FILE_LIST_COL_MTIME; + } + else + { + name_id = SEARCH_MODEL_COL_PATH; + mtime_id = SEARCH_MODEL_COL_STAT; + } + + gtk_tree_view_column_set_sort_column_id (impl->list_name_column, name_id); + gtk_tree_view_column_set_sort_column_id (impl->list_mtime_column, mtime_id); +} + /* Creates the widgets for the file list */ static GtkWidget * create_file_list (GtkFileChooserDefault *impl) { GtkWidget *swin; GtkTreeSelection *selection; - GtkTreeViewColumn *column; GtkCellRenderer *renderer; /* Scrolled window */ swin = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swin), - GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (swin), GTK_SHADOW_IN); @@ -3823,7 +4087,6 @@ create_file_list (GtkFileChooserDefault gtk_tree_view_column_set_expand (impl->list_name_column, TRUE); gtk_tree_view_column_set_resizable (impl->list_name_column, TRUE); gtk_tree_view_column_set_title (impl->list_name_column, _("Name")); - gtk_tree_view_column_set_sort_column_id (impl->list_name_column, FILE_LIST_COL_NAME); renderer = gtk_cell_renderer_pixbuf_new (); gtk_tree_view_column_pack_start (impl->list_name_column, renderer, FALSE); @@ -3831,9 +4094,6 @@ create_file_list (GtkFileChooserDefault list_icon_data_func, impl, NULL); impl->list_name_renderer = gtk_cell_renderer_text_new (); - g_object_set (impl->list_name_renderer, - "ellipsize", PANGO_ELLIPSIZE_END, - NULL); g_signal_connect (impl->list_name_renderer, "edited", G_CALLBACK (renderer_edited_cb), impl); g_signal_connect (impl->list_name_renderer, "editing-canceled", @@ -3858,16 +4118,18 @@ create_file_list (GtkFileChooserDefault #endif /* Modification time column */ - column = gtk_tree_view_column_new (); - gtk_tree_view_column_set_resizable (column, TRUE); - gtk_tree_view_column_set_title (column, _("Modified")); + impl->list_mtime_column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_resizable (impl->list_mtime_column, TRUE); + gtk_tree_view_column_set_title (impl->list_mtime_column, _("Modified")); renderer = gtk_cell_renderer_text_new (); - gtk_tree_view_column_pack_start (column, renderer, TRUE); - gtk_tree_view_column_set_cell_data_func (column, renderer, + gtk_tree_view_column_pack_start (impl->list_mtime_column, renderer, TRUE); + gtk_tree_view_column_set_cell_data_func (impl->list_mtime_column, renderer, list_mtime_data_func, impl, NULL); - gtk_tree_view_column_set_sort_column_id (column, FILE_LIST_COL_MTIME); - gtk_tree_view_append_column (GTK_TREE_VIEW (impl->browse_files_tree_view), column); + gtk_tree_view_append_column (GTK_TREE_VIEW (impl->browse_files_tree_view), impl->list_mtime_column); + + file_list_set_sort_column_ids (impl); + gtk_widget_show_all (swin); return swin; @@ -3920,19 +4182,19 @@ file_pane_create (GtkFileChooserDefault gtk_widget_show (vbox); /* The path bar and 'Create Folder' button */ - hbox = gtk_hbox_new (FALSE, 12); - gtk_widget_show (hbox); + impl->browse_path_bar_hbox = gtk_hbox_new (FALSE, 12); + gtk_widget_show (impl->browse_path_bar_hbox); impl->browse_path_bar = create_path_bar (impl); g_signal_connect (impl->browse_path_bar, "path-clicked", G_CALLBACK (path_bar_clicked), impl); gtk_widget_show_all (impl->browse_path_bar); - gtk_box_pack_start (GTK_BOX (hbox), impl->browse_path_bar, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (impl->browse_path_bar_hbox), impl->browse_path_bar, TRUE, TRUE, 0); /* Create Folder */ impl->browse_new_folder_button = gtk_button_new_with_mnemonic (_("Create Fo_lder")); g_signal_connect (impl->browse_new_folder_button, "clicked", G_CALLBACK (new_folder_button_clicked), impl); - gtk_box_pack_end (GTK_BOX (hbox), impl->browse_new_folder_button, FALSE, FALSE, 0); - gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + gtk_box_pack_end (GTK_BOX (impl->browse_path_bar_hbox), impl->browse_new_folder_button, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), impl->browse_path_bar_hbox, FALSE, FALSE, 0); /* Box for lists and preview */ @@ -3989,7 +4251,54 @@ save_folder_combo_changed_cb (GtkComboBo return; if (gtk_combo_box_get_active_iter (combo, &iter)) - shortcuts_activate_iter (impl, &iter); + { + GtkTreeIter child_iter; + + gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (impl->shortcuts_combo_filter_model), + &child_iter, + &iter); + shortcuts_activate_iter (impl, &child_iter); + } +} + +/* Filter function used to filter out the Search item and its separator. Used + * for the "Save in folder" combo box, so that these items do not appear in it. + */ +static gboolean +shortcuts_combo_filter_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + GtkFileChooserDefault *impl; + GtkTreePath *tree_path; + gint *indices; + int idx; + gboolean retval; + + impl = GTK_FILE_CHOOSER_DEFAULT (data); + + g_assert (model == GTK_TREE_MODEL (impl->shortcuts_model)); + + tree_path = gtk_tree_model_get_path (GTK_TREE_MODEL (impl->shortcuts_model), iter); + g_assert (tree_path != NULL); + + indices = gtk_tree_path_get_indices (tree_path); + + retval = TRUE; + + idx = shortcuts_get_index (impl, SHORTCUTS_SEARCH); + if (idx == indices[0]) + retval = FALSE; + else + { + idx = shortcuts_get_index (impl, SHORTCUTS_SEARCH_SEPARATOR); + if (idx == indices[0]) + retval = FALSE; + } + + gtk_tree_path_free (tree_path); + + return retval; } /* Creates the combo box with the save folders */ @@ -3999,8 +4308,14 @@ save_folder_combo_create (GtkFileChooser GtkWidget *combo; GtkCellRenderer *cell; + impl->shortcuts_combo_filter_model = gtk_tree_model_filter_new (GTK_TREE_MODEL (impl->shortcuts_model), NULL); + gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (impl->shortcuts_combo_filter_model), + shortcuts_combo_filter_func, + impl, + NULL); + combo = g_object_new (GTK_TYPE_COMBO_BOX, - "model", impl->shortcuts_model, + "model", impl->shortcuts_combo_filter_model, "focus-on-click", FALSE, NULL); gtk_widget_show (combo); @@ -4022,8 +4337,7 @@ save_folder_combo_create (GtkFileChooser gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (combo), shortcuts_row_separator_func, - GINT_TO_POINTER (SHORTCUTS_COL_NAME), - NULL); + NULL, NULL); g_signal_connect (combo, "changed", G_CALLBACK (save_folder_combo_changed_cb), impl); @@ -4600,6 +4914,9 @@ gtk_file_chooser_default_dispose (GObjec impl->extra_widget = NULL; } + if (impl->operation_mode == OPERATION_MODE_SEARCH) + search_stop_searching (impl); + remove_settings_signal (impl, gtk_widget_get_screen (GTK_WIDGET (impl))); G_OBJECT_CLASS (parent_class)->dispose (object); @@ -4814,30 +5131,31 @@ gtk_file_chooser_default_map (GtkWidget GTK_WIDGET_CLASS (parent_class)->map (widget); - switch (impl->reload_state) - { - case RELOAD_EMPTY: - /* The user didn't explicitly give us a folder to display, so we'll use the cwd */ - 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); - break; - - case RELOAD_HAS_FOLDER: - /* Nothing; we are already loading or loaded, so we don't need to reload */ - break; - - case RELOAD_WAS_UNMAPPED: - /* Just reload the current folder */ - g_assert (impl->current_folder != NULL); - - pending_select_paths_store_selection (impl); - change_folder_and_display_error (impl, impl->current_folder); - break; + if (impl->operation_mode == OPERATION_MODE_BROWSE) + switch (impl->reload_state) + { + case RELOAD_EMPTY: + /* The user didn't explicitly give us a folder to display, so we'll use the cwd */ + 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); + break; + + case RELOAD_HAS_FOLDER: + /* Nothing; we are already loading or loaded, so we don't need to reload */ + break; + + case RELOAD_WAS_UNMAPPED: + /* Just reload the current folder */ + g_assert (impl->current_folder != NULL); + + pending_select_paths_store_selection (impl); + change_folder_and_display_error (impl, impl->current_folder); + break; - default: - g_assert_not_reached (); - } + default: + g_assert_not_reached (); + } bookmarks_changed_cb (impl->file_system, impl); @@ -5280,15 +5598,9 @@ browse_files_model_finished_loading_cb ( profile_end ("end", NULL); } -/* 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) +static void +stop_loading_and_clear_list_model (GtkFileChooserDefault *impl) { - g_assert (impl->current_folder != NULL); - - profile_start ("start", NULL); - load_remove_timer (impl); /* This changes the state to LOAD_EMPTY */ if (impl->browse_files_model) @@ -5303,8 +5615,21 @@ set_list_model (GtkFileChooserDefault *i impl->sort_model = NULL; } - set_busy_cursor (impl, TRUE); gtk_tree_view_set_model (GTK_TREE_VIEW (impl->browse_files_tree_view), NULL); +} + +/* 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) +{ + g_assert (impl->current_folder != NULL); + + profile_start ("start", NULL); + + stop_loading_and_clear_list_model (impl); + + set_busy_cursor (impl, TRUE); impl->browse_files_model = _gtk_file_system_model_new (impl->file_system, impl->current_folder, 0, @@ -5335,10 +5660,7 @@ static void update_chooser_entry (GtkFileChooserDefault *impl) { GtkTreeSelection *selection; - const GtkFileInfo *info; GtkTreeIter iter; - GtkTreeIter child_iter; - gboolean change_entry; if (!(impl->action == GTK_FILE_CHOOSER_ACTION_SAVE || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)) return; @@ -5359,20 +5681,34 @@ update_chooser_entry (GtkFileChooserDefa return; } - gtk_tree_model_sort_convert_iter_to_child_iter (impl->sort_model, - &child_iter, - &iter); + if (impl->operation_mode == OPERATION_MODE_BROWSE) + { + const GtkFileInfo *info; + GtkTreeIter child_iter; + gboolean change_entry; - info = _gtk_file_system_model_get_info (impl->browse_files_model, &child_iter); + gtk_tree_model_sort_convert_iter_to_child_iter (impl->sort_model, + &child_iter, + &iter); + + info = _gtk_file_system_model_get_info (impl->browse_files_model, &child_iter); + + if (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE) + change_entry = !gtk_file_info_get_is_folder (info); /* We don't want the name to change when clicking on a folder... */ + else + change_entry = TRUE; /* ... unless we are in CREATE_FOLDER mode */ - if (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE) - change_entry = !gtk_file_info_get_is_folder (info); /* We don't want the name to change when clicking on a folder... */ + if (change_entry) + _gtk_file_chooser_entry_set_file_part (GTK_FILE_CHOOSER_ENTRY (impl->save_file_name_entry), + gtk_file_info_get_display_name (info)); + } else - change_entry = TRUE; /* ... unless we are in CREATE_FOLDER mode */ + { + char *display_filename; - if (change_entry) - _gtk_file_chooser_entry_set_file_part (GTK_FILE_CHOOSER_ENTRY (impl->save_file_name_entry), - gtk_file_info_get_display_name (info)); + gtk_tree_model_get (GTK_TREE_MODEL (impl->search_model), &iter, SEARCH_MODEL_COL_DISPLAY_NAME, &display_filename, -1); + _gtk_file_chooser_entry_set_file_part (GTK_FILE_CHOOSER_ENTRY (impl->save_file_name_entry), display_filename); + } } static gboolean @@ -5394,6 +5730,8 @@ gtk_file_chooser_default_update_current_ profile_start ("start", (char *) path); + search_switch_to_browse_mode (impl); + g_assert (path != NULL); if (impl->local_only && @@ -5474,6 +5812,9 @@ gtk_file_chooser_default_get_current_fol { GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); + if (impl->operation_mode == OPERATION_MODE_SEARCH) + return NULL; + if (impl->reload_state == RELOAD_EMPTY) { char *current_working_dir; @@ -5536,7 +5877,7 @@ gtk_file_chooser_default_select_path (Gt if (!parent_path) return _gtk_file_chooser_set_current_folder_path (chooser, path, error); - if (impl->load_state == LOAD_EMPTY) + if (impl->operation_mode == OPERATION_MODE_SEARCH || impl->load_state == LOAD_EMPTY) same_path = FALSE; else { @@ -5632,6 +5973,16 @@ static void gtk_file_chooser_default_select_all (GtkFileChooser *chooser) { GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); + + if (impl->operation_mode == OPERATION_MODE_SEARCH) + { + GtkTreeSelection *selection; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view)); + gtk_tree_selection_select_all (selection); + return; + } + if (impl->select_multiple) gtk_tree_model_foreach (GTK_TREE_MODEL (impl->sort_model), maybe_select, impl); @@ -5752,6 +6103,9 @@ gtk_file_chooser_default_get_paths (GtkF GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); struct get_paths_closure info; + if (impl->operation_mode == OPERATION_MODE_SEARCH) + return search_get_selected_paths (impl); + info.impl = impl; info.result = NULL; info.path_from_entry = NULL; @@ -5926,13 +6280,16 @@ gtk_file_chooser_default_add_shortcut_fo pos = shortcuts_get_pos_for_shortcut_folder (impl, impl->num_shortcuts); - result = shortcuts_insert_path (impl, pos, FALSE, NULL, path, NULL, FALSE, error); + result = shortcuts_insert_path (impl, pos, SHORTCUT_TYPE_PATH, NULL, path, NULL, FALSE, error); if (result) impl->num_shortcuts++; - if (impl->shortcuts_filter_model) - gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->shortcuts_filter_model)); + if (impl->shortcuts_pane_filter_model) + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->shortcuts_pane_filter_model)); + + if (impl->shortcuts_combo_filter_model) + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->shortcuts_combo_filter_model)); return result; } @@ -5958,15 +6315,15 @@ gtk_file_chooser_default_remove_shortcut for (i = 0; i < impl->num_shortcuts; i++) { gpointer col_data; - gboolean is_volume; + ShortcutType shortcut_type; GtkFilePath *shortcut; gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter, SHORTCUTS_COL_DATA, &col_data, - SHORTCUTS_COL_IS_VOLUME, &is_volume, + SHORTCUTS_COL_TYPE, &shortcut_type, -1); g_assert (col_data != NULL); - g_assert (!is_volume); + g_assert (shortcut_type == SHORTCUT_TYPE_PATH); shortcut = col_data; if (gtk_file_path_compare (shortcut, path) == 0) @@ -6014,15 +6371,15 @@ gtk_file_chooser_default_list_shortcut_f for (i = 0; i < impl->num_shortcuts; i++) { gpointer col_data; - gboolean is_volume; + ShortcutType shortcut_type; GtkFilePath *shortcut; gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter, SHORTCUTS_COL_DATA, &col_data, - SHORTCUTS_COL_IS_VOLUME, &is_volume, + SHORTCUTS_COL_TYPE, &shortcut_type, -1); g_assert (col_data != NULL); - g_assert (!is_volume); + g_assert (shortcut_type == SHORTCUT_TYPE_PATH); shortcut = col_data; list = g_slist_prepend (list, gtk_file_path_copy (shortcut)); @@ -6353,6 +6710,23 @@ should_respond_after_confirm_overwrite ( } } +/* Gives the focus to the browse tree view only if it is visible */ +static void +focus_browse_tree_view_if_possible (GtkFileChooserDefault *impl) +{ + gboolean do_focus; + + if ((impl->action == GTK_FILE_CHOOSER_ACTION_SAVE + || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) + && !gtk_expander_get_expanded (GTK_EXPANDER (impl->save_expander))) + do_focus = FALSE; + else + do_focus = TRUE; + + if (do_focus) + gtk_widget_grab_focus (impl->browse_files_tree_view); +} + /* Implementation for GtkFileChooserEmbed::should_respond() */ static gboolean gtk_file_chooser_default_should_respond (GtkFileChooserEmbed *chooser_embed) @@ -6399,6 +6773,9 @@ gtk_file_chooser_default_should_respond g_assert (impl->action >= GTK_FILE_CHOOSER_ACTION_OPEN && impl->action <= GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER); + if (impl->operation_mode == OPERATION_MODE_SEARCH) + return search_should_respond (impl); + selection_check (impl, &num_selected, &all_files, &all_folders); if (num_selected > 2) @@ -6562,8 +6939,7 @@ gtk_file_chooser_default_should_respond if (shortcuts_get_selected (impl, &iter)) { shortcuts_activate_iter (impl, &iter); - - gtk_widget_grab_focus (impl->browse_files_tree_view); + focus_browse_tree_view_if_possible (impl); } else goto file_list; @@ -6577,6 +6953,11 @@ gtk_file_chooser_default_should_respond */ goto file_list; } + else if (impl->operation_mode == OPERATION_MODE_SEARCH && impl->toplevel_last_focus_widget == impl->search_entry) + { + search_entry_activate_cb (GTK_ENTRY (impl->search_entry), impl); + return FALSE; + } else /* The focus is on a dialog's action area button or something else */ if (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE @@ -6613,6 +6994,424 @@ gtk_file_chooser_default_initial_focus ( gtk_widget_grab_focus (widget); } +/* Callback used from gtk_tree_selection_selected_foreach(); gets the selected GtkFilePaths */ +static void +search_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, SEARCH_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 search mode */ +static GSList * +search_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, search_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 search list. + */ +static gboolean +search_should_respond (GtkFileChooserDefault *impl) +{ + GtkTreeSelection *selection; + + g_assert (impl->operation_mode == OPERATION_MODE_SEARCH); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view)); + return (gtk_tree_selection_count_selected_rows (selection) != 0); +} + +/* Adds one hit from Beagle to the search_model */ +static void +search_add_hit (GtkFileChooserDefault *impl, + BeagleHit *hit) +{ + const char *uri; + GtkFilePath *path; + char *filename; + char *display_name; + char *collation_key; + struct stat statbuf; + struct stat *statbuf_copy; + GtkTreeIter iter; + + uri = beagle_hit_get_uri (hit); + + path = gtk_file_system_uri_to_path (impl->file_system, uri); + if (!path) + return; + + filename = gtk_file_system_path_to_filename (impl->file_system, path); + if (!filename) + { + gtk_file_path_free (path); + return; + } + + if (stat (filename, &statbuf) != 0) + { + gtk_file_path_free (path); + g_free (filename); + return; + } + + statbuf_copy = g_new (struct stat, 1); + *statbuf_copy = statbuf; + + display_name = g_filename_display_name (filename); + collation_key = g_utf8_collate_key_for_filename (display_name, -1); + + gtk_list_store_insert_with_values (impl->search_model, &iter, -1, + SEARCH_MODEL_COL_PATH, path, + SEARCH_MODEL_COL_DISPLAY_NAME, display_name, + SEARCH_MODEL_COL_COLLATION_KEY, collation_key, + SEARCH_MODEL_COL_STAT, statbuf_copy, + -1); +} + +/* Callback used from BeagleQuery when we get new hits */ +static void +search_query_hits_added_cb (BeagleQuery *query, + BeagleHitsAddedResponse *response, + gpointer data) +{ + GtkFileChooserDefault *impl; + GSList *hits, *l; + + impl = GTK_FILE_CHOOSER_DEFAULT (data); + + /* The list we get from beagle_hits_added_response_get_hits() is immutable, so don't free it */ + hits = beagle_hits_added_response_get_hits (response); + for (l = hits; l; l = l->next) + { + BeagleHit *hit; + + hit = BEAGLE_HIT (l->data); + search_add_hit (impl, hit); + } +} + +/* Callback used from BeagleQuery when the query is done running */ +static void +search_query_finished_cb (BeagleQuery *query, + BeagleFinishedResponse *response, + gpointer data) +{ + GtkFileChooserDefault *impl; + + impl = GTK_FILE_CHOOSER_DEFAULT (data); + + /* 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 BeagleClient. It would be + * better if beagle_client_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 Beagle " + "daemon. Please make sure Beagle is running.")); +} + +/* Frees the data in the search_model */ +static void +search_clear_model (GtkFileChooserDefault *impl, gboolean remove_from_treeview) +{ + GtkTreeIter iter; + + if (!impl->search_model) + return; + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (impl->search_model), &iter)) + do + { + GtkFilePath *path; + char *display_name; + char *collation_key; + struct stat *statbuf; + + gtk_tree_model_get (GTK_TREE_MODEL (impl->search_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, + -1); + + gtk_file_path_free (path); + g_free (display_name); + g_free (collation_key); + g_free (statbuf); + } + while (gtk_tree_model_iter_next (GTK_TREE_MODEL (impl->search_model), &iter)); + + g_object_unref (impl->search_model); + impl->search_model = 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) +{ + if (impl->search_query) + { + g_object_unref (impl->search_query); + impl->search_query = NULL; + } + + if (impl->search_client) + { + g_object_unref (impl->search_client); + impl->search_client = 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) +{ + if (impl->operation_mode == OPERATION_MODE_BROWSE) + return; + + search_stop_searching (impl); + 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); + + 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) +{ + const char *collation_key_a, *collation_key_b; + + gtk_tree_model_get (model, a, SEARCH_MODEL_COL_COLLATION_KEY, &collation_key_a, -1); + gtk_tree_model_get (model, b, SEARCH_MODEL_COL_COLLATION_KEY, &collation_key_b, -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) +{ + const struct stat *statbuf_a, *statbuf_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 (model, a, SEARCH_MODEL_COL_STAT, &statbuf_a, -1); + gtk_tree_model_get (model, b, SEARCH_MODEL_COL_STAT, &statbuf_b, -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; +} + +/* Creates the search_model and puts it in the tree view */ +static void +search_setup_model (GtkFileChooserDefault *impl) +{ + g_assert (impl->search_model == 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 + * + * Keep this in sync with the enumeration defined near the beginning of this file. + */ + impl->search_model = gtk_list_store_new (4, + G_TYPE_POINTER, + G_TYPE_POINTER, + G_TYPE_POINTER, + G_TYPE_POINTER); + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (impl->search_model), + SEARCH_MODEL_COL_PATH, + search_column_path_sort_func, + impl, + NULL); + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (impl->search_model), + SEARCH_MODEL_COL_STAT, + search_column_mtime_sort_func, + impl, + NULL); + + gtk_tree_view_set_model (GTK_TREE_VIEW (impl->browse_files_tree_view), GTK_TREE_MODEL (impl->search_model)); +} + +/* Creates a new Beagle query with the specified text and launches it */ +static void +search_start_query (GtkFileChooserDefault *impl, + const char *query_text) +{ + GError *error; + BeagleQueryPartProperty *part; + + search_stop_searching (impl); + search_clear_model (impl, TRUE); + search_setup_model (impl); + set_busy_cursor (impl, TRUE); + + impl->search_client = beagle_client_new (NULL); + if (!impl->search_client) + { + set_busy_cursor (impl, FALSE); + search_error_could_not_create_client (impl); /* lame; we don't get an error code or anything */ + return; + } + + impl->search_query = beagle_query_new (); + g_signal_connect (impl->search_query, "hits-added", + G_CALLBACK (search_query_hits_added_cb), impl); + g_signal_connect (impl->search_query, "finished", + G_CALLBACK (search_query_finished_cb), impl); + + beagle_query_add_text (impl->search_query, query_text); + beagle_query_add_hit_type (impl->search_query, "File"); + + /* Don't let the system documentation appear in the hits */ + + part = beagle_query_part_property_new (); + beagle_query_part_set_logic (BEAGLE_QUERY_PART (part), BEAGLE_QUERY_PART_LOGIC_PROHIBITED); + beagle_query_part_property_set_key (part, "beagle:Source"); + beagle_query_part_property_set_value (part, "documentation"); + beagle_query_part_property_set_property_type (part, BEAGLE_PROPERTY_TYPE_KEYWORD); + beagle_query_add_part (impl->search_query, BEAGLE_QUERY_PART (part)); /* the query takes ownership of the part */ + + error = NULL; + if (!beagle_client_send_request_async (impl->search_client, BEAGLE_REQUEST (impl->search_query), &error)) + { + search_stop_searching (impl); + error_message (impl, + _("Could not send the search request"), + error->message); + g_error_free (error); + set_busy_cursor (impl, FALSE); + } +} + +/* 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; + + 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 = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL (label), "Search:"); + gtk_box_pack_start (GTK_BOX (impl->search_hbox), label, FALSE, FALSE, 0); + + impl->search_entry = gtk_entry_new (); + 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); + + gtk_widget_hide (impl->browse_path_bar); + gtk_widget_hide (impl->browse_new_folder_button); + + gtk_box_pack_start (GTK_BOX (impl->browse_path_bar_hbox), impl->search_hbox, TRUE, TRUE, 0); + gtk_widget_show_all (impl->search_hbox); + + 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) +{ + if (impl->operation_mode == OPERATION_MODE_SEARCH) + { + gtk_widget_grab_focus (impl->search_entry); + return; + } + + impl->operation_mode = OPERATION_MODE_SEARCH; + + g_assert (impl->search_hbox == NULL); + g_assert (impl->search_entry == NULL); + g_assert (impl->search_model == NULL); + + stop_loading_and_clear_list_model (impl); + search_setup_widgets (impl); + file_list_set_sort_column_ids (impl); +} + static void set_current_filter (GtkFileChooserDefault *impl, GtkFileFilter *filter) @@ -6662,26 +7461,45 @@ check_preview_change (GtkFileChooserDefa { GtkTreePath *cursor_path; const GtkFilePath *new_path; - const GtkFileInfo *new_info; + const char *new_display_name; gtk_tree_view_get_cursor (GTK_TREE_VIEW (impl->browse_files_tree_view), &cursor_path, NULL); - if (cursor_path && impl->sort_model) - { - GtkTreeIter iter; - GtkTreeIter child_iter; - - 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 = NULL; + new_display_name = NULL; - 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); - } - else + if (cursor_path) { - new_path = NULL; - new_info = NULL; + if (impl->operation_mode == OPERATION_MODE_BROWSE) + { + if (impl->sort_model) + { + GtkTreeIter iter; + GtkTreeIter child_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); + new_display_name = gtk_file_info_get_display_name (new_info); + } + } + else + { + GtkTreeIter iter; + + gtk_tree_model_get_iter (GTK_TREE_MODEL (impl->search_model), &iter, cursor_path); + gtk_tree_path_free (cursor_path); + + gtk_tree_model_get (GTK_TREE_MODEL (impl->search_model), &iter, + SEARCH_MODEL_COL_PATH, &new_path, + SEARCH_MODEL_COL_DISPLAY_NAME, &new_display_name, + -1); + } } if (new_path != impl->preview_path && @@ -6697,7 +7515,7 @@ check_preview_change (GtkFileChooserDefa if (new_path) { impl->preview_path = gtk_file_path_copy (new_path); - impl->preview_display_name = g_strdup (gtk_file_info_get_display_name (new_info)); + impl->preview_display_name = g_strdup (new_display_name); } else { @@ -6721,6 +7539,8 @@ shortcuts_activate_volume (GtkFileChoose { GtkFilePath *path; + search_switch_to_browse_mode (impl); + /* 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. */ @@ -6757,6 +7577,8 @@ shortcuts_activate_volume (GtkFileChoose if (path != NULL) { change_folder_and_display_error (impl, path); + focus_browse_tree_view_if_possible (impl); + gtk_file_path_free (path); } @@ -6771,17 +7593,16 @@ shortcuts_activate_iter (GtkFileChooserD GtkTreeIter *iter) { gpointer col_data; - gboolean is_volume; + ShortcutType shortcut_type; gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), iter, SHORTCUTS_COL_DATA, &col_data, - SHORTCUTS_COL_IS_VOLUME, &is_volume, + SHORTCUTS_COL_TYPE, &shortcut_type, -1); - if (!col_data) - return; /* We are on a separator */ - - if (is_volume) + if (shortcut_type == SHORTCUT_TYPE_SEPARATOR) + return; + else if (shortcut_type == SHORTCUT_TYPE_VOLUME) { GtkFileSystemVolume *volume; @@ -6789,12 +7610,17 @@ shortcuts_activate_iter (GtkFileChooserD shortcuts_activate_volume (impl, volume); } - else + else if (shortcut_type == SHORTCUT_TYPE_PATH) { const GtkFilePath *file_path; file_path = col_data; change_folder_and_display_error (impl, file_path); + focus_browse_tree_view_if_possible (impl); + } + else if (shortcut_type == SHORTCUT_TYPE_SEARCH) + { + search_activate (impl); } } @@ -6808,15 +7634,13 @@ shortcuts_row_activated_cb (GtkTreeView GtkTreeIter iter; GtkTreeIter child_iter; - if (!gtk_tree_model_get_iter (impl->shortcuts_filter_model, &iter, path)) + if (!gtk_tree_model_get_iter (impl->shortcuts_pane_filter_model, &iter, path)) return; - gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (impl->shortcuts_filter_model), + gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (impl->shortcuts_pane_filter_model), &child_iter, &iter); shortcuts_activate_iter (impl, &child_iter); - - gtk_widget_grab_focus (impl->browse_files_tree_view); } /* Handler for GtkWidget::key-press-event on the shortcuts list */ @@ -6856,8 +7680,15 @@ shortcuts_select_func (GtkTreeSelection gpointer data) { GtkFileChooserDefault *impl = data; + GtkTreeIter filter_iter; + ShortcutType shortcut_type; + + if (!gtk_tree_model_get_iter (impl->shortcuts_pane_filter_model, &filter_iter, path)) + g_assert_not_reached (); + + gtk_tree_model_get (impl->shortcuts_pane_filter_model, &filter_iter, SHORTCUTS_COL_TYPE, &shortcut_type, -1); - return (*gtk_tree_path_get_indices (path) != shortcuts_get_index (impl, SHORTCUTS_BOOKMARKS_SEPARATOR)); + return (shortcut_type != SHORTCUT_TYPE_SEPARATOR); } static gboolean @@ -6869,6 +7700,9 @@ list_select_func (GtkTreeSelection *se { GtkFileChooserDefault *impl = data; + if (impl->operation_mode == OPERATION_MODE_SEARCH) + return TRUE; + if (impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) { @@ -6894,7 +7728,7 @@ list_selection_changed (GtkTreeSelection GtkFileChooserDefault *impl) { /* See if we are in the new folder editable row for Save mode */ - if (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE) + if (impl->operation_mode == OPERATION_MODE_BROWSE && impl->action == GTK_FILE_CHOOSER_ACTION_SAVE) { const GtkFileInfo *info; gboolean had_selection; @@ -6909,6 +7743,8 @@ list_selection_changed (GtkTreeSelection out: + /* AQUI: cambiar las siguientes funciones para que acepten SEARCH */ + update_chooser_entry (impl); check_preview_change (impl); bookmarks_check_add_sensitivity (impl); @@ -6926,6 +7762,12 @@ list_row_activated (GtkTreeView GtkTreeIter iter, child_iter; const GtkFileInfo *info; + if (impl->operation_mode == OPERATION_MODE_SEARCH) + { + g_signal_emit_by_name (impl, "file-activated"); + return; + } + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (impl->sort_model), &iter, path)) return; @@ -6996,6 +7838,15 @@ list_icon_data_func (GtkTreeViewColumn * const GtkFileInfo *info; gboolean sensitive = TRUE; + if (impl->operation_mode == OPERATION_MODE_SEARCH) + { + g_object_set (cell, + "pixbuf", NULL, + "sensitive", TRUE, + NULL); + return; + } + profile_start ("start", NULL); info = get_list_file_info (impl, iter); @@ -7040,19 +7891,38 @@ list_name_data_func (GtkTreeViewColumn * gpointer data) { GtkFileChooserDefault *impl = data; - const GtkFileInfo *info = get_list_file_info (impl, iter); - gboolean sensitive = TRUE; + const GtkFileInfo *info; + gboolean sensitive; + + if (impl->operation_mode == OPERATION_MODE_SEARCH) + { + char *display_name; + + gtk_tree_model_get (GTK_TREE_MODEL (impl->search_model), iter, + SEARCH_MODEL_COL_DISPLAY_NAME, &display_name, + -1); + g_object_set (cell, + "text", display_name, + "sensitive", TRUE, + "ellipsize", PANGO_ELLIPSIZE_START, + NULL); + return; + } + + info = get_list_file_info (impl, iter); + sensitive = TRUE; if (!info) { g_object_set (cell, "text", _("Type name of new folder"), + "sensitive", TRUE, + "ellipsize", PANGO_ELLIPSIZE_NONE, NULL); return; } - if (impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) { @@ -7062,6 +7932,7 @@ list_name_data_func (GtkTreeViewColumn * g_object_set (cell, "text", gtk_file_info_get_display_name (info), "sensitive", sensitive, + "ellipsize", PANGO_ELLIPSIZE_END, NULL); } @@ -7118,7 +7989,6 @@ list_mtime_data_func (GtkTreeViewColumn gpointer data) { GtkFileChooserDefault *impl; - const GtkFileInfo *info; GtkFileTime time_mtime, time_now; GDate mtime, now; int days_diff; @@ -7127,17 +7997,35 @@ list_mtime_data_func (GtkTreeViewColumn impl = data; - info = get_list_file_info (impl, iter); - if (!info) + if (impl->operation_mode == OPERATION_MODE_SEARCH) { - g_object_set (cell, - "text", "", - "sensitive", TRUE, - NULL); - return; + struct stat *statbuf; + + gtk_tree_model_get (GTK_TREE_MODEL (impl->search_model), iter, + SEARCH_MODEL_COL_STAT, &statbuf, + -1); + time_mtime = statbuf->st_mtime; } + else + { + const GtkFileInfo *info; - time_mtime = gtk_file_info_get_modification_time (info); + info = get_list_file_info (impl, iter); + if (!info) + { + g_object_set (cell, + "text", "", + "sensitive", TRUE, + NULL); + return; + } + + time_mtime = gtk_file_info_get_modification_time (info); + + 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); + } if (time_mtime == 0) strcpy (buf, _("Unknown")); @@ -7168,10 +8056,6 @@ list_mtime_data_func (GtkTreeViewColumn } } - 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); - g_object_set (cell, "text", buf, "sensitive", sensitive, @@ -7461,6 +8345,7 @@ switch_to_shortcut (GtkFileChooserDefaul g_assert_not_reached (); shortcuts_activate_iter (impl, &iter); + focus_browse_tree_view_if_possible (impl); } /* Handler for the "home-folder" keybinding signal */ @@ -7484,26 +8369,26 @@ desktop_folder_handler (GtkFileChooserDe /* Drag and drop interfaces */ static void -_shortcuts_model_filter_class_init (ShortcutsModelFilterClass *class) +_shortcuts_pane_model_filter_class_init (ShortcutsPaneModelFilterClass *class) { } static void -_shortcuts_model_filter_init (ShortcutsModelFilter *model) +_shortcuts_pane_model_filter_init (ShortcutsPaneModelFilter *model) { model->impl = NULL; } /* GtkTreeDragSource::row_draggable implementation for the shortcuts filter model */ static gboolean -shortcuts_model_filter_row_draggable (GtkTreeDragSource *drag_source, - GtkTreePath *path) +shortcuts_pane_model_filter_row_draggable (GtkTreeDragSource *drag_source, + GtkTreePath *path) { - ShortcutsModelFilter *model; + ShortcutsPaneModelFilter *model; int pos; int bookmarks_pos; - model = SHORTCUTS_MODEL_FILTER (drag_source); + model = SHORTCUTS_PANE_MODEL_FILTER (drag_source); pos = *gtk_tree_path_get_indices (path); bookmarks_pos = shortcuts_get_index (model->impl, SHORTCUTS_BOOKMARKS); @@ -7513,13 +8398,13 @@ shortcuts_model_filter_row_draggable (Gt /* GtkTreeDragSource::drag_data_get implementation for the shortcuts filter model */ static gboolean -shortcuts_model_filter_drag_data_get (GtkTreeDragSource *drag_source, +shortcuts_pane_model_filter_drag_data_get (GtkTreeDragSource *drag_source, GtkTreePath *path, GtkSelectionData *selection_data) { - ShortcutsModelFilter *model; + ShortcutsPaneModelFilter *model; - model = SHORTCUTS_MODEL_FILTER (drag_source); + model = SHORTCUTS_PANE_MODEL_FILTER (drag_source); /* FIXME */ @@ -7528,30 +8413,30 @@ shortcuts_model_filter_drag_data_get (Gt /* Fill the GtkTreeDragSourceIface vtable */ static void -shortcuts_model_filter_drag_source_iface_init (GtkTreeDragSourceIface *iface) +shortcuts_pane_model_filter_drag_source_iface_init (GtkTreeDragSourceIface *iface) { - iface->row_draggable = shortcuts_model_filter_row_draggable; - iface->drag_data_get = shortcuts_model_filter_drag_data_get; + iface->row_draggable = shortcuts_pane_model_filter_row_draggable; + iface->drag_data_get = shortcuts_pane_model_filter_drag_data_get; } #if 0 /* Fill the GtkTreeDragDestIface vtable */ static void -shortcuts_model_filter_drag_dest_iface_init (GtkTreeDragDestIface *iface) +shortcuts_pane_model_filter_drag_dest_iface_init (GtkTreeDragDestIface *iface) { - iface->drag_data_received = shortcuts_model_filter_drag_data_received; - iface->row_drop_possible = shortcuts_model_filter_row_drop_possible; + iface->drag_data_received = shortcuts_pane_model_filter_drag_data_received; + iface->row_drop_possible = shortcuts_pane_model_filter_row_drop_possible; } #endif static GtkTreeModel * -shortcuts_model_filter_new (GtkFileChooserDefault *impl, - GtkTreeModel *child_model, - GtkTreePath *root) +shortcuts_pane_model_filter_new (GtkFileChooserDefault *impl, + GtkTreeModel *child_model, + GtkTreePath *root) { - ShortcutsModelFilter *model; + ShortcutsPaneModelFilter *model; - model = g_object_new (SHORTCUTS_MODEL_FILTER_TYPE, + model = g_object_new (SHORTCUTS_PANE_MODEL_FILTER_TYPE, "child-model", child_model, "virtual-root", root, NULL);