From d3aeccf774fd13c4efdbdb873bb2258e1b94f853 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Wed, 2 May 2007 22:51:43 +0000 Subject: [PATCH] Add search file support in the GtkFileChooser. Original patch by Federico 2007-05-02 Emmanuele Bassi Add search file support in the GtkFileChooser. Original patch by Federico Mena Quintero; patch updated by Matthias Clasen. See bug #344785. * gtk/gtksearchengine.[ch]: Private search engine abstraction object. * gtk/gtksearchenginebeagle.[ch]: Private search engine implementation using libbeagle (via g_module_open()). * gtk/gtksearchenginesimple.[ch]: Private search engine implementation using file tree walking. * gtk/gtksearchenginetracker.[ch]: Private earch engine implementation using libtracker (via g_module_open()). * gtk/gtkquery.[ch]: Private query object for the search engines. * gtk/gtkfilechooserprivate.h: * gtk/gtkfilechooserdefault.c: Use the GtkSearchEngine to query a search engine backend using GtkQuery; create a new operating mode, OPERATION_MODE_SEARCH, and call the common operating mode OPERATION_MODE_BROWSE; add support for virtual shortcuts inside the shortcuts model and create a new "Search" virtual shortcut. * gtk/Makefile.am: Update the build with the new files svn path=/trunk/; revision=17783 --- ChangeLog | 31 + gtk/Makefile.am | 10 + gtk/gtkfilechooserdefault.c | 1499 ++++++++++++++++++++++++++-------- gtk/gtkfilechooserprivate.h | 35 +- gtk/gtkquery.c | 142 ++++ gtk/gtkquery.h | 74 ++ gtk/gtksearchengine.c | 197 +++++ gtk/gtksearchengine.h | 91 +++ gtk/gtksearchenginebeagle.c | 422 ++++++++++ gtk/gtksearchenginebeagle.h | 59 ++ gtk/gtksearchenginesimple.c | 378 +++++++++ gtk/gtksearchenginesimple.h | 59 ++ gtk/gtksearchenginetracker.c | 362 ++++++++ gtk/gtksearchenginetracker.h | 59 ++ 14 files changed, 3088 insertions(+), 330 deletions(-) create mode 100644 gtk/gtkquery.c create mode 100644 gtk/gtkquery.h create mode 100644 gtk/gtksearchengine.c create mode 100644 gtk/gtksearchengine.h create mode 100644 gtk/gtksearchenginebeagle.c create mode 100644 gtk/gtksearchenginebeagle.h create mode 100644 gtk/gtksearchenginesimple.c create mode 100644 gtk/gtksearchenginesimple.h create mode 100644 gtk/gtksearchenginetracker.c create mode 100644 gtk/gtksearchenginetracker.h diff --git a/ChangeLog b/ChangeLog index 80fc6cbfdb..10a7f098cb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,34 @@ +2007-05-02 Emmanuele Bassi + + Add search file support in the GtkFileChooser. Original patch + by Federico Mena Quintero; patch updated by Matthias Clasen. + See bug #344785. + + * gtk/gtksearchengine.[ch]: Private search engine abstraction + object. + + * gtk/gtksearchenginebeagle.[ch]: Private search engine + implementation using libbeagle (via g_module_open()). + + * gtk/gtksearchenginesimple.[ch]: Private search engine + implementation using file tree walking. + + * gtk/gtksearchenginetracker.[ch]: Private earch engine + implementation using libtracker (via g_module_open()). + + * gtk/gtkquery.[ch]: Private query object for the search + engines. + + * gtk/gtkfilechooserprivate.h: + * gtk/gtkfilechooserdefault.c: Use the GtkSearchEngine to + query a search engine backend using GtkQuery; create a new + operating mode, OPERATION_MODE_SEARCH, and call the common + operating mode OPERATION_MODE_BROWSE; add support for virtual + shortcuts inside the shortcuts model and create a new "Search" + virtual shortcut. + + * gtk/Makefile.am: Update the build with the new files + 2007-05-02 Armin Burgmeier * gtk/gtkcombobox.c: Destroy the menu in dispose instead of diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 4a5b0764e1..3097d20544 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -335,6 +335,11 @@ gtk_semi_private_h_sources = \ # GTK+ header files that don't get installed gtk_private_h_sources = \ + gtkquery.h \ + gtksearchengine.h \ + gtksearchenginebeagle.h \ + gtksearchenginetracker.h\ + gtksearchenginesimple.h \ gtkdndcursors.h \ gtkentryprivate.h \ gtkfilechooserdefault.h \ @@ -376,6 +381,11 @@ gtk_private_h_sources = \ # GTK+ C sources to build the library from gtk_base_c_sources = \ + gtkquery.c \ + gtksearchengine.c \ + gtksearchenginebeagle.c \ + gtksearchenginetracker.c\ + gtksearchenginesimple.c \ fnmatch.c \ gtkaboutdialog.c \ gtkaccelgroup.c \ diff --git a/gtk/gtkfilechooserdefault.c b/gtk/gtkfilechooserdefault.c index a4a596f9aa..d54e1d606c 100644 --- a/gtk/gtkfilechooserdefault.c +++ b/gtk/gtkfilechooserdefault.c @@ -78,6 +78,8 @@ #include #include #include +#include +#include #ifdef HAVE_UNISTD_H @@ -172,13 +174,20 @@ 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_HANDLE, 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, @@ -187,6 +196,16 @@ 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, @@ -226,9 +245,19 @@ static const GtkTargetEntry file_list_dest_targets[] = { static const int num_file_list_dest_targets = (sizeof (file_list_dest_targets) / sizeof (file_list_dest_targets[0])); +static gboolean +search_is_possible (GtkFileChooserDefault *impl) +{ + if (impl->search_engine == NULL) + impl->search_engine = _gtk_search_engine_new (); + + return impl->search_engine != NULL; +} /* Interesting places in the shortcuts bar */ typedef enum { + SHORTCUTS_SEARCH, + SHORTCUTS_SEARCH_SEPARATOR, SHORTCUTS_HOME, SHORTCUTS_DESKTOP, SHORTCUTS_VOLUMES, @@ -423,6 +452,14 @@ static void location_button_toggled_cb (GtkToggleButton *toggle, GtkFileChooserDefault *impl); static void location_switch_to_path_bar (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); @@ -432,26 +469,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); @@ -680,6 +717,7 @@ _gtk_file_chooser_default_init (GtkFileChooserDefault *impl) impl->reload_state = RELOAD_EMPTY; impl->pending_select_paths = NULL; impl->location_mode = LOCATION_MODE_PATH_BAR; + impl->operation_mode = OPERATION_MODE_BROWSE; gtk_box_set_spacing (GTK_BOX (impl), 12); @@ -695,22 +733,24 @@ shortcuts_free_row_data (GtkFileChooserDefault *impl, GtkTreeIter *iter) { gpointer col_data; - gboolean is_volume; + ShortcutType shortcut_type; GtkFileSystemHandle *handle; 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, SHORTCUTS_COL_HANDLE, &handle, -1); if (handle) gtk_file_system_cancel_operation (handle); - 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; @@ -721,6 +761,8 @@ shortcuts_free_row_data (GtkFileChooserDefault *impl, { GtkFilePath *path; + g_assert (shortcut_type == SHORTCUT_TYPE_PATH); + path = col_data; gtk_file_path_free (path); } @@ -807,8 +849,11 @@ gtk_file_chooser_default_finalize (GObject *object) 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); @@ -846,6 +891,8 @@ gtk_file_chooser_default_finalize (GObject *object) 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); @@ -1098,6 +1145,14 @@ set_preview_widget (GtkFileChooserDefault *impl, 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 */ struct ReloadIconsData { @@ -1168,19 +1223,20 @@ shortcuts_reload_icons (GtkFileChooserDefault *impl) 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; @@ -1195,46 +1251,53 @@ shortcuts_reload_icons (GtkFileChooserDefault *impl) if (pixbuf) g_object_unref (pixbuf); } - else if (gtk_file_system_path_is_local (impl->file_system, (GtkFilePath *)data)) + else if (shortcut_type == SHORTCUT_TYPE_PATH) + { + if (gtk_file_system_path_is_local (impl->file_system, (GtkFilePath *)data)) + { + const GtkFilePath *path; + struct ReloadIconsData *info; + GtkTreePath *tree_path; + GtkFileSystemHandle *handle; + + path = data; + + info = g_new0 (struct ReloadIconsData, 1); + info->impl = g_object_ref (impl); + tree_path = gtk_tree_model_get_path (GTK_TREE_MODEL (impl->shortcuts_model), &iter); + info->row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (impl->shortcuts_model), tree_path); + gtk_tree_path_free (tree_path); + + handle = gtk_file_system_get_info (impl->file_system, path, + GTK_FILE_INFO_ICON, + shortcuts_reload_icons_get_info_cb, + info); + impl->reload_icon_handles = g_slist_append (impl->reload_icon_handles, handle); + } + else + { + GtkIconTheme *icon_theme; + + /* Don't call get_info for remote paths to avoid latency and + * auth dialogs. + * If we switch to a better bookmarks file format (XBEL), we + * should use mime info to get a better icon. + */ + icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (impl))); + pixbuf = gtk_icon_theme_load_icon (icon_theme, "gnome-fs-directory", + impl->icon_size, 0, NULL); + + gtk_list_store_set (impl->shortcuts_model, &iter, + SHORTCUTS_COL_PIXBUF, pixbuf, + -1); + + if (pixbuf) + g_object_unref (pixbuf); + } + } + else if (shortcut_type == SHORTCUT_TYPE_SEARCH) { - const GtkFilePath *path; - struct ReloadIconsData *info; - GtkTreePath *tree_path; - GtkFileSystemHandle *handle; - - path = data; - - info = g_new0 (struct ReloadIconsData, 1); - info->impl = g_object_ref (impl); - tree_path = gtk_tree_model_get_path (GTK_TREE_MODEL (impl->shortcuts_model), &iter); - info->row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (impl->shortcuts_model), tree_path); - gtk_tree_path_free (tree_path); - - handle = gtk_file_system_get_info (impl->file_system, path, - GTK_FILE_INFO_ICON, - shortcuts_reload_icons_get_info_cb, - info); - impl->reload_icon_handles = g_slist_append (impl->reload_icon_handles, handle); - } - else - { - GtkIconTheme *icon_theme; - - /* Don't call get_info for remote paths to avoid latency and - * auth dialogs. - * If we switch to a better bookmarks file format (XBEL), we - * should use mime info to get a better icon. - */ - icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (impl))); - pixbuf = gtk_icon_theme_load_icon (icon_theme, "gnome-fs-directory", - impl->icon_size, 0, NULL); - - gtk_list_store_set (impl->shortcuts_model, &iter, - SHORTCUTS_COL_PIXBUF, pixbuf, - -1); - - if (pixbuf) - g_object_unref (pixbuf); + pixbuf = render_search_icon (impl); } } } @@ -1366,7 +1429,6 @@ get_file_info_finished (GtkFileSystemHandle *handle, { gint pos = -1; gboolean cancelled = handle->cancelled; - gboolean is_volume = FALSE; GdkPixbuf *pixbuf; GtkTreePath *path; GtkTreeIter iter; @@ -1431,12 +1493,15 @@ get_file_info_finished (GtkFileSystemHandle *handle, SHORTCUTS_COL_PIXBUF, pixbuf, SHORTCUTS_COL_PIXBUF_VISIBLE, TRUE, SHORTCUTS_COL_NAME, request->label_copy, - SHORTCUTS_COL_IS_VOLUME, is_volume, + SHORTCUTS_COL_TYPE, SHORTCUT_TYPE_PATH, SHORTCUTS_COL_REMOVABLE, request->removable, -1); - if (request->impl->shortcuts_filter_model) - gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (request->impl->shortcuts_filter_model)); + if (request->impl->shortcuts_pane_filter_model) + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (request->impl->shortcuts_pane_filter_model)); + + if (request->impl->shortcuts_combo_filter_model) + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (request->impl->shortcuts_combo_filter_model)); if (request->type == SHORTCUTS_CURRENT_FOLDER && request->impl->save_folder_combo != NULL) @@ -1448,7 +1513,7 @@ get_file_info_finished (GtkFileSystemHandle *handle, g_signal_handlers_block_by_func (request->impl->save_folder_combo, G_CALLBACK (save_folder_combo_changed_cb), request->impl); - gtk_combo_box_set_active (GTK_COMBO_BOX (request->impl->save_folder_combo), pos); + gtk_combo_box_set_active (GTK_COMBO_BOX (request->impl->save_folder_combo), request->impl->has_search ? pos - 2 : pos); g_signal_handlers_unblock_by_func (request->impl->save_folder_combo, G_CALLBACK (save_folder_combo_changed_cb), request->impl); @@ -1522,7 +1587,7 @@ _gtk_file_chooser_label_for_uri (const gchar *uri) static void shortcuts_insert_path (GtkFileChooserDefault *impl, int pos, - gboolean is_volume, + ShortcutType shortcut_type, GtkFileSystemVolume *volume, const GtkFilePath *path, const char *label, @@ -1535,80 +1600,90 @@ shortcuts_insert_path (GtkFileChooserDefault *impl, GtkTreeIter iter; GtkIconTheme *icon_theme; - 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 if (gtk_file_system_path_is_local (impl->file_system, path)) + else if (shortcut_type == SHORTCUT_TYPE_PATH) { - struct ShortcutsInsertRequest *request; - GtkFileSystemHandle *handle; - GtkTreePath *p; + if (gtk_file_system_path_is_local (impl->file_system, path)) + { + struct ShortcutsInsertRequest *request; + GtkFileSystemHandle *handle; + GtkTreePath *p; - request = g_new0 (struct ShortcutsInsertRequest, 1); - request->impl = g_object_ref (impl); - request->path = gtk_file_path_copy (path); - request->name_only = TRUE; - request->removable = removable; - request->pos = pos; - request->type = type; - if (label) - request->label_copy = g_strdup (label); + request = g_new0 (struct ShortcutsInsertRequest, 1); + request->impl = g_object_ref (impl); + request->path = gtk_file_path_copy (path); + request->name_only = TRUE; + request->removable = removable; + request->pos = pos; + request->type = type; + if (label) + request->label_copy = g_strdup (label); - if (pos == -1) - gtk_list_store_append (impl->shortcuts_model, &iter); + if (pos == -1) + gtk_list_store_append (impl->shortcuts_model, &iter); + else + gtk_list_store_insert (impl->shortcuts_model, &iter, pos); + + p = gtk_tree_model_get_path (GTK_TREE_MODEL (impl->shortcuts_model), &iter); + request->row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (impl->shortcuts_model), p); + gtk_tree_path_free (p); + + handle = gtk_file_system_get_info (request->impl->file_system, request->path, + GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_IS_HIDDEN | GTK_FILE_INFO_ICON, + get_file_info_finished, request); + + gtk_list_store_set (impl->shortcuts_model, &iter, + SHORTCUTS_COL_DATA, gtk_file_path_copy (path), + SHORTCUTS_COL_TYPE, SHORTCUT_TYPE_PATH, + SHORTCUTS_COL_HANDLE, handle, + -1); + + shortcuts_update_count (impl, type, 1); + + return; + } else - gtk_list_store_insert (impl->shortcuts_model, &iter, pos); + { + /* Don't call get_info for remote paths to avoid latency and + * auth dialogs. + */ + data = gtk_file_path_copy (path); + if (label) + label_copy = g_strdup (label); + else + { + gchar *uri; - p = gtk_tree_model_get_path (GTK_TREE_MODEL (impl->shortcuts_model), &iter); - request->row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (impl->shortcuts_model), p); - gtk_tree_path_free (p); + uri = gtk_file_system_path_to_uri (impl->file_system, path); - handle = gtk_file_system_get_info (request->impl->file_system, request->path, - GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_IS_HIDDEN | GTK_FILE_INFO_ICON, - get_file_info_finished, request); + label_copy = _gtk_file_chooser_label_for_uri (uri); - gtk_list_store_set (impl->shortcuts_model, &iter, - SHORTCUTS_COL_DATA, gtk_file_path_copy (path), - SHORTCUTS_COL_IS_VOLUME, is_volume, - SHORTCUTS_COL_HANDLE, handle, - -1); - - shortcuts_update_count (impl, type, 1); + g_free (uri); + } + + /* If we switch to a better bookmarks file format (XBEL), we + * should use mime info to get a better icon. + */ + icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (impl))); + pixbuf = gtk_icon_theme_load_icon (icon_theme, "gnome-fs-directory", + impl->icon_size, 0, NULL); + } + } + else + { + g_assert_not_reached (); return; } - else - { - /* Don't call get_info for remote paths to avoid latency and - * auth dialogs. - */ - data = gtk_file_path_copy (path); - if (label) - label_copy = g_strdup (label); - else - { - gchar *uri; - - uri = gtk_file_system_path_to_uri (impl->file_system, path); - - label_copy = _gtk_file_chooser_label_for_uri (uri); - - g_free (uri); - } - - /* If we switch to a better bookmarks file format (XBEL), we - * should use mime info to get a better icon. - */ - icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (impl))); - pixbuf = gtk_icon_theme_load_icon (icon_theme, "gnome-fs-directory", - impl->icon_size, 0, NULL); - } if (pos == -1) gtk_list_store_append (impl->shortcuts_model, &iter); @@ -1622,13 +1697,16 @@ shortcuts_insert_path (GtkFileChooserDefault *impl, 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, SHORTCUTS_COL_HANDLE, NULL, -1); - 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 (type == SHORTCUTS_CURRENT_FOLDER && impl->save_folder_combo != NULL) { @@ -1640,7 +1718,8 @@ shortcuts_insert_path (GtkFileChooserDefault *impl, g_signal_handlers_block_by_func (impl->save_folder_combo, G_CALLBACK (save_folder_combo_changed_cb), impl); - gtk_combo_box_set_active (GTK_COMBO_BOX (impl->save_folder_combo), combo_pos); + gtk_combo_box_set_active (GTK_COMBO_BOX (impl->save_folder_combo), + impl->has_search ? combo_pos - 2 : combo_pos); g_signal_handlers_unblock_by_func (impl->save_folder_combo, G_CALLBACK (save_folder_combo_changed_cb), impl); @@ -1654,6 +1733,30 @@ shortcuts_insert_path (GtkFileChooserDefault *impl, profile_end ("end", NULL); } +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); + + impl->has_search = TRUE; +} + /* Appends an item for the user's home directory to the shortcuts model */ static void shortcuts_append_home (GtkFileChooserDefault *impl) @@ -1672,7 +1775,8 @@ shortcuts_append_home (GtkFileChooserDefault *impl) home_path = gtk_file_system_filename_to_path (impl->file_system, home); - shortcuts_insert_path (impl, -1, FALSE, NULL, home_path, NULL, FALSE, SHORTCUTS_HOME); + shortcuts_insert_path (impl, -1, SHORTCUT_TYPE_PATH, NULL, home_path, NULL, FALSE, SHORTCUTS_HOME); + impl->has_home = TRUE; gtk_file_path_free (home_path); @@ -1705,7 +1809,9 @@ shortcuts_append_desktop (GtkFileChooserDefault *impl) path = gtk_file_system_filename_to_path (impl->file_system, name); g_free (name); - shortcuts_insert_path (impl, -1, FALSE, NULL, path, _("Desktop"), FALSE, SHORTCUTS_DESKTOP); + shortcuts_insert_path (impl, -1, SHORTCUT_TYPE_PATH, NULL, path, _("Desktop"), FALSE, SHORTCUTS_DESKTOP); + impl->has_desktop = TRUE; + /* 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. */ @@ -1744,7 +1850,7 @@ shortcuts_append_paths (GtkFileChooserDefault *impl, label = gtk_file_system_get_bookmark_label (impl->file_system, path); /* NULL GError, but we don't really want to show error boxes here */ - shortcuts_insert_path (impl, start_row + num_inserted, FALSE, NULL, path, label, TRUE, SHORTCUTS_BOOKMARKS); + shortcuts_insert_path (impl, start_row + num_inserted, SHORTCUT_TYPE_PATH, NULL, path, label, TRUE, SHORTCUTS_BOOKMARKS); num_inserted++; g_free (label); @@ -1763,6 +1869,16 @@ shortcuts_get_index (GtkFileChooserDefault *impl, int n; n = 0; + + if (where == SHORTCUTS_SEARCH) + goto out; + + n += impl->has_search ? 1 : 0; + + if (where == SHORTCUTS_SEARCH_SEPARATOR) + goto out; + + n += impl->has_search ? 1 : 0; if (where == SHORTCUTS_HOME) goto out; @@ -1860,15 +1976,18 @@ shortcuts_add_volumes (GtkFileChooserDefault *impl) } } - shortcuts_insert_path (impl, start_row + n, TRUE, volume, NULL, NULL, FALSE, SHORTCUTS_VOLUMES); + shortcuts_insert_path (impl, start_row + n, SHORTCUT_TYPE_VOLUME, volume, NULL, NULL, FALSE, SHORTCUTS_VOLUMES); n++; } 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; @@ -1878,11 +1997,13 @@ shortcuts_add_volumes (GtkFileChooserDefault *impl) /* Inserts a separator node in the shortcuts list */ static void shortcuts_insert_separator (GtkFileChooserDefault *impl, - ShortcutsIndex where) + ShortcutsIndex where) { 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)); @@ -1891,6 +2012,7 @@ shortcuts_insert_separator (GtkFileChooserDefault *impl, SHORTCUTS_COL_PIXBUF_VISIBLE, FALSE, SHORTCUTS_COL_NAME, NULL, SHORTCUTS_COL_DATA, NULL, + SHORTCUTS_COL_TYPE, SHORTCUT_TYPE_SEPARATOR, -1); } @@ -1903,7 +2025,7 @@ shortcuts_add_bookmarks (GtkFileChooserDefault *impl) GtkTreeIter iter; GtkFilePath *list_selected = NULL; GtkFilePath *combo_selected = NULL; - gboolean is_volume; + ShortcutType shortcut_type; gpointer col_data; profile_start ("start", NULL); @@ -1916,10 +2038,10 @@ shortcuts_add_bookmarks (GtkFileChooserDefault *impl) 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); } @@ -1927,13 +2049,18 @@ shortcuts_add_bookmarks (GtkFileChooserDefault *impl) gtk_combo_box_get_active_iter (GTK_COMBO_BOX (impl->save_folder_combo), &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); gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), - &iter, + &child_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); } @@ -1951,8 +2078,11 @@ shortcuts_add_bookmarks (GtkFileChooserDefault *impl) 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) { @@ -1966,8 +2096,8 @@ shortcuts_add_bookmarks (GtkFileChooserDefault *impl) pos = shortcut_find_position (impl, combo_selected); if (pos != -1) - gtk_combo_box_set_active (GTK_COMBO_BOX (impl->save_folder_combo), - pos); + gtk_combo_box_set_active (GTK_COMBO_BOX (impl->save_folder_combo), + impl->has_search ? pos - 2 : pos); gtk_file_path_free (combo_selected); } @@ -2012,11 +2142,11 @@ shortcuts_add_current_folder (GtkFileChooserDefault *impl) if (base_path && strcmp (gtk_file_path_get_string (base_path), gtk_file_path_get_string (impl->current_folder)) == 0) { - shortcuts_insert_path (impl, pos, TRUE, volume, NULL, NULL, FALSE, SHORTCUTS_CURRENT_FOLDER); + shortcuts_insert_path (impl, pos, SHORTCUT_TYPE_VOLUME, volume, NULL, NULL, FALSE, SHORTCUTS_CURRENT_FOLDER); } else { - shortcuts_insert_path (impl, pos, FALSE, NULL, impl->current_folder, NULL, FALSE, SHORTCUTS_CURRENT_FOLDER); + shortcuts_insert_path (impl, pos, SHORTCUT_TYPE_PATH, NULL, impl->current_folder, NULL, FALSE, SHORTCUTS_CURRENT_FOLDER); if (volume) gtk_file_system_volume_free (impl->file_system, volume); } @@ -2025,7 +2155,8 @@ shortcuts_add_current_folder (GtkFileChooserDefault *impl) gtk_file_path_free (base_path); } else if (impl->save_folder_combo != NULL) - gtk_combo_box_set_active (GTK_COMBO_BOX (impl->save_folder_combo), pos); + gtk_combo_box_set_active (GTK_COMBO_BOX (impl->save_folder_combo), + impl->has_search ? pos - 2 : pos); } /* Updates the current folder row in the shortcuts model */ @@ -2047,9 +2178,9 @@ shortcuts_update_current_folder (GtkFileChooserDefault *impl) /* 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; @@ -2076,11 +2207,17 @@ shortcuts_model_create (GtkFileChooserDefault *impl) 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 */ G_TYPE_POINTER); /* GtkFileSystemHandle */ + if (search_is_possible (impl)) + { + shortcuts_append_search (impl); + shortcuts_insert_separator (impl, SHORTCUTS_SEARCH_SEPARATOR); + } + if (impl->file_system) { shortcuts_append_home (impl); @@ -2088,12 +2225,12 @@ shortcuts_model_create (GtkFileChooserDefault *impl) 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); } @@ -2311,16 +2448,16 @@ shortcut_find_position (GtkFileChooserDefault *impl, 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; @@ -2336,7 +2473,7 @@ shortcut_find_position (GtkFileChooserDefault *impl, if (exists) return i; } - else + else if (shortcut_type == SHORTCUT_TYPE_PATH) { GtkFilePath *model_path; @@ -2442,7 +2579,7 @@ shortcuts_get_selected (GtkFileChooserDefault *impl, 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; @@ -2465,11 +2602,12 @@ remove_selected_bookmarks (GtkFileChooserDefault *impl) 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; @@ -2626,6 +2764,16 @@ bookmarks_check_add_sensitivity (GtkFileChooserDefault *impl) 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) @@ -3139,7 +3287,7 @@ shortcuts_reorder (GtkFileChooserDefault *impl, { GtkTreeIter iter; gpointer col_data; - gboolean is_volume; + ShortcutType shortcut_type; GtkTreePath *path; int old_position; int bookmarks_index; @@ -3164,10 +3312,10 @@ shortcuts_reorder (GtkFileChooserDefault *impl, 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 */ @@ -3251,17 +3399,11 @@ shortcuts_row_separator_func (GtkTreeModel *model, 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 @@ -3506,7 +3648,7 @@ shortcuts_list_create (GtkFileChooserDefault *impl) /* Accessible object name for the file chooser's shortcuts pane */ atk_object_set_name (gtk_widget_get_accessible (impl->browse_shortcuts_tree_view), _("Places")); - 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, @@ -3579,8 +3721,7 @@ shortcuts_list_create (GtkFileChooserDefault *impl) 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); @@ -3951,6 +4092,8 @@ file_list_update_popup_menu (GtkFileChooserDefault *impl) { 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() */ @@ -4050,6 +4193,28 @@ list_button_press_event_cb (GtkWidget *widget, 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) @@ -4063,7 +4228,7 @@ create_file_list (GtkFileChooserDefault *impl) 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); @@ -4153,6 +4318,7 @@ create_file_list (GtkFileChooserDefault *impl) gtk_tree_view_column_set_sort_column_id (column, FILE_LIST_COL_SIZE); gtk_tree_view_append_column (GTK_TREE_VIEW (impl->browse_files_tree_view), column); #endif + /* Modification time column */ column = gtk_tree_view_column_new (); @@ -4163,8 +4329,11 @@ create_file_list (GtkFileChooserDefault *impl) gtk_tree_view_column_pack_start (column, renderer, TRUE); gtk_tree_view_column_set_cell_data_func (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); + impl->list_mtime_column = column; + + file_list_set_sort_column_ids (impl); + gtk_widget_show_all (swin); return swin; @@ -4272,9 +4441,59 @@ save_folder_combo_changed_cb (GtkComboBox *combo, 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; + + if (impl->has_search) + { + 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 */ static GtkWidget * save_folder_combo_create (GtkFileChooserDefault *impl) @@ -4282,8 +4501,14 @@ save_folder_combo_create (GtkFileChooserDefault *impl) 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); @@ -4305,8 +4530,7 @@ save_folder_combo_create (GtkFileChooserDefault *impl) 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); @@ -4651,6 +4875,7 @@ browse_widgets_create (GtkFileChooserDefault *impl) hbox = gtk_hbox_new (FALSE, 12); gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); gtk_widget_show (hbox); + impl->browse_path_bar_hbox = hbox; location_button_create (impl); gtk_box_pack_start (GTK_BOX (hbox), impl->location_button, FALSE, FALSE, 0); @@ -5235,6 +5460,9 @@ gtk_file_chooser_default_dispose (GObject *object) impl->shortcuts_activate_iter_handle = 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 (_gtk_file_chooser_default_parent_class)->dispose (object); @@ -5512,30 +5740,40 @@ gtk_file_chooser_default_map (GtkWidget *widget) GTK_WIDGET_CLASS (_gtk_file_chooser_default_parent_class)->map (widget); - switch (impl->reload_state) + if (impl->operation_mode == OPERATION_MODE_BROWSE) { - 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; else continue the pending load. */ - if (impl->current_folder) + switch (impl->reload_state) { - pending_select_paths_store_selection (impl); - change_folder_and_display_error (impl, impl->current_folder, FALSE); - } - break; + 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; - default: - g_assert_not_reached (); + case RELOAD_WAS_UNMAPPED: + /* Just reload the current folder; else continue + * the pending load. + */ + if (impl->current_folder) + { + pending_select_paths_store_selection (impl); + change_folder_and_display_error (impl, impl->current_folder, FALSE); + } + break; + + default: + g_assert_not_reached (); + } } bookmarks_changed_cb (impl->file_system, impl); @@ -6003,12 +6241,6 @@ pending_select_paths_process (GtkFileChooserDefault *impl) * but rather on behalf of something else like GtkFileChooserButton. In * that case, the chooser's selection should be what the caller expects, * as the user can't see that something else got selected. See bug #165264. - * - * Also, we don't select the first file if we are not in OPEN mode. Doing - * so would change the contents of the filename entry for SAVE or - * CREATE_FOLDER, which is undesired; in SELECT_FOLDER, we don't want to - * select a *different* folder from the one into which the user just - * navigated. */ if (GTK_WIDGET_MAPPED (impl) && impl->action == GTK_FILE_CHOOSER_ACTION_OPEN) browse_files_select_first_row (impl); @@ -6055,17 +6287,11 @@ browse_files_model_finished_loading_cb (GtkFileSystemModel *model, 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) { g_object_unref (impl->browse_files_model); @@ -6077,6 +6303,20 @@ set_list_model (GtkFileChooserDefault *impl, g_object_unref (impl->sort_model); impl->sort_model = NULL; } + + 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); gtk_tree_view_set_model (GTK_TREE_VIEW (impl->browse_files_tree_view), NULL); @@ -6151,6 +6391,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) + return; + if (!(impl->action == GTK_FILE_CHOOSER_ACTION_SAVE || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER || ((impl->action == GTK_FILE_CHOOSER_ACTION_OPEN @@ -6172,35 +6415,46 @@ update_chooser_entry (GtkFileChooserDefault *impl) } else if (closure.num_selected == 1) { - GtkTreeIter child_iter; - const GtkFileInfo *info; - gboolean change_entry; + if (impl->operation_mode == OPERATION_MODE_BROWSE) + { + GtkTreeIter child_iter; + const GtkFileInfo *info; + gboolean change_entry; - gtk_tree_model_sort_convert_iter_to_child_iter (impl->sort_model, - &child_iter, - &closure.first_selected_iter); + gtk_tree_model_sort_convert_iter_to_child_iter (impl->sort_model, + &child_iter, + &closure.first_selected_iter); - info = _gtk_file_system_model_get_info (impl->browse_files_model, &child_iter); + info = _gtk_file_system_model_get_info (impl->browse_files_model, &child_iter); - /* If the cursor moved to the row of the newly created folder, - * retrieving info will return NULL. - */ - if (!info) - return; + /* If the cursor moved to the row of the newly created folder, + * retrieving info will return NULL. + */ + if (!info) + return; - g_free (impl->browse_files_last_selected_name); - impl->browse_files_last_selected_name = g_strdup (gtk_file_info_get_display_name (info)); + g_free (impl->browse_files_last_selected_name); + impl->browse_files_last_selected_name = + g_strdup (gtk_file_info_get_display_name (info)); - if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN - || 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 (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN || + 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 one of the folder modes */ + + if (change_entry) + _gtk_file_chooser_entry_set_file_part (GTK_FILE_CHOOSER_ENTRY (impl->location_entry), + impl->browse_files_last_selected_name); + + } else - change_entry = TRUE; /* ... unless we are in one of the folder modes */ - - if (change_entry) - _gtk_file_chooser_entry_set_file_part (GTK_FILE_CHOOSER_ENTRY (impl->location_entry), impl->browse_files_last_selected_name); - - return; + { + gtk_tree_model_get (GTK_TREE_MODEL (impl->search_model), + &closure.first_selected_iter, + SEARCH_MODEL_COL_DISPLAY_NAME, &file_part, + -1); + } } else { @@ -6414,6 +6668,8 @@ gtk_file_chooser_default_update_current_folder (GtkFileChooser *chooser, profile_start ("start", (char *) path); + search_switch_to_browse_mode (impl); + g_assert (path != NULL); if (impl->local_only && @@ -6456,6 +6712,9 @@ gtk_file_chooser_default_get_current_folder (GtkFileChooser *chooser) { 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; @@ -6516,9 +6775,9 @@ gtk_file_chooser_default_select_path (GtkFileChooser *chooser, return FALSE; if (!parent_path) - return _gtk_file_chooser_set_current_folder_path (chooser, path, error); + 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 { @@ -6614,6 +6873,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); @@ -6753,6 +7022,10 @@ gtk_file_chooser_default_get_paths (GtkFileChooser *chooser) struct get_paths_closure info; GtkWindow *toplevel; GtkWidget *current_focus; + gboolean file_list_seen; + + if (impl->operation_mode == OPERATION_MODE_SEARCH) + return search_get_selected_paths (impl); info.impl = impl; info.result = NULL; @@ -6764,12 +7037,14 @@ gtk_file_chooser_default_get_paths (GtkFileChooser *chooser) else current_focus = NULL; + file_list_seen = FALSE; if (current_focus == impl->browse_files_tree_view) { GtkTreeSelection *selection; file_list: + file_list_seen = TRUE; selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view)); gtk_tree_selection_selected_foreach (selection, get_paths_foreach, &info); @@ -6804,8 +7079,12 @@ gtk_file_chooser_default_get_paths (GtkFileChooser *chooser) return NULL; } - g_assert (info.path_from_entry != NULL); - info.result = g_slist_prepend (info.result, info.path_from_entry); + if (info.path_from_entry) + info.result = g_slist_prepend (info.result, info.path_from_entry); + else if (!file_list_seen) + goto file_list; + else + return NULL; } else if (impl->toplevel_last_focus_widget == impl->browse_files_tree_view) goto file_list; @@ -6974,7 +7253,7 @@ add_shortcut_get_info_cb (GtkFileSystemHandle *handle, pos = shortcuts_get_pos_for_shortcut_folder (data->impl, data->impl->num_shortcuts); - shortcuts_insert_path (data->impl, pos, FALSE, NULL, data->path, NULL, FALSE, SHORTCUTS_SHORTCUTS); + shortcuts_insert_path (data->impl, pos, SHORTCUT_TYPE_PATH, NULL, data->path, NULL, FALSE, SHORTCUTS_SHORTCUTS); out: g_object_unref (data->impl); @@ -7088,15 +7367,15 @@ gtk_file_chooser_default_remove_shortcut_folder (GtkFileChooser *chooser, 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) @@ -7145,15 +7424,15 @@ gtk_file_chooser_default_list_shortcut_folders (GtkFileChooser *chooser) 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)); @@ -7500,6 +7779,23 @@ should_respond_after_confirm_overwrite (GtkFileChooserDefault *impl, } } +/* 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); +} + static void action_create_folder_cb (GtkFileSystemHandle *handle, const GtkFilePath *path, @@ -7765,6 +8061,9 @@ gtk_file_chooser_default_should_respond (GtkFileChooserEmbed *chooser_embed) 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) @@ -7900,7 +8199,7 @@ gtk_file_chooser_default_should_respond (GtkFileChooserEmbed *chooser_embed) { 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; @@ -7914,6 +8213,11 @@ gtk_file_chooser_default_should_respond (GtkFileChooserEmbed *chooser_embed) */ 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 if (impl->location_entry && impl->toplevel_last_focus_widget == impl->location_entry) { /* The focus is on a dialog's action area button, *and* the widget that @@ -7963,6 +8267,444 @@ gtk_file_chooser_default_initial_focus (GtkFileChooserEmbed *chooser_embed) 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 the search engine to the search_model */ +static void +search_add_hit (GtkFileChooserDefault *impl, + gchar *uri) +{ + GtkFilePath *path; + char *filename; + char *display_name; + char *collation_key; + struct stat statbuf; + struct stat *statbuf_copy; + GtkTreeIter iter; + + 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 GtkSearchEngine when we get new hits */ +static void +search_engine_hits_added_cb (GtkSearchEngine *engine, + GList *hits, + gpointer data) +{ + GtkFileChooserDefault *impl; + GList *l; + + impl = GTK_FILE_CHOOSER_DEFAULT (data); + + for (l = hits; l; l = l->next) + search_add_hit (impl, (gchar*)l->data); +} + +/* Callback used from GtkSearchEngine when the query is done running */ +static void +search_engine_finished_cb (GtkSearchEngine *engine, + gpointer data) +{ + GtkFileChooserDefault *impl; + + impl = GTK_FILE_CHOOSER_DEFAULT (data); + + /* FMQ: if search was empty, say that we got no hits */ + + set_busy_cursor (impl, FALSE); +} + +/* Displays a generic error when we cannot create a GtkSearchEngine. + * It would be better if _gtk_search_engine_new() gave us a GError + * with a better message, but it doesn't do that right now. + */ +static void +search_error_could_not_create_client (GtkFileChooserDefault *impl) +{ + error_message (impl, + _("Could not start the search process"), + _("The program was not able to create a connection to the indexer " + "daemon. Please make sure it is running.")); +} + +static void +search_engine_error_cb (GtkSearchEngine *engine, + const gchar *message, + gpointer data) +{ + GtkFileChooserDefault *impl; + + impl = GTK_FILE_CHOOSER_DEFAULT (data); + + search_stop_searching (impl); + error_message (impl, _("Could not send the search request"), message); + + set_busy_cursor (impl, FALSE); +} + +/* Frees the data in the search_model */ +static void +search_clear_model (GtkFileChooserDefault *impl, + gboolean remove_from_treeview) +{ + 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_engine) + { + g_object_unref (impl->search_engine); + impl->search_engine = NULL; + } +} + +/* Stops any pending searches, clears the file list, and switches back to OPERATION_MODE_BROWSE */ +static void +search_switch_to_browse_mode (GtkFileChooserDefault *impl) +{ + 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); + + if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN + || impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) + { + gtk_widget_show (impl->location_button); + + if (impl->location_mode == LOCATION_MODE_FILENAME_ENTRY) + gtk_widget_show (impl->location_entry_box); + } + + impl->operation_mode = OPERATION_MODE_BROWSE; + + file_list_set_sort_column_ids (impl); +} + +/* Sort callback from the path column */ +static gint +search_column_path_sort_func (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gpointer user_data) +{ + 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_sortable_set_sort_column_id (GTK_TREE_SORTABLE (impl->search_model), + SEARCH_MODEL_COL_STAT, + GTK_SORT_DESCENDING); + + gtk_tree_view_set_model (GTK_TREE_VIEW (impl->browse_files_tree_view), GTK_TREE_MODEL (impl->search_model)); +} + +/* Creates a new query with the specified text and launches it */ +static void +search_start_query (GtkFileChooserDefault *impl, + const gchar *query_text) +{ + search_stop_searching (impl); + search_clear_model (impl, TRUE); + search_setup_model (impl); + set_busy_cursor (impl, TRUE); + + if (impl->search_engine == NULL) + impl->search_engine = _gtk_search_engine_new (); + + if (!impl->search_engine) + { + set_busy_cursor (impl, FALSE); + search_error_could_not_create_client (impl); /* lame; we don't get an error code or anything */ + return; + } + + impl->search_query = _gtk_query_new (); + _gtk_query_set_text (impl->search_query, query_text); + _gtk_search_engine_set_query (impl->search_engine, impl->search_query); + + g_signal_connect (impl->search_engine, "hits-added", + G_CALLBACK (search_engine_hits_added_cb), impl); + g_signal_connect (impl->search_engine, "finished", + G_CALLBACK (search_engine_finished_cb), impl); + g_signal_connect (impl->search_engine, "error", + G_CALLBACK (search_engine_error_cb), impl); + + _gtk_search_engine_start (impl->search_engine); +} + +/* Callback used when the user presses Enter while typing on the search entry; starts the query */ +static void +search_entry_activate_cb (GtkEntry *entry, + gpointer data) +{ + GtkFileChooserDefault *impl; + const char *text; + + impl = GTK_FILE_CHOOSER_DEFAULT (data); + + text = gtk_entry_get_text (GTK_ENTRY (impl->search_entry)); + if (strlen (text) == 0) + return; + + search_start_query (impl, text); +} + +/* Hides the path bar and creates the search entry */ +static void +search_setup_widgets (GtkFileChooserDefault *impl) +{ + GtkWidget *label; + gchar *text; + + impl->search_hbox = gtk_hbox_new (FALSE, 12); + + /* Label */ + + label = gtk_label_new (NULL); + text = g_strdup_printf ("%s", _("Search:")); + gtk_label_set_markup (GTK_LABEL (label), text); + g_free (text); + gtk_box_pack_start (GTK_BOX (impl->search_hbox), label, FALSE, FALSE, 0); + + /* Entry */ + + 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); + + /* Box for search widgets */ + + gtk_box_pack_start (GTK_BOX (impl->browse_path_bar_hbox), impl->search_hbox, TRUE, TRUE, 0); + gtk_widget_show_all (impl->search_hbox); + + /* Hide the location widgets temporarily */ + + if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN + || impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) + { + gtk_widget_hide (impl->location_button); + gtk_widget_hide (impl->location_entry_box); + } + + gtk_widget_grab_focus (impl->search_entry); + + /* FMQ: hide the filter combo? */ +} + +/* Main entry point to the searching functions; this gets called when the user + * activates the Search shortcut. + */ +static void +search_activate (GtkFileChooserDefault *impl) +{ + 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) @@ -8011,28 +8753,44 @@ check_preview_change (GtkFileChooserDefault *impl) { 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) + new_path = NULL; + new_display_name = NULL; + if (cursor_path) { - GtkTreeIter iter; - GtkTreeIter child_iter; + if (impl->operation_mode == OPERATION_MODE_BROWSE) + { + if (impl->sort_model) + { + GtkTreeIter iter; + GtkTreeIter child_iter; + const GtkFileInfo *new_info; - if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (impl->sort_model), &iter, cursor_path)) - g_assert_not_reached (); + gtk_tree_model_get_iter (GTK_TREE_MODEL (impl->sort_model), &iter, cursor_path); + gtk_tree_path_free (cursor_path); - gtk_tree_path_free (cursor_path); + gtk_tree_model_sort_convert_iter_to_child_iter (impl->sort_model, &child_iter, &iter); - gtk_tree_model_sort_convert_iter_to_child_iter (impl->sort_model, &child_iter, &iter); + new_path = _gtk_file_system_model_get_path (impl->browse_files_model, &child_iter); + new_info = _gtk_file_system_model_get_info (impl->browse_files_model, &child_iter); + if (new_info) + new_display_name = gtk_file_info_get_display_name (new_info); + } + } + else + { + GtkTreeIter 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); - } - else - { - new_path = NULL; - new_info = NULL; + 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 && @@ -8048,7 +8806,7 @@ check_preview_change (GtkFileChooserDefault *impl) 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 { @@ -8099,6 +8857,8 @@ shortcuts_activate_volume_mount_cb (GtkFileSystemHandle *handle, if (path != NULL) { change_folder_and_display_error (impl, path, FALSE); + focus_browse_tree_view_if_possible (impl); + gtk_file_path_free (path); } @@ -8117,6 +8877,8 @@ shortcuts_activate_volume (GtkFileChooserDefault *impl, { 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. */ @@ -8169,9 +8931,14 @@ shortcuts_activate_get_info_cb (GtkFileSystemHandle *handle, goto out; if (!error && gtk_file_info_get_is_folder (info)) - change_folder_and_display_error (data->impl, data->path, FALSE); + { + change_folder_and_display_error (data->impl, data->path, FALSE); + focus_browse_tree_view_if_possible (data->impl); + } else - gtk_file_chooser_default_select_path (GTK_FILE_CHOOSER (data->impl), data->path, NULL); + gtk_file_chooser_default_select_path (GTK_FILE_CHOOSER (data->impl), + data->path, + NULL); out: g_object_unref (data->impl); @@ -8186,26 +8953,25 @@ shortcuts_activate_iter (GtkFileChooserDefault *impl, GtkTreeIter *iter) { gpointer col_data; - gboolean is_volume; + ShortcutType shortcut_type; if (impl->location_mode == LOCATION_MODE_FILENAME_ENTRY && impl->action != GTK_FILE_CHOOSER_ACTION_SAVE) _gtk_file_chooser_entry_set_file_part (GTK_FILE_CHOOSER_ENTRY (impl->location_entry), ""); 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 (impl->shortcuts_activate_iter_handle) { gtk_file_system_cancel_operation (impl->shortcuts_activate_iter_handle); impl->shortcuts_activate_iter_handle = NULL; } - if (is_volume) + if (shortcut_type == SHORTCUT_TYPE_SEPARATOR) + return; + else if (shortcut_type == SHORTCUT_TYPE_VOLUME) { GtkFileSystemVolume *volume; @@ -8213,7 +8979,7 @@ shortcuts_activate_iter (GtkFileChooserDefault *impl, shortcuts_activate_volume (impl, volume); } - else + else if (shortcut_type == SHORTCUT_TYPE_PATH) { struct ShortcutsActivateData *data; @@ -8226,6 +8992,10 @@ shortcuts_activate_iter (GtkFileChooserDefault *impl, GTK_FILE_INFO_IS_FOLDER, shortcuts_activate_get_info_cb, data); } + else if (shortcut_type == SHORTCUT_TYPE_SEARCH) + { + search_activate (impl); + } } /* Callback used when a row in the shortcuts list is activated */ @@ -8238,15 +9008,13 @@ shortcuts_row_activated_cb (GtkTreeView *tree_view, 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 */ @@ -8286,8 +9054,15 @@ shortcuts_select_func (GtkTreeSelection *selection, gpointer data) { GtkFileChooserDefault *impl = data; + GtkTreeIter filter_iter; + ShortcutType shortcut_type; - return (*gtk_tree_path_get_indices (path) != shortcuts_get_index (impl, SHORTCUTS_BOOKMARKS_SEPARATOR)); + 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 shortcut_type != SHORTCUT_TYPE_SEPARATOR; } static gboolean @@ -8299,6 +9074,9 @@ list_select_func (GtkTreeSelection *selection, { 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) { @@ -8324,7 +9102,7 @@ list_selection_changed (GtkTreeSelection *selection, 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; @@ -8339,8 +9117,10 @@ list_selection_changed (GtkTreeSelection *selection, out: + /* TODO - Change the following functions to make them accept MODE_SEARCH */ if (impl->location_entry) update_chooser_entry (impl); + check_preview_change (impl); bookmarks_check_add_sensitivity (impl); @@ -8357,6 +9137,12 @@ list_row_activated (GtkTreeView *tree_view, 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; @@ -8427,6 +9213,15 @@ list_icon_data_func (GtkTreeViewColumn *tree_column, 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); @@ -8476,13 +9271,33 @@ list_name_data_func (GtkTreeViewColumn *tree_column, 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; @@ -8498,6 +9313,7 @@ list_name_data_func (GtkTreeViewColumn *tree_column, g_object_set (cell, "text", gtk_file_info_get_display_name (info), "sensitive", sensitive, + "ellipsize", PANGO_ELLIPSIZE_END, NULL); } @@ -8559,7 +9375,6 @@ list_mtime_data_func (GtkTreeViewColumn *tree_column, gpointer data) { GtkFileChooserDefault *impl; - const GtkFileInfo *info; GtkFileTime time_mtime; GDate mtime, now; int days_diff; @@ -8568,17 +9383,35 @@ list_mtime_data_func (GtkTreeViewColumn *tree_column, 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; - time_mtime = gtk_file_info_get_modification_time (info); + 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; + + 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")); @@ -8609,10 +9442,6 @@ list_mtime_data_func (GtkTreeViewColumn *tree_column, } } - 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, @@ -8638,7 +9467,24 @@ location_set_user_text (GtkFileChooserDefault *impl, static void location_popup_handler (GtkFileChooserDefault *impl, const gchar *path) -{ +{ + if (impl->operation_mode == OPERATION_MODE_SEARCH) + { + GtkWidget *widget_to_focus; + + /* This will give us the location widgets back */ + search_switch_to_browse_mode (impl); + if (impl->current_folder) + change_folder_and_display_error (impl, impl->current_folder, FALSE); + + if (impl->location_mode == LOCATION_MODE_PATH_BAR) + widget_to_focus = impl->browse_files_tree_view; + else + widget_to_focus = impl->location_entry; + + gtk_widget_grab_focus (widget_to_focus); + return; + } if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN || impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) { @@ -8707,6 +9553,7 @@ switch_to_shortcut (GtkFileChooserDefault *impl, g_assert_not_reached (); shortcuts_activate_iter (impl, &iter); + focus_browse_tree_view_if_possible (impl); } /* Handler for the "home-folder" keybinding signal */ @@ -8758,26 +9605,26 @@ show_hidden_handler (GtkFileChooserDefault *impl) /* 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); @@ -8787,13 +9634,13 @@ shortcuts_model_filter_row_draggable (GtkTreeDragSource *drag_source, /* GtkTreeDragSource::drag_data_get implementation for the shortcuts filter model */ static gboolean -shortcuts_model_filter_drag_data_get (GtkTreeDragSource *drag_source, - GtkTreePath *path, - GtkSelectionData *selection_data) +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 */ @@ -8802,30 +9649,30 @@ shortcuts_model_filter_drag_data_get (GtkTreeDragSource *drag_source, /* 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); diff --git a/gtk/gtkfilechooserprivate.h b/gtk/gtkfilechooserprivate.h index 24f57e0591..3a7416ccdd 100644 --- a/gtk/gtkfilechooserprivate.h +++ b/gtk/gtkfilechooserprivate.h @@ -25,6 +25,8 @@ #include "gtkfilesystem.h" #include "gtkfilesystemmodel.h" #include "gtkliststore.h" +#include "gtksearchengine.h" +#include "gtkquery.h" #include "gtktooltips.h" #include "gtktreemodelsort.h" #include "gtktreestore.h" @@ -147,6 +149,11 @@ typedef enum { LOCATION_MODE_FILENAME_ENTRY } LocationMode; +typedef enum { + OPERATION_MODE_BROWSE, + OPERATION_MODE_SEARCH +} OperationMode; + struct _GtkFileChooserDefault { GtkVBox parent_instance; @@ -175,11 +182,19 @@ 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; GtkFileSystemModel *browse_files_model; char *browse_files_last_selected_name; + /* Widgets for searching */ + GtkWidget *search_hbox; + GtkWidget *search_entry; + GtkSearchEngine *search_engine; + GtkQuery *search_query; + GtkListStore *search_model; + GtkWidget *filter_combo_hbox; GtkWidget *filter_combo; GtkWidget *preview_box; @@ -195,7 +210,16 @@ struct _GtkFileChooserDefault LocationMode location_mode; 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 +239,8 @@ struct _GtkFileChooserDefault ReloadState reload_state; guint load_timeout_id; + OperationMode operation_mode; + GSList *pending_select_paths; GtkFileFilter *current_filter; @@ -222,9 +248,6 @@ struct _GtkFileChooserDefault GtkTooltips *tooltips; - gboolean has_home; - gboolean has_desktop; - int num_volumes; int num_shortcuts; int num_bookmarks; @@ -239,6 +262,7 @@ struct _GtkFileChooserDefault GtkTreeViewColumn *list_name_column; GtkCellRenderer *list_name_renderer; + GtkTreeViewColumn *list_mtime_column; GSource *edited_idle; char *edited_new_text; @@ -266,6 +290,9 @@ struct _GtkFileChooserDefault guint changing_folder : 1; guint shortcuts_current_folder_active : 1; guint expand_folders : 1; + guint has_home : 1; + guint has_desktop : 1; + guint has_search : 1; #if 0 guint shortcuts_drag_outside : 1; diff --git a/gtk/gtkquery.c b/gtk/gtkquery.c new file mode 100644 index 0000000000..274a0a628d --- /dev/null +++ b/gtk/gtkquery.c @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2005 Novell, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * + * Author: Anders Carlsson + * + * Based on nautilus-query.c + */ + +#include +#include + +#include "gtkquery.h" + +struct _GtkQueryPrivate +{ + gchar *text; + gchar *location_uri; + GList *mime_types; +}; + +G_DEFINE_TYPE (GtkQuery, _gtk_query, G_TYPE_OBJECT); + +static void +finalize (GObject *object) +{ + GtkQuery *query; + + query = GTK_QUERY (object); + + g_free (query->priv->text); + + G_OBJECT_CLASS (_gtk_query_parent_class)->finalize (object); +} + +static void +_gtk_query_class_init (GtkQueryClass *class) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (class); + gobject_class->finalize = finalize; + + g_type_class_add_private (gobject_class, sizeof (GtkQueryPrivate)); +} + +static void +_gtk_query_init (GtkQuery *query) +{ + query->priv = G_TYPE_INSTANCE_GET_PRIVATE (query, GTK_TYPE_QUERY, GtkQueryPrivate); +} + +GtkQuery * +_gtk_query_new (void) +{ + return g_object_new (GTK_TYPE_QUERY, NULL); +} + + +gchar * +_gtk_query_get_text (GtkQuery *query) +{ + return g_strdup (query->priv->text); +} + +void +_gtk_query_set_text (GtkQuery *query, + const gchar *text) +{ + g_free (query->priv->text); + query->priv->text = g_strdup (text); +} + +gchar * +_gtk_query_get_location (GtkQuery *query) +{ + return g_strdup (query->priv->location_uri); +} + +void +_gtk_query_set_location (GtkQuery *query, + const gchar *uri) +{ + g_free (query->priv->location_uri); + query->priv->location_uri = g_strdup (uri); +} + +GList * +_gtk_query_get_mime_types (GtkQuery *query) +{ + GList *list, *l; + gchar *mime_type; + + list = NULL; + for (l = query->priv->mime_types; l; l = l->next) + { + mime_type = (gchar*)l->data; + list = g_list_prepend (list, g_strdup (mime_type)); + } + + return list; +} + +void +_gtk_query_set_mime_types (GtkQuery *query, + GList *mime_types) +{ + GList *l; + gchar *mime_type; + + g_list_foreach (query->priv->mime_types, (GFunc)g_free, NULL); + g_list_free (query->priv->mime_types); + query->priv->mime_types = NULL; + + for (l = mime_types; l; l = l->next) + { + mime_type = (gchar*)l->data; + query->priv->mime_types = g_list_prepend (query->priv->mime_types, g_strdup (mime_type)); + } +} + +void +_gtk_query_add_mime_type (GtkQuery *query, + const gchar *mime_type) +{ + query->priv->mime_types = g_list_prepend (query->priv->mime_types, + g_strdup (mime_type)); +} + diff --git a/gtk/gtkquery.h b/gtk/gtkquery.h new file mode 100644 index 0000000000..a6e6553b94 --- /dev/null +++ b/gtk/gtkquery.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2005 Novell, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * + * Author: Anders Carlsson + * + * Based on nautilus-query.h + */ + +#ifndef __GTK_QUERY_H__ +#define __GTK_QUERY_H__ + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_QUERY (_gtk_query_get_type ()) +#define GTK_QUERY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_QUERY, GtkQuery)) +#define GTK_QUERY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_QUERY, GtkQueryClass)) +#define GTK_IS_QUERY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_QUERY)) +#define GTK_IS_QUERY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_QUERY)) +#define GTK_QUERY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_QUERY, GtkQueryClass)) + +typedef struct _GtkQuery GtkQuery; +typedef struct _GtkQueryClass GtkQueryClass; +typedef struct _GtkQueryPrivate GtkQueryPrivate; + +struct _GtkQuery +{ + GObject parent; + + GtkQueryPrivate *priv; +}; + +struct _GtkQueryClass +{ + GObjectClass parent_class; +}; + +GType _gtk_query_get_type (void); +gboolean _gtk_query_enabled (void); + +GtkQuery* _gtk_query_new (void); + +gchar* _gtk_query_get_text (GtkQuery *query); +void _gtk_query_set_text (GtkQuery *query, + const gchar *text); + +gchar* _gtk_query_get_location (GtkQuery *query); +void _gtk_query_set_location (GtkQuery *query, + const gchar *uri); + +GList* _gtk_query_get_mime_types (GtkQuery *query); +void _gtk_query_set_mime_types (GtkQuery *query, + GList *mime_types); +void _gtk_query_add_mime_type (GtkQuery *query, + const gchar *mime_type); + +G_END_DECLS + +#endif /* __GTK_QUERY_H__ */ diff --git a/gtk/gtksearchengine.c b/gtk/gtksearchengine.c new file mode 100644 index 0000000000..196e944d8e --- /dev/null +++ b/gtk/gtksearchengine.c @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2005 Novell, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * + * Author: Anders Carlsson + * + * Based on nautilus-search-engine.c + */ + +#include +#include "gtksearchengine.h" +#include "gtksearchenginebeagle.h" +#include "gtksearchenginesimple.h" +#include "gtksearchenginetracker.h" + +#define HAVE_BEAGLE 1 +#define HAVE_TRACKER 1 + +enum +{ + HITS_ADDED, + HITS_SUBTRACTED, + FINISHED, + ERROR, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +G_DEFINE_ABSTRACT_TYPE (GtkSearchEngine, _gtk_search_engine, G_TYPE_OBJECT); + +static void +finalize (GObject *object) +{ + G_OBJECT_CLASS (_gtk_search_engine_parent_class)->finalize (object); +} + +static void +_gtk_search_engine_class_init (GtkSearchEngineClass *class) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (class); + gobject_class->finalize = finalize; + + signals[HITS_ADDED] = + g_signal_new ("hits-added", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkSearchEngineClass, hits_added), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + + signals[HITS_SUBTRACTED] = + g_signal_new ("hits-subtracted", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkSearchEngineClass, hits_subtracted), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + + signals[FINISHED] = + g_signal_new ("finished", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkSearchEngineClass, finished), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[ERROR] = + g_signal_new ("error", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkSearchEngineClass, error), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, + G_TYPE_STRING); +} + +static void +_gtk_search_engine_init (GtkSearchEngine *engine) +{ +} + +GtkSearchEngine * +_gtk_search_engine_new (void) +{ + GtkSearchEngine *engine; + +#ifdef HAVE_TRACKER + engine = _gtk_search_engine_tracker_new (); + if (engine) + return engine; +#endif + +#ifdef HAVE_BEAGLE + engine = _gtk_search_engine_beagle_new (); + if (engine) + return engine; +#endif + + engine = _gtk_search_engine_simple_new (); + return engine; +} + +void +_gtk_search_engine_set_query (GtkSearchEngine *engine, + GtkQuery *query) +{ + g_return_if_fail (GTK_IS_SEARCH_ENGINE (engine)); + g_return_if_fail (GTK_SEARCH_ENGINE_GET_CLASS (engine)->set_query != NULL); + + GTK_SEARCH_ENGINE_GET_CLASS (engine)->set_query (engine, query); +} + +void +_gtk_search_engine_start (GtkSearchEngine *engine) +{ + g_return_if_fail (GTK_IS_SEARCH_ENGINE (engine)); + g_return_if_fail (GTK_SEARCH_ENGINE_GET_CLASS (engine)->start != NULL); + + GTK_SEARCH_ENGINE_GET_CLASS (engine)->start (engine); +} + +void +_gtk_search_engine_stop (GtkSearchEngine *engine) +{ + g_return_if_fail (GTK_IS_SEARCH_ENGINE (engine)); + g_return_if_fail (GTK_SEARCH_ENGINE_GET_CLASS (engine)->stop != NULL); + + GTK_SEARCH_ENGINE_GET_CLASS (engine)->stop (engine); +} + +gboolean +_gtk_search_engine_is_indexed (GtkSearchEngine *engine) +{ + g_return_val_if_fail (GTK_IS_SEARCH_ENGINE (engine), FALSE); + g_return_val_if_fail (GTK_SEARCH_ENGINE_GET_CLASS (engine)->is_indexed != NULL, FALSE); + + return GTK_SEARCH_ENGINE_GET_CLASS (engine)->is_indexed (engine); +} + +void +_gtk_search_engine_hits_added (GtkSearchEngine *engine, + GList *hits) +{ + g_return_if_fail (GTK_IS_SEARCH_ENGINE (engine)); + + g_signal_emit (engine, signals[HITS_ADDED], 0, hits); +} + + +void +_gtk_search_engine_hits_subtracted (GtkSearchEngine *engine, + GList *hits) +{ + g_return_if_fail (GTK_IS_SEARCH_ENGINE (engine)); + + g_signal_emit (engine, signals[HITS_SUBTRACTED], 0, hits); +} + + +void +_gtk_search_engine_finished (GtkSearchEngine *engine) +{ + g_return_if_fail (GTK_IS_SEARCH_ENGINE (engine)); + + g_signal_emit (engine, signals[FINISHED], 0); +} + +void +_gtk_search_engine_error (GtkSearchEngine *engine, + const gchar *error_message) +{ + g_return_if_fail (GTK_IS_SEARCH_ENGINE (engine)); + + g_signal_emit (engine, signals[ERROR], 0, error_message); +} diff --git a/gtk/gtksearchengine.h b/gtk/gtksearchengine.h new file mode 100644 index 0000000000..3b0a34c8c0 --- /dev/null +++ b/gtk/gtksearchengine.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2005 Novell, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * + * Author: Anders Carlsson + * + * Based on nautilus-search-engine.h + */ + +#ifndef __GTK_SEARCH_ENGINE_H__ +#define __GTK_SEARCH_ENGINE_H__ + +#include +#include "gtkquery.h" + +G_BEGIN_DECLS + +#define GTK_TYPE_SEARCH_ENGINE (_gtk_search_engine_get_type ()) +#define GTK_SEARCH_ENGINE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_SEARCH_ENGINE, GtkSearchEngine)) +#define GTK_SEARCH_ENGINE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_SEARCH_ENGINE, GtkSearchEngineClass)) +#define GTK_IS_SEARCH_ENGINE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_SEARCH_ENGINE)) +#define GTK_IS_SEARCH_ENGINE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SEARCH_ENGINE)) +#define GTK_SEARCH_ENGINE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_SEARCH_ENGINE, GtkSearchEngineClass)) + +typedef struct _GtkSearchEngine GtkSearchEngine; +typedef struct _GtkSearchEngineClass GtkSearchEngineClass; +typedef struct _GtkSearchEnginePrivate GtkSearchEnginePrivate; + +struct _GtkSearchEngine +{ + GObject parent; + + GtkSearchEnginePrivate *priv; +}; + +struct _GtkSearchEngineClass +{ + GObjectClass parent_class; + + /* VTable */ + void (*set_query) (GtkSearchEngine *engine, + GtkQuery *query); + void (*start) (GtkSearchEngine *engine); + void (*stop) (GtkSearchEngine *engine); + gboolean (*is_indexed) (GtkSearchEngine *engine); + + /* Signals */ + void (*hits_added) (GtkSearchEngine *engine, + GList *hits); + void (*hits_subtracted) (GtkSearchEngine *engine, + GList *hits); + void (*finished) (GtkSearchEngine *engine); + void (*error) (GtkSearchEngine *engine, + const gchar *error_message); +}; + +GType _gtk_search_engine_get_type (void); +gboolean _gtk_search_engine_enabled (void); + +GtkSearchEngine* _gtk_search_engine_new (void); + +void _gtk_search_engine_set_query (GtkSearchEngine *engine, + GtkQuery *query); +void _gtk_search_engine_start (GtkSearchEngine *engine); +void _gtk_search_engine_stop (GtkSearchEngine *engine); +gboolean _gtk_search_engine_is_indexed (GtkSearchEngine *engine); + +void _gtk_search_engine_hits_added (GtkSearchEngine *engine, + GList *hits); +void _gtk_search_engine_hits_subtracted (GtkSearchEngine *engine, + GList *hits); +void _gtk_search_engine_finished (GtkSearchEngine *engine); +void _gtk_search_engine_error (GtkSearchEngine *engine, + const gchar *error_message); + +G_END_DECLS + +#endif /* __GTK_SEARCH_ENGINE_H__ */ diff --git a/gtk/gtksearchenginebeagle.c b/gtk/gtksearchenginebeagle.c new file mode 100644 index 0000000000..f85ac06aca --- /dev/null +++ b/gtk/gtksearchenginebeagle.c @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2005 Novell, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * + * Author: Anders Carlsson + * + * Based on nautilus-search-engine-beagle.c + */ + +#include +#include +#include "gtksearchenginebeagle.h" +#if 0 +#include +#endif + +/* We dlopen() all the following from libbeagle at runtime */ + +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 _BeagleHitsSubtractedResponse BeagleHitsSubtractedResponse; +typedef struct _BeagleQueryPartProperty BeagleQueryPartProperty; +typedef struct _BeagleQueryPart BeagleQueryPart; + +#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 GSList *(*beagle_hits_subtracted_response_get_uris) (BeagleHitsSubtractedResponse *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 void (*beagle_query_add_mime_type) (BeagleQuery *query, + const char *mime_type) = NULL; +static void (*beagle_query_set_max_hits) (BeagleQuery *query, + gint max_hits) = 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 gboolean (*beagle_util_daemon_is_running) (void) = NULL; +static BeagleClient *(*beagle_client_new) (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_hits_subtracted_response_get_uris), + MAP (beagle_query_new), + MAP (beagle_query_add_text), + MAP (beagle_query_add_hit_type), + MAP (beagle_query_add_mime_type), + MAP (beagle_query_set_max_hits), + 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), + MAP (beagle_util_daemon_is_running), + MAP (beagle_client_new) +#undef MAP +}; + +static void +open_libbeagle (void) +{ + static gboolean done = FALSE; + + if (!done) + { + int i; + GModule *beagle; + + done = TRUE; + + beagle = g_module_open ("libbeagle.so.0", G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL); + if (!beagle) + return; + + 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); + + for (i = 0; i < G_N_ELEMENTS (beagle_dl_mapping); i++) + beagle_dl_mapping[i].fn_ptr_ref = NULL; + + return; + } + } + } +} + + +struct _GtkSearchEngineBeaglePrivate +{ + BeagleClient *client; + GtkQuery *query; + + BeagleQuery *current_query; + char *current_query_uri_prefix; + gboolean query_finished; +}; + + +G_DEFINE_TYPE (GtkSearchEngineBeagle, _gtk_search_engine_beagle, GTK_TYPE_SEARCH_ENGINE); + +static void +finalize (GObject *object) +{ + GtkSearchEngineBeagle *beagle; + + beagle = GTK_SEARCH_ENGINE_BEAGLE (object); + + if (beagle->priv->current_query) + { + g_object_unref (beagle->priv->current_query); + beagle->priv->current_query = NULL; + g_free (beagle->priv->current_query_uri_prefix); + beagle->priv->current_query_uri_prefix = NULL; + } + + if (beagle->priv->query) + { + g_object_unref (beagle->priv->query); + beagle->priv->query = NULL; + } + + if (beagle->priv->client) + { + g_object_unref (beagle->priv->client); + beagle->priv->client = NULL; + } + + G_OBJECT_CLASS (_gtk_search_engine_beagle_parent_class)->finalize (object); +} + +static void +beagle_hits_added (BeagleQuery *query, + BeagleHitsAddedResponse *response, + GtkSearchEngineBeagle *engine) +{ + GSList *hits, *list; + GList *hit_uris; + const gchar *uri; + + hit_uris = NULL; + + hits = beagle_hits_added_response_get_hits (response); + + for (list = hits; list != NULL; list = list->next) + { + BeagleHit *hit = BEAGLE_HIT (list->data); + + uri = beagle_hit_get_uri (hit); + + if (engine->priv->current_query_uri_prefix && + !g_str_has_prefix (uri, engine->priv->current_query_uri_prefix)) + continue; + + hit_uris = g_list_prepend (hit_uris, (char *)uri); + } + + _gtk_search_engine_hits_added (GTK_SEARCH_ENGINE (engine), hit_uris); + g_list_free (hit_uris); +} + +static void +beagle_hits_subtracted (BeagleQuery *query, + BeagleHitsSubtractedResponse *response, + GtkSearchEngineBeagle *engine) +{ + GSList *uris, *list; + GList *hit_uris; + + hit_uris = NULL; + + uris = beagle_hits_subtracted_response_get_uris (response); + + for (list = uris; list != NULL; list = list->next) + { + hit_uris = g_list_prepend (hit_uris, (char *)list->data); + } + + _gtk_search_engine_hits_subtracted (GTK_SEARCH_ENGINE (engine), hit_uris); + g_list_free (hit_uris); +} + +static void +beagle_finished (BeagleQuery *query, + BeagleFinishedResponse *response, + GtkSearchEngineBeagle *engine) +{ + /* For some reason we keep getting finished events, + * only emit finished once */ + if (engine->priv->query_finished) + return; + + engine->priv->query_finished = TRUE; + _gtk_search_engine_finished (GTK_SEARCH_ENGINE (engine)); +} + +static void +beagle_error (BeagleQuery *query, + GError *error, + GtkSearchEngineBeagle *engine) +{ + _gtk_search_engine_error (GTK_SEARCH_ENGINE (engine), error->message); +} + +static void +gtk_search_engine_beagle_start (GtkSearchEngine *engine) +{ + GtkSearchEngineBeagle *beagle; + GError *error; + GList *mimetypes, *l; + gchar *text, *mimetype; + + error = NULL; + beagle = GTK_SEARCH_ENGINE_BEAGLE (engine); + + g_return_if_fail (beagle->priv->query != NULL); + + if (beagle->priv->current_query) + return; + + beagle->priv->query_finished = FALSE; + beagle->priv->current_query = beagle_query_new (); + g_signal_connect (beagle->priv->current_query, + "hits-added", G_CALLBACK (beagle_hits_added), engine); + g_signal_connect (beagle->priv->current_query, + "hits-subtracted", G_CALLBACK (beagle_hits_subtracted), engine); + g_signal_connect (beagle->priv->current_query, + "finished", G_CALLBACK (beagle_finished), engine); + g_signal_connect (beagle->priv->current_query, + "error", G_CALLBACK (beagle_error), engine); + + /* We only want files */ + beagle_query_add_hit_type (beagle->priv->current_query, + "File"); + beagle_query_set_max_hits (beagle->priv->current_query, + 1000); + + text = _gtk_query_get_text (beagle->priv->query); + beagle_query_add_text (beagle->priv->current_query, + text); + + mimetypes = _gtk_query_get_mime_types (beagle->priv->query); + for (l = mimetypes; l != NULL; l = l->next) + { + mimetype = l->data; + beagle_query_add_mime_type (beagle->priv->current_query, mimetype); + } + + beagle->priv->current_query_uri_prefix = _gtk_query_get_location (beagle->priv->query); + + if (!beagle_client_send_request_async (beagle->priv->client, + BEAGLE_REQUEST (beagle->priv->current_query), &error)) + { + _gtk_search_engine_error (engine, error->message); + g_error_free (error); + } + + /* These must live during the lifetime of the query */ + g_free (text); + g_list_foreach (mimetypes, (GFunc)g_free, NULL); + g_list_free (mimetypes); +} + +static void +gtk_search_engine_beagle_stop (GtkSearchEngine *engine) +{ + GtkSearchEngineBeagle *beagle; + + beagle = GTK_SEARCH_ENGINE_BEAGLE (engine); + + if (beagle->priv->current_query) + { + g_object_unref (beagle->priv->current_query); + beagle->priv->current_query = NULL; + g_free (beagle->priv->current_query_uri_prefix); + beagle->priv->current_query_uri_prefix = NULL; + } +} + +static gboolean +gtk_search_engine_beagle_is_indexed (GtkSearchEngine *engine) +{ + return TRUE; +} + +static void +gtk_search_engine_beagle_set_query (GtkSearchEngine *engine, + GtkQuery *query) +{ + GtkSearchEngineBeagle *beagle; + + beagle = GTK_SEARCH_ENGINE_BEAGLE (engine); + + if (query) + g_object_ref (query); + + if (beagle->priv->query) + g_object_unref (beagle->priv->query); + + beagle->priv->query = query; +} + +static void +_gtk_search_engine_beagle_class_init (GtkSearchEngineBeagleClass *class) +{ + GObjectClass *gobject_class; + GtkSearchEngineClass *engine_class; + + gobject_class = G_OBJECT_CLASS (class); + gobject_class->finalize = finalize; + + engine_class = GTK_SEARCH_ENGINE_CLASS (class); + engine_class->set_query = gtk_search_engine_beagle_set_query; + engine_class->start = gtk_search_engine_beagle_start; + engine_class->stop = gtk_search_engine_beagle_stop; + engine_class->is_indexed = gtk_search_engine_beagle_is_indexed; + + g_type_class_add_private (gobject_class, sizeof (GtkSearchEngineBeaglePrivate)); +} + +static void +_gtk_search_engine_beagle_init (GtkSearchEngineBeagle *engine) +{ + engine->priv = G_TYPE_INSTANCE_GET_PRIVATE (engine, GTK_TYPE_SEARCH_ENGINE_BEAGLE, GtkSearchEngineBeaglePrivate); +} + + +GtkSearchEngine * +_gtk_search_engine_beagle_new (void) +{ + GtkSearchEngineBeagle *engine; + BeagleClient *client; + + open_libbeagle (); + + if (!beagle_util_daemon_is_running) + return NULL; + + /* check whether daemon is running as beagle_client_new + * doesn't fail when a stale socket file exists */ + if (!beagle_util_daemon_is_running ()) + return NULL; + + client = beagle_client_new (NULL); + + if (client == NULL) + return NULL; + + engine = g_object_new (GTK_TYPE_SEARCH_ENGINE_BEAGLE, NULL); + + engine->priv->client = client; + + return GTK_SEARCH_ENGINE (engine); +} diff --git a/gtk/gtksearchenginebeagle.h b/gtk/gtksearchenginebeagle.h new file mode 100644 index 0000000000..e768ff11ac --- /dev/null +++ b/gtk/gtksearchenginebeagle.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2005 Novell, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * + * Author: Anders Carlsson + * + * Based on nautilus-search-engine-beagle.h + */ + +#ifndef __GTK_SEARCH_ENGINE_BEAGLE_H__ +#define __GTK_SEARCH_ENGINE_BEAGLE_H__ + +#include "gtksearchengine.h" + +G_BEGIN_DECLS + +#define GTK_TYPE_SEARCH_ENGINE_BEAGLE (_gtk_search_engine_beagle_get_type ()) +#define GTK_SEARCH_ENGINE_BEAGLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_SEARCH_ENGINE_BEAGLE, GtkSearchEngineBeagle)) +#define GTK_SEARCH_ENGINE_BEAGLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_SEARCH_ENGINE_BEAGLE, GtkSearchEngineBeagleClass)) +#define GTK_IS_SEARCH_ENGINE_BEAGLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_SEARCH_ENGINE_BEAGLE)) +#define GTK_IS_SEARCH_ENGINE_BEAGLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SEARCH_ENGINE_BEAGLE)) +#define GTK_SEARCH_ENGINE_BEAGLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_SEARCH_ENGINE_BEAGLE, GtkSearchEngineBeagleClass)) + +typedef struct _GtkSearchEngineBeagle GtkSearchEngineBeagle; +typedef struct _GtkSearchEngineBeagleClass GtkSearchEngineBeagleClass; +typedef struct _GtkSearchEngineBeaglePrivate GtkSearchEngineBeaglePrivate; + +struct _GtkSearchEngineBeagle +{ + GtkSearchEngine parent; + + GtkSearchEngineBeaglePrivate *priv; +}; + +struct _GtkSearchEngineBeagleClass +{ + GtkSearchEngineClass parent_class; +}; + +GType _gtk_search_engine_beagle_get_type (void); + +GtkSearchEngine* _gtk_search_engine_beagle_new (void); + +G_END_DECLS + +#endif /* __GTK_SEARCH_ENGINE_BEAGLE_H__ */ diff --git a/gtk/gtksearchenginesimple.c b/gtk/gtksearchenginesimple.c new file mode 100644 index 0000000000..bfe1aec138 --- /dev/null +++ b/gtk/gtksearchenginesimple.c @@ -0,0 +1,378 @@ +/* + * Copyright (C) 2005 Red Hat, Inc + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * + * Author: Alexander Larsson + * + * Based on nautilus-search-engine-simple.c + */ + +#define _XOPEN_SOURCE 500 +#define _GNU_SOURCE + +#include +#include "gtksearchenginesimple.h" + +#define XDG_PREFIX _gtk_xdg +#include "xdgmime/xdgmime.h" + +#include +#include +#include + +#define BATCH_SIZE 500 + +typedef struct +{ + GtkSearchEngineSimple *engine; + + gchar *path; + GList *mime_types; + gchar **words; + GList *found_list; + + gint n_processed_files; + GList *uri_hits; + + /* accessed on both threads: */ + volatile gboolean cancelled; +} SearchThreadData; + + +struct _GtkSearchEngineSimplePrivate +{ + GtkQuery *query; + + SearchThreadData *active_search; + + gboolean query_finished; +}; + + +G_DEFINE_TYPE (GtkSearchEngineSimple, _gtk_search_engine_simple, GTK_TYPE_SEARCH_ENGINE); + +static void +finalize (GObject *object) +{ + GtkSearchEngineSimple *simple; + + simple = GTK_SEARCH_ENGINE_SIMPLE (object); + + if (simple->priv->query) + { + g_object_unref (simple->priv->query); + simple->priv->query = NULL; + } + + g_free (simple->priv); + + G_OBJECT_CLASS (_gtk_search_engine_simple_parent_class)->finalize (object); +} + +static SearchThreadData * +search_thread_data_new (GtkSearchEngineSimple *engine, + GtkQuery *query) +{ + SearchThreadData *data; + char *text, *lower, *uri; + + data = g_new0 (SearchThreadData, 1); + + data->engine = engine; + uri = _gtk_query_get_location (query); + if (uri != NULL) + { + data->path = g_filename_from_uri (uri, NULL, NULL); + g_free (uri); + } + if (data->path == NULL) + data->path = g_strdup (g_get_home_dir ()); + + text = _gtk_query_get_text (query); + lower = g_ascii_strdown (text, -1); + data->words = g_strsplit (lower, " ", -1); + g_free (text); + g_free (lower); + + data->mime_types = _gtk_query_get_mime_types (query); + + return data; +} + +static void +search_thread_data_free (SearchThreadData *data) +{ + g_free (data->path); + g_strfreev (data->words); + g_list_foreach (data->mime_types, (GFunc)g_free, NULL); + g_list_free (data->mime_types); + g_free (data); +} + +static gboolean +search_thread_done_idle (gpointer user_data) +{ + SearchThreadData *data; + + data = user_data; + + if (!data->cancelled) + { + _gtk_search_engine_finished (GTK_SEARCH_ENGINE (data->engine)); + data->engine->priv->active_search = NULL; + } + + search_thread_data_free (data); + + return FALSE; +} + +typedef struct +{ + GList *uris; + SearchThreadData *thread_data; +} SearchHits; + + +static gboolean +search_thread_add_hits_idle (gpointer user_data) +{ + SearchHits *hits; + + hits = user_data; + + if (!hits->thread_data->cancelled) + { + _gtk_search_engine_hits_added (GTK_SEARCH_ENGINE (hits->thread_data->engine), + hits->uris); + } + + g_list_foreach (hits->uris, (GFunc)g_free, NULL); + g_list_free (hits->uris); + g_free (hits); + + return FALSE; +} + +static void +send_batch (SearchThreadData *data) +{ + SearchHits *hits; + + data->n_processed_files = 0; + + if (data->uri_hits) + { + hits = g_new (SearchHits, 1); + hits->uris = data->uri_hits; + hits->thread_data = data; + g_idle_add (search_thread_add_hits_idle, hits); + } + data->uri_hits = NULL; +} + +static GStaticPrivate search_thread_data = G_STATIC_PRIVATE_INIT; + +static int +search_visit_func (const char *fpath, + const struct stat *sb, + int typeflag, + struct FTW *ftwbuf) +{ + SearchThreadData *data; + gint i; + const gchar *name; + gchar *lower_name, *path, *mime_type; + gchar *uri; + gboolean hit; + GList *l; + gboolean is_hidden; + + data = (SearchThreadData*)g_static_private_get (&search_thread_data); + + if (data->cancelled) + return FTW_STOP; + + name = strrchr (fpath, '/'); + if (name) + name++; + else + name = fpath; + + path = g_build_filename (data->path, fpath, NULL); + + is_hidden = *name == '.'; + + hit = FALSE; + + if (!is_hidden) + { + lower_name = g_ascii_strdown (name, -1); + + hit = TRUE; + for (i = 0; data->words[i] != NULL; i++) + { + if (strstr (lower_name, data->words[i]) == NULL) + { + hit = FALSE; + break; + } + } + g_free (lower_name); + } + + if (hit && data->mime_types != NULL) + { + hit = FALSE; + mime_type = xdg_mime_get_mime_type_for_file (path, (struct stat *)sb); + for (l = data->mime_types; l != NULL; l = l->next) + { + if (strcmp (mime_type, l->data) == 0) + { + hit = TRUE; + break; + } + } + + g_free (mime_type); + } + + if (hit) + { + uri = g_filename_to_uri (path, NULL, NULL); + data->uri_hits = g_list_prepend (data->uri_hits, uri); + } + + data->n_processed_files++; + + if (data->n_processed_files > BATCH_SIZE) + send_batch (data); + + if (is_hidden) + return FTW_SKIP_SUBTREE; + else + return FTW_CONTINUE; +} + +static gpointer +search_thread_func (gpointer user_data) +{ + SearchThreadData *data; + + data = user_data; + + g_static_private_set (&search_thread_data, data, NULL); + + nftw (data->path, search_visit_func, 20, FTW_ACTIONRETVAL | FTW_PHYS); + + send_batch (data); + + g_idle_add (search_thread_done_idle, data); + + return NULL; +} + +static void +gtk_search_engine_simple_start (GtkSearchEngine *engine) +{ + GtkSearchEngineSimple *simple; + SearchThreadData *data; + + simple = GTK_SEARCH_ENGINE_SIMPLE (engine); + + if (simple->priv->active_search != NULL) + return; + + if (simple->priv->query == NULL) + return; + + data = search_thread_data_new (simple, simple->priv->query); + + g_thread_create (search_thread_func, data, FALSE, NULL); + + simple->priv->active_search = data; +} + +static void +gtk_search_engine_simple_stop (GtkSearchEngine *engine) +{ + GtkSearchEngineSimple *simple; + + simple = GTK_SEARCH_ENGINE_SIMPLE (engine); + + if (simple->priv->active_search != NULL) + { + simple->priv->active_search->cancelled = TRUE; + simple->priv->active_search = NULL; + } +} + +static gboolean +gtk_search_engine_simple_is_indexed (GtkSearchEngine *engine) +{ + return FALSE; +} + +static void +gtk_search_engine_simple_set_query (GtkSearchEngine *engine, + GtkQuery *query) +{ + GtkSearchEngineSimple *simple; + + simple = GTK_SEARCH_ENGINE_SIMPLE (engine); + + if (query) + g_object_ref (query); + + if (simple->priv->query) + g_object_unref (simple->priv->query); + + simple->priv->query = query; +} + +static void +_gtk_search_engine_simple_class_init (GtkSearchEngineSimpleClass *class) +{ + GObjectClass *gobject_class; + GtkSearchEngineClass *engine_class; + + gobject_class = G_OBJECT_CLASS (class); + gobject_class->finalize = finalize; + + engine_class = GTK_SEARCH_ENGINE_CLASS (class); + engine_class->set_query = gtk_search_engine_simple_set_query; + engine_class->start = gtk_search_engine_simple_start; + engine_class->stop = gtk_search_engine_simple_stop; + engine_class->is_indexed = gtk_search_engine_simple_is_indexed; + + g_type_class_add_private (gobject_class, sizeof (GtkSearchEngineSimplePrivate)); +} + +static void +_gtk_search_engine_simple_init (GtkSearchEngineSimple *engine) +{ + engine->priv = G_TYPE_INSTANCE_GET_PRIVATE (engine, GTK_TYPE_SEARCH_ENGINE_SIMPLE, GtkSearchEngineSimplePrivate); +} + +GtkSearchEngine * +_gtk_search_engine_simple_new (void) +{ + GtkSearchEngine *engine; + + engine = g_object_new (GTK_TYPE_SEARCH_ENGINE_SIMPLE, NULL); + + return engine; +} diff --git a/gtk/gtksearchenginesimple.h b/gtk/gtksearchenginesimple.h new file mode 100644 index 0000000000..1a93000dbc --- /dev/null +++ b/gtk/gtksearchenginesimple.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2005 Red Hat, Inc + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * + * Author: Alexander Larsson + * + * Based on nautilus-search-engine-simple.h + */ + +#ifndef __GTK_SEARCH_ENGINE_SIMPLE_H__ +#define __GTK_SEARCH_ENGINE_SIMPLE_H__ + +#include "gtksearchengine.h" + +G_END_DECLS + +#define GTK_TYPE_SEARCH_ENGINE_SIMPLE (_gtk_search_engine_simple_get_type ()) +#define GTK_SEARCH_ENGINE_SIMPLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_SEARCH_ENGINE_SIMPLE, GtkSearchEngineSimple)) +#define GTK_SEARCH_ENGINE_SIMPLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_SEARCH_ENGINE_SIMPLE, GtkSearchEngineSimpleClass)) +#define GTK_IS_SEARCH_ENGINE_SIMPLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_SEARCH_ENGINE_SIMPLE)) +#define GTK_IS_SEARCH_ENGINE_SIMPLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SEARCH_ENGINE_SIMPLE)) +#define GTK_SEARCH_ENGINE_SIMPLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_SEARCH_ENGINE_SIMPLE, GtkSearchEngineSimpleClass)) + +typedef struct _GtkSearchEngineSimple GtkSearchEngineSimple; +typedef struct _GtkSearchEngineSimpleClass GtkSearchEngineSimpleClass; +typedef struct _GtkSearchEngineSimplePrivate GtkSearchEngineSimplePrivate; + +struct _GtkSearchEngineSimple +{ + GtkSearchEngine parent; + + GtkSearchEngineSimplePrivate *priv; +}; + +struct _GtkSearchEngineSimpleClass +{ + GtkSearchEngineClass parent_class; +}; + +GType _gtk_search_engine_simple_get_type (void); + +GtkSearchEngine* _gtk_search_engine_simple_new (void); + +G_END_DECLS + +#endif /* __GTK_SEARCH_ENGINE_SIMPLE_H__ */ diff --git a/gtk/gtksearchenginetracker.c b/gtk/gtksearchenginetracker.c new file mode 100644 index 0000000000..7d63461369 --- /dev/null +++ b/gtk/gtksearchenginetracker.c @@ -0,0 +1,362 @@ +/* + * Copyright (C) 2005 Mr Jamie McCracken + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * + * Author: Jamie McCracken + * + * Based on nautilus-search-engine-tracker.c + */ + +#include +#include +#include "gtksearchenginetracker.h" +#if 0 +#include +#endif + +typedef struct _TrackerClient TrackerClient; + +typedef void (*TrackerArrayReply) (char **result, GError *error, gpointer user_data); + +static TrackerClient * (*tracker_connect) (gboolean enable_warnings) = NULL; +static void (*tracker_disconnect) (TrackerClient *client) = NULL; +static void (*tracker_cancel_last_call) (TrackerClient *client) = NULL; + +static void (*tracker_search_metadata_by_text_async) (TrackerClient *client, + const char *query, + TrackerArrayReply callback, + gpointer user_data) = NULL; +static void (*tracker_search_metadata_by_text_and_mime_async) (TrackerClient *client, + const char *query, + const char **mimes, + TrackerArrayReply callback, + gpointer user_data) = NULL; +static void (*tracker_search_metadata_by_text_and_location_async) (TrackerClient *client, + const char *query, + const char *location, + TrackerArrayReply callback, + gpointer user_data) = NULL; +static void (*tracker_search_metadata_by_text_and_mime_and_location_async) (TrackerClient *client, + const char *query, + const char **mimes, + const char *location, + TrackerArrayReply callback, + gpointer user_data) = NULL; + +static struct TrackerDlMapping +{ + const char *fn_name; + gpointer *fn_ptr_ref; +} tracker_dl_mapping[] = +{ +#define MAP(a) { #a, (gpointer *)&a } + MAP (tracker_connect), + MAP (tracker_disconnect), + MAP (tracker_cancel_last_call), + MAP (tracker_search_metadata_by_text_async), + MAP (tracker_search_metadata_by_text_and_mime_async), + MAP (tracker_search_metadata_by_text_and_location_async), + MAP (tracker_search_metadata_by_text_and_mime_and_location_async) +#undef MAP +}; + +static void +open_libtracker (void) +{ + static gboolean done = FALSE; + + if (!done) + { + int i; + GModule *tracker; + + done = TRUE; + + tracker = g_module_open ("libtracker.so.0", G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL); + if (!tracker) + return; + + for (i = 0; i < G_N_ELEMENTS (tracker_dl_mapping); i++) + { + if (!g_module_symbol (tracker, tracker_dl_mapping[i].fn_name, + tracker_dl_mapping[i].fn_ptr_ref)) + { + g_warning ("Missing symbol '%s' in libtracker\n", + tracker_dl_mapping[i].fn_name); + g_module_close (tracker); + + for (i = 0; i < G_N_ELEMENTS (tracker_dl_mapping); i++) + tracker_dl_mapping[i].fn_ptr_ref = NULL; + + return; + } + } + } +} + +struct _GtkSearchEngineTrackerPrivate +{ + GtkQuery *query; + TrackerClient *client; + gboolean query_pending; +}; + +G_DEFINE_TYPE (GtkSearchEngineTracker, _gtk_search_engine_tracker, GTK_TYPE_SEARCH_ENGINE); + + +static void +finalize (GObject *object) +{ + GtkSearchEngineTracker *tracker; + + tracker = GTK_SEARCH_ENGINE_TRACKER (object); + + if (tracker->priv->query) + { + g_object_unref (tracker->priv->query); + tracker->priv->query = NULL; + } + + tracker_disconnect (tracker->priv->client); + + G_OBJECT_CLASS (_gtk_search_engine_tracker_parent_class)->finalize (object); +} + + +static void +search_callback (gchar **results, + GError *error, + gpointer user_data) +{ + GtkSearchEngineTracker *tracker; + gchar **results_p; + GList *hit_uris; + + tracker = GTK_SEARCH_ENGINE_TRACKER (user_data); + hit_uris = NULL; + + tracker->priv->query_pending = FALSE; + + if (error) + { + _gtk_search_engine_error ( GTK_SEARCH_ENGINE (tracker), error->message); + g_error_free (error); + return; + } + + if (!results) + return; + + for (results_p = results; *results_p; results_p++) + { + gchar *uri; + + uri = g_filename_to_uri ((char *)*results_p, NULL, NULL); + if (uri) + hit_uris = g_list_prepend (hit_uris, (char *)uri); + } + + _gtk_search_engine_hits_added (GTK_SEARCH_ENGINE (tracker), hit_uris); + _gtk_search_engine_finished (GTK_SEARCH_ENGINE (tracker)); + + g_strfreev (results); + g_list_foreach (hit_uris, (GFunc)g_free, NULL); + g_list_free (hit_uris); +} + + +static void +gtk_search_engine_tracker_start (GtkSearchEngine *engine) +{ + GtkSearchEngineTracker *tracker; + GList *mimetypes, *l; + gchar *search_text, *location, *location_uri; + gchar **mimes; + gint i, mime_count; + + tracker = GTK_SEARCH_ENGINE_TRACKER (engine); + + if (tracker->priv->query_pending) + return; + + if (tracker->priv->query == NULL) + return; + + search_text = _gtk_query_get_text (tracker->priv->query); + + mimetypes = _gtk_query_get_mime_types (tracker->priv->query); + + location_uri = _gtk_query_get_location (tracker->priv->query); + + if (location_uri) + { + location = g_filename_from_uri (location_uri, NULL, NULL); + g_free (location_uri); + } + else + { + location = NULL; + } + + mime_count = g_list_length (mimetypes); + + i = 0; + + /* convert list into array */ + if (mime_count > 0) + { + mimes = g_new (gchar *, (mime_count + 1)); + + for (l = mimetypes; l != NULL; l = l->next) + { + mimes[i] = g_strdup (l->data); + i++; + } + + mimes[mime_count] = NULL; + + if (location) + { + tracker_search_metadata_by_text_and_mime_and_location_async (tracker->priv->client, + search_text, (const char **)mimes, location, + search_callback, + tracker); + g_free (location); + } + else + { + tracker_search_metadata_by_text_and_mime_async (tracker->priv->client, + search_text, (const char**)mimes, + search_callback, + tracker); + } + + g_strfreev (mimes); + + + } + else + { + if (location) + { + tracker_search_metadata_by_text_and_location_async (tracker->priv->client, + search_text, + location, + search_callback, + tracker); + g_free (location); + } + else + { + tracker_search_metadata_by_text_async (tracker->priv->client, + search_text, + search_callback, + tracker); + } + } + + tracker->priv->query_pending = TRUE; + g_free (search_text); + g_list_foreach (mimetypes, (GFunc)g_free, NULL); + g_list_free (mimetypes); +} + +static void +gtk_search_engine_tracker_stop (GtkSearchEngine *engine) +{ + GtkSearchEngineTracker *tracker; + + tracker = GTK_SEARCH_ENGINE_TRACKER (engine); + + if (tracker->priv->query && tracker->priv->query_pending) + { + tracker_cancel_last_call (tracker->priv->client); + tracker->priv->query_pending = FALSE; + } +} + +static gboolean +gtk_search_engine_tracker_is_indexed (GtkSearchEngine *engine) +{ + return TRUE; +} + +static void +gtk_search_engine_tracker_set_query (GtkSearchEngine *engine, + GtkQuery *query) +{ + GtkSearchEngineTracker *tracker; + + tracker = GTK_SEARCH_ENGINE_TRACKER (engine); + + if (query) + g_object_ref (query); + + if (tracker->priv->query) + g_object_unref (tracker->priv->query); + + tracker->priv->query = query; +} + +static void +_gtk_search_engine_tracker_class_init (GtkSearchEngineTrackerClass *class) +{ + GObjectClass *gobject_class; + GtkSearchEngineClass *engine_class; + + gobject_class = G_OBJECT_CLASS (class); + gobject_class->finalize = finalize; + + engine_class = GTK_SEARCH_ENGINE_CLASS (class); + engine_class->set_query = gtk_search_engine_tracker_set_query; + engine_class->start = gtk_search_engine_tracker_start; + engine_class->stop = gtk_search_engine_tracker_stop; + engine_class->is_indexed = gtk_search_engine_tracker_is_indexed; + + g_type_class_add_private (gobject_class, sizeof (GtkSearchEngineTrackerPrivate)); +} + +static void +_gtk_search_engine_tracker_init (GtkSearchEngineTracker *engine) +{ + engine->priv = G_TYPE_INSTANCE_GET_PRIVATE (engine, GTK_TYPE_SEARCH_ENGINE_TRACKER, GtkSearchEngineTrackerPrivate); +} + + +GtkSearchEngine * +_gtk_search_engine_tracker_new (void) +{ + GtkSearchEngineTracker *engine; + TrackerClient *tracker_client; + + open_libtracker (); + + if (!tracker_connect) + return NULL; + + tracker_client = tracker_connect (FALSE); + + if (!tracker_client) + return NULL; + + engine = g_object_new (GTK_TYPE_SEARCH_ENGINE_TRACKER, NULL); + + engine->priv->client = tracker_client; + + engine->priv->query_pending = FALSE; + + return GTK_SEARCH_ENGINE (engine); +} diff --git a/gtk/gtksearchenginetracker.h b/gtk/gtksearchenginetracker.h new file mode 100644 index 0000000000..089b7e8b1f --- /dev/null +++ b/gtk/gtksearchenginetracker.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2005 Mr Jamie McCracken + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * + * Author: Jamie McCracken (jamiemcc@gnome.org) + * + * Based on nautilus-search-engine-tracker.h + */ + +#ifndef __GTK_SEARCH_ENGINE_TRACKER_H__ +#define __GTK_SEARCH_ENGINE_TRACKER_H__ + +#include "gtksearchengine.h" + +G_BEGIN_DECLS + +#define GTK_TYPE_SEARCH_ENGINE_TRACKER (_gtk_search_engine_tracker_get_type ()) +#define GTK_SEARCH_ENGINE_TRACKER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_SEARCH_ENGINE_TRACKER, GtkSearchEngineTracker)) +#define GTK_SEARCH_ENGINE_TRACKER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_SEARCH_ENGINE_TRACKER, GtkSearchEngineTrackerClass)) +#define GTK_IS_SEARCH_ENGINE_TRACKER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_SEARCH_ENGINE_TRACKER)) +#define GTK_IS_SEARCH_ENGINE_TRACKER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SEARCH_ENGINE_TRACKER)) +#define GTK_SEARCH_ENGINE_TRACKER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_SEARCH_ENGINE_TRACKER, GtkSearchEngineTrackerClass)) + +typedef struct _GtkSearchEngineTracker GtkSearchEngineTracker; +typedef struct _GtkSearchEngineTrackerClass GtkSearchEngineTrackerClass; +typedef struct _GtkSearchEngineTrackerPrivate GtkSearchEngineTrackerPrivate; + +struct _GtkSearchEngineTracker +{ + GtkSearchEngine parent; + + GtkSearchEngineTrackerPrivate *priv; +}; + +struct _GtkSearchEngineTrackerClass +{ + GtkSearchEngineClass parent_class; +}; + +GType _gtk_search_engine_tracker_get_type (void); + +GtkSearchEngine* _gtk_search_engine_tracker_new (void); + +G_END_DECLS + +#endif /* __GTK_SEARCH_ENGINE_TRACKER_H__ */