diff --git a/data/org.gnome.nautilus.gschema.xml b/data/org.gnome.nautilus.gschema.xml index e162ebb42..b64e526a0 100644 --- a/data/org.gnome.nautilus.gschema.xml +++ b/data/org.gnome.nautilus.gschema.xml @@ -207,6 +207,11 @@ Bulk rename utility If set, Nautilus will append URIs of selected files and treat the result as a command line for bulk renaming. Bulk rename applications can register themselves in this key by setting the key to a space-separated string of their executable name and any command line options. If the executable name is not set to a full path, it will be searched for in the search path. + + true + Enable interactive (type-ahead) search + If set to true, enables interactive search, similar to Nautilus 3.4. + true Whether to open the hovered folder after a timeout when drag and drop operation diff --git a/src/nautilus-global-preferences.h b/src/nautilus-global-preferences.h index 7e52f6c77..b5b761da1 100644 --- a/src/nautilus-global-preferences.h +++ b/src/nautilus-global-preferences.h @@ -164,6 +164,9 @@ typedef enum /* Recent files */ #define NAUTILUS_PREFERENCES_RECENT_FILES_ENABLED "remember-recent-files" +/* Interactive search (typeahead) */ +#define NAUTILUS_PREFERENCES_ENABLE_INTERACTIVE_SEARCH "enable-interactive-search" + /* Move to trash shorcut changed dialog */ #define NAUTILUS_PREFERENCES_SHOW_MOVE_TO_TRASH_SHORTCUT_CHANGED_DIALOG "show-move-to-trash-shortcut-changed-dialog" diff --git a/src/nautilus-list-view.c b/src/nautilus-list-view.c index 0d2f54fb5..9f9a1692f 100644 --- a/src/nautilus-list-view.c +++ b/src/nautilus-list-view.c @@ -2706,6 +2706,7 @@ nautilus_list_view_set_selection (NautilusFilesView *view, GList *node; GList *iters, *l; NautilusFile *file; + GtkTreePath *path = NULL; list_view = NAUTILUS_LIST_VIEW (view); tree_selection = gtk_tree_view_get_selection (list_view->details->tree_view); @@ -2722,10 +2723,22 @@ nautilus_list_view_set_selection (NautilusFilesView *view, { gtk_tree_selection_select_iter (tree_selection, (GtkTreeIter *) l->data); + if (!path) + path = gtk_tree_model_get_path (GTK_TREE_MODEL (list_view->details->model), (GtkTreeIter *) l->data); } + g_list_free_full (iters, g_free); } + if (path) { + gtk_tree_view_set_cursor_on_cell (list_view->details->tree_view, + path, + list_view->details->file_name_column, + GTK_CELL_RENDERER (list_view->details->file_name_cell), + TRUE); + gtk_tree_path_free (path); + } + g_signal_handlers_unblock_by_func (tree_selection, list_selection_changed_callback, view); nautilus_files_view_notify_selection_changed (view); } @@ -3600,3 +3613,9 @@ nautilus_list_view_new (NautilusWindowSlot *slot) "window-slot", slot, NULL); } + +GtkTreeView * +nautilus_list_view_get_tree_view (NautilusListView *list_view) +{ + return list_view->details->tree_view; +} diff --git a/src/nautilus-list-view.h b/src/nautilus-list-view.h index f77543100..bbcb565d7 100644 --- a/src/nautilus-list-view.h +++ b/src/nautilus-list-view.h @@ -52,5 +52,6 @@ typedef struct { GType nautilus_list_view_get_type (void); NautilusFilesView * nautilus_list_view_new (NautilusWindowSlot *slot); +GtkTreeView * nautilus_list_view_get_tree_view (NautilusListView *list_view); #endif /* NAUTILUS_LIST_VIEW_H */ diff --git a/src/nautilus-preferences-window.c b/src/nautilus-preferences-window.c index 8c4f981c0..b545fc3ec 100644 --- a/src/nautilus-preferences-window.c +++ b/src/nautilus-preferences-window.c @@ -59,6 +59,8 @@ "trash_confirm_checkbutton" #define NAUTILUS_PREFERENCES_DIALOG_AUTOMATIC_DECOMPRESSION_WIDGET \ "automatic_decompression_checkbutton" +#define NAUTILUS_PREFERENCES_DIALOG_ENABLE_INTERACTIVE_SEARCH_WIDGET \ + "interactive_search_checkbutton" /* int enums */ #define NAUTILUS_PREFERENCES_DIALOG_THUMBNAIL_LIMIT_WIDGET \ @@ -492,6 +494,9 @@ static void nautilus_preferences_window_setup(GtkBuilder *builder, bind_builder_bool (builder, nautilus_preferences, NAUTILUS_PREFERENCES_DIALOG_AUTOMATIC_DECOMPRESSION_WIDGET, NAUTILUS_PREFERENCES_AUTOMATIC_DECOMPRESSION); + bind_builder_bool (builder, nautilus_preferences, + NAUTILUS_PREFERENCES_DIALOG_ENABLE_INTERACTIVE_SEARCH_WIDGET, + NAUTILUS_PREFERENCES_ENABLE_INTERACTIVE_SEARCH); bind_builder_bool (builder, nautilus_list_view_preferences, NAUTILUS_PREFERENCES_DIALOG_LIST_VIEW_USE_TREE_WIDGET, NAUTILUS_PREFERENCES_LIST_VIEW_USE_TREE); diff --git a/src/nautilus-window-slot.c b/src/nautilus-window-slot.c index c5c94e103..67a6fd0a9 100644 --- a/src/nautilus-window-slot.c +++ b/src/nautilus-window-slot.c @@ -122,6 +122,17 @@ typedef struct GError *mount_error; gboolean tried_mount; gint view_mode_before_search; + + /* Interactive search */ + gboolean isearch_enable; + gboolean isearch_imcontext_changed; + gboolean isearch_disable_hide; + NautilusFile *isearch_selected_file; + GtkWidget *isearch_window; + GtkWidget *isearch_entry; + gulong isearch_entry_changed_id; + guint isearch_timeout_id; + gulong isearch_configure_event_id; } NautilusWindowSlotPrivate; G_DEFINE_TYPE_WITH_PRIVATE (NautilusWindowSlot, nautilus_window_slot, GTK_TYPE_BOX); @@ -153,6 +164,98 @@ static void trash_state_changed_cb (NautilusTrashMonitor *monitor, gboolean is_empty, gpointer user_data); + +/* Interactive search */ +static void isearch_ensure (NautilusWindowSlot *slot); + +static gboolean isearch_start (NautilusWindowSlot *slot, + GdkDevice *device); + +static void isearch_enable_changed (gpointer callback_data); + +static void isearch_dispose (NautilusWindowSlot *slot); + +static void isearch_hide (NautilusWindowSlot *slot, + GdkDevice *device); + +static gboolean isearch_timeout (gpointer user_data); + +static void isearch_timeout_destroy (gpointer user_data); + +static void isearch_timeout_restart (NautilusWindowSlot *slot); + +static gboolean isearch_window_delete_event (GtkWidget *widget, + GdkEventAny *event, + NautilusWindowSlot *slot); + +static gboolean isearch_window_button_press_event (GtkWidget *widget, + GdkEventButton *event, + NautilusWindowSlot *slot); + +static gboolean isearch_window_scroll_event (GtkWidget *widget, + GdkEventScroll *event, + NautilusWindowSlot *slot); + +static void isearch_activate_items_alternate (NautilusWindowSlot *slot); + +static gboolean isearch_window_key_press_event (GtkWidget *widget, + GdkEventKey *event, + NautilusWindowSlot *slot); + +static void isearch_disable_hide (GtkEntry *entry, + GtkMenu *menu, + gpointer data); + +static void isearch_preedit_changed (GtkEntry *entry, + gchar *preedit, + NautilusWindowSlot *slot); + +static void isearch_activate_event (GtkEntry *entry, + NautilusWindowSlot *slot); + +static gboolean isearch_enable_hide_real (gpointer data); + +static void isearch_enable_hide (GtkWidget *widget, + gpointer data); + +static void send_focus_change (GtkWidget *widget, + GdkDevice *device, + gboolean in); + +static void isearch_entry_changed (GtkWidget *entry, + NautilusWindowSlot *slot); + +static void isearch_position (NautilusWindowSlot *slot); + +static gboolean isearch_compare_filename (const gchar *f1, + const gchar *f2, + guint length); + +static int compare_files (gconstpointer a, + gconstpointer b, + gpointer callback_data); + +static GList *isearch_get_sorted_files (NautilusWindowSlot *slot); + +static NautilusFile *isearch_find (NautilusWindowSlot *slot, + const gchar *text); + +static NautilusFile *isearch_find_next (NautilusWindowSlot *slot, + const gchar *text); + +static NautilusFile *isearch_find_prev (NautilusWindowSlot *slot, + const gchar *text); + +static gboolean isearch_move_next (NautilusWindowSlot *slot); + +static gboolean isearch_move_prev (NautilusWindowSlot *slot); + +static void isearch_set_selection (NautilusWindowSlot *slot, + NautilusFile *file); + +#define ISEARCH_TIMEOUT 5000 + + gboolean nautilus_window_slot_handles_location (NautilusWindowSlot *self, GFile *location) @@ -569,21 +672,86 @@ nautilus_window_slot_handle_event (NautilusWindowSlot *self, action = g_action_map_lookup_action (G_ACTION_MAP (priv->slot_action_group), "search-visible"); - /* If the action is not enabled, don't try to handle search */ - if (g_action_get_enabled (action)) - { - retval = gtk_search_bar_handle_event (GTK_SEARCH_BAR (priv->query_editor), - (GdkEvent *) event); - } + if (priv->isearch_enable) { + GdkEvent *new_event; + gchar *old_text; + const gchar *new_text; + GdkScreen *screen; + gboolean text_modified; + gulong popup_menu_id; + + isearch_ensure (self); + + /* Make a copy of the current text */ + old_text = g_strdup (gtk_entry_get_text (GTK_ENTRY (priv->isearch_entry))); + new_event = gdk_event_copy ((GdkEvent *) event); + g_object_unref (((GdkEventKey *) new_event)->window); + + ((GdkEventKey *) new_event)->window = + g_object_ref (gtk_widget_get_window (priv->isearch_window)); + gtk_widget_realize (priv->isearch_window); + + popup_menu_id = g_signal_connect (priv->isearch_entry, + "popup-menu", G_CALLBACK (gtk_true), + NULL); + + /* Move the entry off screen */ + screen = gtk_widget_get_screen (GTK_WIDGET (self)); + gtk_window_move (GTK_WINDOW (priv->isearch_window), + gdk_screen_get_width (screen) + 1, + gdk_screen_get_height (screen) + 1); + gtk_widget_show (priv->isearch_window); + + /* Send the event to the window. If the preedit_changed signal is emitted during this + * event, we will set priv->imcontext_changed */ + priv->isearch_imcontext_changed = FALSE; + retval = gtk_widget_event (priv->isearch_window, new_event); + gdk_event_free (new_event); + gtk_widget_hide (priv->isearch_window); + + g_signal_handler_disconnect (priv->isearch_entry, popup_menu_id); + + /* We check to make sure that the entry tried to handle the text, and that the text has + * changed. */ + new_text = gtk_entry_get_text (GTK_ENTRY (priv->isearch_entry)); + text_modified = strcmp (old_text, new_text) != 0; + g_free (old_text); + + if (priv->isearch_imcontext_changed || (retval && text_modified)) { + if (isearch_start (self, gdk_event_get_device ((GdkEvent *) event))) { + gtk_widget_grab_focus (GTK_WIDGET (self)); + return TRUE; + } - if (retval) - { - nautilus_window_slot_set_search_visible (self, TRUE); + gtk_entry_set_text (GTK_ENTRY (priv->isearch_entry), ""); + return FALSE; + } + } else { + /* If the action is not enabled, don't try to handle search */ + if (g_action_get_enabled (action)) + { + retval = gtk_search_bar_handle_event (GTK_SEARCH_BAR (priv->query_editor), + (GdkEvent *) event); + } + + if (retval) + { + nautilus_window_slot_set_search_visible (self, TRUE); + } } return retval; } +/* static gboolean +configure_event_cb (GtkWidget *widget, + GdkEventConfigure *event, + NautilusWindowSlot *slot) +{ + isearch_hide (slot, NULL); + return FALSE; +} */ + static void real_active (NautilusWindowSlot *self) { @@ -612,10 +780,19 @@ static void real_inactive (NautilusWindowSlot *self) { NautilusWindow *window; + NautilusWindowSlotPrivate *priv; + priv = nautilus_window_slot_get_instance_private (self); window = nautilus_window_slot_get_window (self); g_assert (self == nautilus_window_get_active_slot (window)); + isearch_hide (self, NULL); + if (priv->isearch_configure_event_id != 0) { + g_signal_handler_disconnect (GTK_WIDGET (priv->window), + priv->isearch_configure_event_id); + priv->isearch_configure_event_id = 0; + } + gtk_widget_insert_action_group (GTK_WIDGET (window), "slot", NULL); } @@ -908,9 +1085,30 @@ nautilus_window_slot_init (NautilusWindowSlot *self) nautilus_application_set_accelerator (app, "slot.files-view-mode(uint32 0)", "2"); nautilus_application_set_accelerator (app, "slot.search-visible", "f"); + priv->isearch_enable = g_settings_get_boolean (nautilus_preferences, + NAUTILUS_PREFERENCES_ENABLE_INTERACTIVE_SEARCH); + + g_signal_connect_swapped (nautilus_preferences, + "changed::" NAUTILUS_PREFERENCES_ENABLE_INTERACTIVE_SEARCH, + G_CALLBACK (isearch_enable_changed), + self); + priv->view_mode_before_search = NAUTILUS_VIEW_INVALID_ID; } +static void +nautilus_window_slot_finalize (GObject *object) +{ + NautilusWindowSlot *slot; + slot = NAUTILUS_WINDOW_SLOT (object); + + g_signal_handlers_disconnect_by_func (nautilus_preferences, + isearch_enable_changed, + slot); + + G_OBJECT_CLASS (nautilus_window_slot_parent_class)->finalize (object); +} + #define DEBUG_FLAG NAUTILUS_DEBUG_WINDOW #include "nautilus-debug.h" @@ -2687,6 +2885,7 @@ nautilus_window_slot_dispose (GObject *object) self = NAUTILUS_WINDOW_SLOT (object); priv = nautilus_window_slot_get_instance_private (self); + isearch_dispose (self); nautilus_window_slot_clear_forward_list (self); nautilus_window_slot_clear_back_list (self); @@ -2777,6 +2976,7 @@ nautilus_window_slot_class_init (NautilusWindowSlotClass *klass) oclass->constructed = nautilus_window_slot_constructed; oclass->set_property = nautilus_window_slot_set_property; oclass->get_property = nautilus_window_slot_get_property; + oclass->finalize = nautilus_window_slot_finalize; widget_class->grab_focus = nautilus_window_slot_grab_focus; @@ -3198,3 +3398,792 @@ nautilus_window_slot_get_loading (NautilusWindowSlot *self) return priv->loading; } + +/* Interactive search */ +static void +isearch_ensure (NautilusWindowSlot *slot) +{ + GtkWidget *frame; + GtkWidget *vbox; + GtkWidget *toplevel; + GdkScreen *screen; + NautilusWindowSlotPrivate *priv; + + priv = nautilus_window_slot_get_instance_private (slot); + + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (slot)); + screen = gtk_widget_get_screen (GTK_WIDGET (slot)); + + if (priv->isearch_window != NULL) + { + if (gtk_window_has_group (GTK_WINDOW (toplevel))) + gtk_window_group_add_window (gtk_window_get_group (GTK_WINDOW (toplevel)), + GTK_WINDOW (priv->isearch_window)); + else if (gtk_window_has_group (GTK_WINDOW (priv->isearch_window))) + gtk_window_group_remove_window (gtk_window_get_group ( + GTK_WINDOW (priv->isearch_window)), + GTK_WINDOW (priv->isearch_window)); + + gtk_window_set_screen (GTK_WINDOW (priv->isearch_window), screen); + return; + } + + priv->isearch_window = gtk_window_new (GTK_WINDOW_POPUP); + gtk_window_set_screen (GTK_WINDOW (priv->isearch_window), screen); + + if (gtk_window_has_group (GTK_WINDOW (toplevel))) + gtk_window_group_add_window (gtk_window_get_group (GTK_WINDOW (toplevel)), + GTK_WINDOW (priv->isearch_window)); + + gtk_window_set_type_hint (GTK_WINDOW (priv->isearch_window), + GDK_WINDOW_TYPE_HINT_UTILITY); + gtk_window_set_modal (GTK_WINDOW (priv->isearch_window), TRUE); + g_signal_connect (priv->isearch_window, "delete-event", + G_CALLBACK (isearch_window_delete_event), slot); + g_signal_connect (priv->isearch_window, "key-press-event", + G_CALLBACK (isearch_window_key_press_event), slot); + g_signal_connect (priv->isearch_window, "button-press-event", + G_CALLBACK (isearch_window_button_press_event), slot); + g_signal_connect (priv->isearch_window, "scroll-event", + G_CALLBACK (isearch_window_scroll_event), slot); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN); + gtk_widget_show (frame); + gtk_container_add (GTK_CONTAINER (priv->isearch_window), frame); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_widget_show (vbox); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 3); + + /* add entry */ + priv->isearch_entry = gtk_entry_new (); + gtk_widget_show (priv->isearch_entry); + g_signal_connect (priv->isearch_entry, "populate-popup", + G_CALLBACK (isearch_disable_hide), slot); + g_signal_connect (priv->isearch_entry, "activate", + G_CALLBACK (isearch_activate_event), slot); + + g_signal_connect (G_OBJECT (priv->isearch_entry), "preedit-changed", + G_CALLBACK (isearch_preedit_changed), slot); + gtk_container_add (GTK_CONTAINER (vbox), priv->isearch_entry); + + gtk_widget_realize (priv->isearch_entry); +} + +static gboolean +isearch_timeout (gpointer user_data) +{ + NautilusWindowSlot *slot = NAUTILUS_WINDOW_SLOT (user_data); + + if (!g_source_is_destroyed (g_main_current_source ())) + isearch_hide (slot, NULL); + + return FALSE; +} + +static void +isearch_timeout_destroy (gpointer user_data) +{ + NautilusWindowSlot *slot = NAUTILUS_WINDOW_SLOT (user_data); + NautilusWindowSlotPrivate *priv; + + priv = nautilus_window_slot_get_instance_private (slot); + priv->isearch_timeout_id = 0; +} + +static void +isearch_timeout_restart (NautilusWindowSlot *slot) +{ + NautilusWindowSlotPrivate *priv; + + priv = nautilus_window_slot_get_instance_private (slot); + if (priv->isearch_timeout_id != 0) + { + g_source_remove (priv->isearch_timeout_id); + + priv->isearch_timeout_id = + gdk_threads_add_timeout_full (G_PRIORITY_LOW, ISEARCH_TIMEOUT, + isearch_timeout, slot, + isearch_timeout_destroy); + } +} + +static gboolean +isearch_window_delete_event (GtkWidget *widget, + GdkEventAny *event, + NautilusWindowSlot *slot) +{ + g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); + + isearch_hide (slot, NULL); + return TRUE; +} + +static gboolean +isearch_window_button_press_event (GtkWidget *widget, + GdkEventButton *event, + NautilusWindowSlot *slot) +{ + GdkDevice *keyb_device; + NautilusWindowSlotPrivate *priv; + + priv = nautilus_window_slot_get_instance_private (slot); + + g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); + + keyb_device = gdk_device_get_associated_device (event->device); + isearch_hide (slot, keyb_device); + + /* A bit of hackery here */ + if (NAUTILUS_IS_CANVAS_VIEW (priv->content_view)) + { + NautilusCanvasContainer *cc = nautilus_canvas_view_get_canvas_container ( + NAUTILUS_CANVAS_VIEW (priv->content_view)); + gboolean retval; + + if (event->window == gtk_layout_get_bin_window (GTK_LAYOUT (cc))) + g_signal_emit_by_name (GTK_WIDGET (cc), "button-press-event", event, + &retval); + + return retval; + } + else if (NAUTILUS_IS_LIST_VIEW (priv->content_view)) + { + gboolean retval; + // NautilusListView *lv = NAUTILUS_LIST_VIEW (priv->content_view); + GtkTreeView *tv = + nautilus_list_view_get_tree_view (NAUTILUS_LIST_VIEW (priv->content_view)); + + if (event->window == gtk_tree_view_get_bin_window (tv)) + g_signal_emit_by_name (GTK_WIDGET (tv), + "button-press-event", + event, + &retval); + + return retval; + } + + return TRUE; +} + +static gboolean +isearch_window_scroll_event (GtkWidget *widget, + GdkEventScroll *event, + NautilusWindowSlot *slot) +{ + gboolean retval = FALSE; + + if (event->direction == GDK_SCROLL_UP) + { + isearch_move_prev (slot); + retval = TRUE; + } + else if (event->direction == GDK_SCROLL_DOWN) + { + isearch_move_next (slot); + retval = TRUE; + } + + if (retval) + isearch_timeout_restart (slot); + + return retval; +} + +static void +isearch_activate_items_alternate (NautilusWindowSlot *slot) +{ + GList *file_list; + NautilusWindowSlotPrivate *priv; + + priv = nautilus_window_slot_get_instance_private (slot); + + file_list = nautilus_view_get_selection (priv->content_view); + nautilus_files_view_activate_files (NAUTILUS_FILES_VIEW (priv->content_view), + file_list, + NAUTILUS_WINDOW_OPEN_FLAG_NEW_TAB, TRUE); + nautilus_file_list_free (file_list); +} + +static gboolean +isearch_window_key_press_event (GtkWidget *widget, + GdkEventKey *event, + NautilusWindowSlot *slot) +{ + GdkModifierType default_accel; + gboolean retval = FALSE; + + g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); + g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (slot), FALSE); + + /* close window and cancel the search */ + if (event->keyval == GDK_KEY_Escape || event->keyval == GDK_KEY_Tab || + event->keyval == GDK_KEY_KP_Tab || event->keyval == GDK_KEY_ISO_Left_Tab) + { + + isearch_hide (slot, gdk_event_get_device ((GdkEvent *) event)); + return TRUE; + } + + default_accel = + gtk_widget_get_modifier_mask (widget, + GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR); + + /* select previous matching iter */ + if (event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_KP_Up) + { + if (!isearch_move_prev (slot)) + gtk_widget_error_bell (widget); + + retval = TRUE; + } + if (((event->state & (default_accel | GDK_SHIFT_MASK)) == + (default_accel | GDK_SHIFT_MASK)) && + (event->keyval == GDK_KEY_g || event->keyval == GDK_KEY_G)) + { + if (!isearch_move_prev (slot)) + gtk_widget_error_bell (widget); + + retval = TRUE; + } + /* select next matching iter */ + if (event->keyval == GDK_KEY_Down || event->keyval == GDK_KEY_KP_Down) + { + if (!isearch_move_next (slot)) + gtk_widget_error_bell (widget); + + retval = TRUE; + } + if (((event->state & (default_accel | GDK_SHIFT_MASK)) == default_accel) && + (event->keyval == GDK_KEY_g || event->keyval == GDK_KEY_G)) + { + if (!isearch_move_next (slot)) + gtk_widget_error_bell (widget); + + retval = TRUE; + } + + /* Alternate activation (ShiftEnter). + * Regular activation (Enter) is handled by the entry activate signal. + */ + if ((event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) && + (event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) + { + isearch_activate_items_alternate (slot); + isearch_hide (slot, gdk_event_get_device ((GdkEvent *) event)); + retval = TRUE; + } + isearch_timeout_restart (slot); + return retval; +} + +static void +isearch_disable_hide (GtkEntry *entry, + GtkMenu *menu, + gpointer data) +{ + NautilusWindowSlot *slot = NAUTILUS_WINDOW_SLOT (data); + NautilusWindowSlotPrivate *priv; + + priv = nautilus_window_slot_get_instance_private (slot); + + priv->isearch_disable_hide = 1; + g_signal_connect (menu, "hide", G_CALLBACK (isearch_enable_hide), data); +} + +static void +isearch_preedit_changed (GtkEntry *entry, + gchar *preedit, + NautilusWindowSlot *slot) +{ + NautilusWindowSlotPrivate *priv; + + priv = nautilus_window_slot_get_instance_private (slot); + priv->isearch_imcontext_changed = 1; + isearch_timeout_restart (slot); +} + +static void +isearch_activate_event (GtkEntry *entry, + NautilusWindowSlot *slot) +{ + // GtkTreePath *path; + NautilusWindowSlotPrivate *priv; + + priv = nautilus_window_slot_get_instance_private (slot); + + isearch_hide (slot, gtk_get_current_event_device ()); + nautilus_files_view_activate_selection (NAUTILUS_FILES_VIEW (priv->content_view)); +} + +static gboolean +isearch_enable_hide_real (gpointer data) +{ + NautilusWindowSlot *slot = NAUTILUS_WINDOW_SLOT (data); + NautilusWindowSlotPrivate *priv; + + priv = nautilus_window_slot_get_instance_private (slot); + priv->isearch_disable_hide = 0; + return FALSE; +} + +static void +isearch_enable_hide (GtkWidget *widget, + gpointer data) +{ + gdk_threads_add_timeout_full (G_PRIORITY_HIGH, 200, + isearch_enable_hide_real, + g_object_ref (data), + g_object_unref); +} + +static void +send_focus_change (GtkWidget *widget, + GdkDevice *device, + gboolean in) +{ + GdkDeviceManager *device_manager; + GList *devices; + GList *d; + + device_manager = gdk_display_get_device_manager (gtk_widget_get_display (widget)); + devices = gdk_device_manager_list_devices (device_manager, GDK_DEVICE_TYPE_MASTER); + devices = g_list_concat (devices, + gdk_device_manager_list_devices (device_manager, + GDK_DEVICE_TYPE_SLAVE)); + devices = g_list_concat (devices, + gdk_device_manager_list_devices (device_manager, + GDK_DEVICE_TYPE_FLOATING)); + + for (d = devices; d; d = d->next) + { + GdkDevice *dev = d->data; + GdkEvent *fevent; + GdkWindow *window; + + if (gdk_device_get_source (dev) != GDK_SOURCE_KEYBOARD) + continue; + + window = gtk_widget_get_window (widget); + + /* Skip non-master keyboards that haven't + * selected for events from this window + */ + if (gdk_device_get_device_type (dev) != GDK_DEVICE_TYPE_MASTER && + !gdk_window_get_device_events (window, dev)) + continue; + + fevent = gdk_event_new (GDK_FOCUS_CHANGE); + + fevent->focus_change.type = GDK_FOCUS_CHANGE; + fevent->focus_change.window = g_object_ref (window); + fevent->focus_change.in = in; + gdk_event_set_device (fevent, device); + + gtk_widget_send_focus_change (widget, fevent); + + gdk_event_free (fevent); + } + + g_list_free (devices); +} + +static void +isearch_hide (NautilusWindowSlot *slot, + GdkDevice *device) +{ + NautilusWindowSlotPrivate *priv; + + priv = nautilus_window_slot_get_instance_private (slot); + + if (priv->isearch_disable_hide) + return; + + if (!priv->isearch_enable) + return; + + if (priv->isearch_entry_changed_id) + { + g_signal_handler_disconnect (priv->isearch_entry, + priv->isearch_entry_changed_id); + priv->isearch_entry_changed_id = 0; + } + if (priv->isearch_timeout_id) + { + g_source_remove (priv->isearch_timeout_id); + priv->isearch_timeout_id = 0; + } + if (priv->isearch_window != NULL && + gtk_widget_get_visible (priv->isearch_window)) + { + /* send focus-in event */ + send_focus_change (GTK_WIDGET (priv->isearch_entry), device, FALSE); + gtk_widget_hide (priv->isearch_window); + gtk_entry_set_text (GTK_ENTRY (priv->isearch_entry), ""); + send_focus_change (GTK_WIDGET (slot), device, TRUE); + } +} + +static void +isearch_entry_changed (GtkWidget *entry, + NautilusWindowSlot *slot) +{ + // gint ret; + const gchar *text; + NautilusWindowSlotPrivate *priv; + + priv = nautilus_window_slot_get_instance_private (slot); + + g_return_if_fail (GTK_IS_ENTRY (entry)); + g_return_if_fail (NAUTILUS_IS_WINDOW_SLOT (slot)); + + text = gtk_entry_get_text (GTK_ENTRY (entry)); + + /* unselect all */ + nautilus_view_set_selection (priv->content_view, NULL); + + isearch_timeout_restart (slot); + + if (*text == '\0') + return; + + isearch_set_selection (slot, isearch_find (slot, text)); +} + +static gboolean +isearch_start (NautilusWindowSlot *slot, + GdkDevice *device) +{ + // GList * list; + // gboolean found_focus = FALSE; + GTypeClass *klass; + NautilusWindowSlotPrivate *priv; + + priv = nautilus_window_slot_get_instance_private (slot); + + if (!priv->isearch_enable) + return FALSE; + + if (priv->isearch_window != NULL && + gtk_widget_get_visible (priv->isearch_window)) + return TRUE; + + if (nautilus_files_view_get_loading (NAUTILUS_FILES_VIEW (priv->content_view))) + return FALSE; + + isearch_ensure (slot); + + /* done, show it */ + isearch_position (slot); + gtk_widget_show (priv->isearch_window); + + if (priv->isearch_entry_changed_id == 0) + { + priv->isearch_entry_changed_id = + g_signal_connect (priv->isearch_entry, "changed", + G_CALLBACK (isearch_entry_changed), slot); + } + + priv->isearch_timeout_id = + gdk_threads_add_timeout_full (G_PRIORITY_LOW, ISEARCH_TIMEOUT, + isearch_timeout, slot, + isearch_timeout_destroy); + + /* Grab focus without selecting all the text. */ + klass = g_type_class_peek_parent (GTK_ENTRY_GET_CLASS (priv->isearch_entry)); + (*GTK_WIDGET_CLASS (klass)->grab_focus) (priv->isearch_entry); + + /* send focus-in event */ + send_focus_change (priv->isearch_entry, device, TRUE); + + /* search first matching iter */ + isearch_entry_changed (priv->isearch_entry, slot); + return TRUE; +} + +static void +isearch_position (NautilusWindowSlot *slot) +{ + gint x, y; + gint window_x, window_y; + gint window_width, window_height; + GdkWindow *window = gtk_widget_get_window (GTK_WIDGET (slot)); + GdkScreen *screen = gdk_window_get_screen (window); + GtkRequisition requisition; + gint monitor_num; + GdkRectangle monitor; + NautilusWindowSlotPrivate *priv; + + priv = nautilus_window_slot_get_instance_private (slot); + + monitor_num = gdk_screen_get_monitor_at_window (screen, window); + gdk_screen_get_monitor_workarea (screen, monitor_num, &monitor); + + gtk_widget_realize (priv->isearch_window); + + gdk_window_get_origin (window, &window_x, &window_y); + window_width = gdk_window_get_width (window); + window_height = gdk_window_get_height (window); + gtk_widget_get_preferred_size (priv->isearch_window, &requisition, NULL); + + if (window_x + window_width > gdk_screen_get_width (screen)) + x = gdk_screen_get_width (screen) - requisition.width; + else if (window_x + window_width - requisition.width < 0) + x = 0; + else + x = window_x + window_width - requisition.width; + + if (window_y + window_height + requisition.height > + gdk_screen_get_height (screen)) + y = gdk_screen_get_height (screen) - requisition.height; + else if (window_y + window_height < 0) /* isn't really possible ... */ + y = 0; + else + y = window_y + window_height; + + gtk_window_move (GTK_WINDOW (priv->isearch_window), x, y); +} + +static gboolean +isearch_compare_filename (const gchar *f1, + const gchar *f2, + guint length) +{ + gchar *normalized_f1; + gchar *normalized_f2; + gchar *case_normalized_f1 = NULL; + gchar *case_normalized_f2 = NULL; + gboolean retval = FALSE; + + normalized_f1 = g_utf8_normalize (f1, -1, G_NORMALIZE_ALL); + normalized_f2 = g_utf8_normalize (f2, -1, G_NORMALIZE_ALL); + + if (G_LIKELY (normalized_f1 != NULL && normalized_f2 != NULL)) + { + case_normalized_f1 = g_utf8_casefold (normalized_f1, -1); + case_normalized_f2 = g_utf8_casefold (normalized_f2, -1); + + retval = (strncmp (case_normalized_f1, case_normalized_f2, length) == 0); + } + + g_free (normalized_f1); + g_free (normalized_f2); + g_free (case_normalized_f1); + g_free (case_normalized_f2); + return retval; +} + +static int +compare_files (gconstpointer a, + gconstpointer b, + gpointer callback_data) +{ + NautilusFilesView *view = NAUTILUS_FILES_VIEW (callback_data); + NautilusFile *f1 = NAUTILUS_FILE (a); + NautilusFile *f2 = NAUTILUS_FILE (b); + + return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->compare_files (view, f1, f2); +} + +static GList * +isearch_get_sorted_files (NautilusWindowSlot *slot) +{ + NautilusWindowSlotPrivate *priv = nautilus_window_slot_get_instance_private (slot); + NautilusView *view = priv->content_view; + NautilusDirectory *dir = nautilus_files_view_get_model (NAUTILUS_FILES_VIEW (view)); + GList *list = nautilus_directory_get_file_list (dir); + GList *sorted_list; + + sorted_list = g_list_sort_with_data (list, compare_files, view); + return sorted_list; +} + +static NautilusFile * +isearch_find (NautilusWindowSlot *slot, + const gchar *text) +{ + GList *files = isearch_get_sorted_files (slot); + GList *l; + NautilusFile *found = NULL; + + for (l = files; l; l = g_list_next (l)) + { + NautilusFile *file = NAUTILUS_FILE (l->data); + gchar *filename = nautilus_file_get_display_name (file); + + if (isearch_compare_filename (filename, text, strlen (text))) + found = file; + + g_free (filename); + + if (found) + break; + } + + return found; +} + +static NautilusFile * +isearch_find_next (NautilusWindowSlot *slot, + const gchar *text) +{ + GList *files = isearch_get_sorted_files (slot); + NautilusFile *found = NULL; + GList *current; + GList *l; + NautilusWindowSlotPrivate *priv; + + priv = nautilus_window_slot_get_instance_private (slot); + + current = g_list_find (files, priv->isearch_selected_file); + for (l = g_list_next (current); l; l = g_list_next (l)) + { + NautilusFile *file = NAUTILUS_FILE (l->data); + gchar *display_name = nautilus_file_get_display_name (file); + + if (isearch_compare_filename (display_name, text, strlen (text))) + found = file; + + g_free (display_name); + + if (found) + break; + } + + return found; +} + +static NautilusFile * +isearch_find_prev (NautilusWindowSlot *slot, + const gchar *text) +{ + GList *files = isearch_get_sorted_files (slot); + NautilusFile *found = NULL; + GList *current; + GList *l; + NautilusWindowSlotPrivate *priv; + + priv = nautilus_window_slot_get_instance_private (slot); + + current = g_list_find (files, priv->isearch_selected_file); + for (l = g_list_previous (current); l; l = g_list_previous (l)) + { + NautilusFile *file = NAUTILUS_FILE (l->data); + gchar *display_name = nautilus_file_get_display_name (file); + + if (isearch_compare_filename (display_name, text, strlen (text))) + found = file; + + g_free (display_name); + + if (found) + break; + } + + return found; +} + +static gboolean +isearch_move_next (NautilusWindowSlot *slot) +{ + const gchar *text; + NautilusFile *iter; + NautilusWindowSlotPrivate *priv; + + priv = nautilus_window_slot_get_instance_private (slot); + + text = gtk_entry_get_text (GTK_ENTRY (priv->isearch_entry)); + iter = isearch_find_next (slot, text); + + if (iter) + isearch_set_selection (slot, iter); + + return iter != NULL; +} + +static gboolean +isearch_move_prev (NautilusWindowSlot *slot) +{ + const gchar *text; + NautilusFile *iter; + NautilusWindowSlotPrivate *priv; + + priv = nautilus_window_slot_get_instance_private (slot); + + text = gtk_entry_get_text (GTK_ENTRY (priv->isearch_entry)); + iter = isearch_find_prev (slot, text); + + if (iter) + isearch_set_selection (slot, iter); + + return iter != NULL; +} + +static void +isearch_set_selection (NautilusWindowSlot *slot, + NautilusFile *file) +{ + GList *list = NULL; + NautilusWindowSlotPrivate *priv; + + list = g_list_append (list, file); + priv = nautilus_window_slot_get_instance_private (slot); + + priv->isearch_selected_file = file; + nautilus_view_set_selection (priv->content_view, list); + g_list_free (list); +} + +static void +isearch_enable_changed (gpointer callback_data) +{ + NautilusWindowSlot *slot; + gboolean enable; + NautilusWindowSlotPrivate *priv; + + slot = NAUTILUS_WINDOW_SLOT (callback_data); + priv = nautilus_window_slot_get_instance_private (slot); + + enable = + g_settings_get_boolean (nautilus_preferences, + NAUTILUS_PREFERENCES_ENABLE_INTERACTIVE_SEARCH); + + if (enable != priv->isearch_enable) + { + if (!enable) + isearch_dispose (slot); + + priv->isearch_enable = enable; + } +} + +static void +isearch_dispose (NautilusWindowSlot *slot) +{ + NautilusWindowSlotPrivate *priv; + + priv = nautilus_window_slot_get_instance_private (slot); + + if (!priv->isearch_enable) + return; + + if (priv->isearch_entry_changed_id != 0) + { + g_signal_handler_disconnect (G_OBJECT (priv->isearch_entry), + priv->isearch_entry_changed_id); + priv->isearch_entry_changed_id = 0; + } + if (priv->isearch_timeout_id != 0) + { + g_source_remove (priv->isearch_timeout_id); + priv->isearch_timeout_id = 0; + } + if (priv->isearch_window != NULL) + { + gtk_widget_destroy (priv->isearch_window); + priv->isearch_window = NULL; + priv->isearch_entry = NULL; + } +} diff --git a/src/resources/ui/nautilus-preferences-window.ui b/src/resources/ui/nautilus-preferences-window.ui index 96a2be860..b1cb7c3d0 100644 --- a/src/resources/ui/nautilus-preferences-window.ui +++ b/src/resources/ui/nautilus-preferences-window.ui @@ -802,6 +802,56 @@ More information will appear when zooming closer. 4 + + + + + True + False + vertical + 6 + + + True + False + Interactive search (typeahead) + 0 + + + + + + False + False + 0 + + + + + Enable interactive search + False + True + True + False + True + 0 + True + + + False + False + 2 + + + + + False + True + 5 + + + + 1