[PATCH] Port GtkTreeView-style typeahead to NautilusIconContainer
- From: Jürg Billeter <j bitron ch>
- To: nautilus-list gnome org
- Subject: [PATCH] Port GtkTreeView-style typeahead to NautilusIconContainer
- Date: Mon, 03 Oct 2005 16:09:39 +0200
Attached patch replaces the previous typeahead implementation of
NautilusIconContainer by a port of the implementation of GtkTreeView. It
is heavily based on that code, just adapted to fit in nautilus. This
adds the visible text search entry and the arrow keys / mouse wheel will
work as in GtkTreeView.
This might cause usability issues as the arrow keys behave differently
when in typeahead mode. Besides that, I don't know whether this
behaviour is ok when used on the desktop.
This patch is intended to be applied in line with
http://mail.gnome.org/archives/nautilus-list/2005-April/msg00226.html
Any comments?
Jürg
--
Jürg Billeter <j bitron ch>
? depcomp
? nautilus-folder-handler.desktop
? stamp-h1
Index: ChangeLog
===================================================================
RCS file: /cvs/gnome/nautilus/ChangeLog,v
retrieving revision 1.6667
diff -p -u -r1.6667 ChangeLog
--- ChangeLog 3 Oct 2005 13:44:46 -0000 1.6667
+++ ChangeLog 3 Oct 2005 14:08:24 -0000
@@ -1,3 +1,33 @@
+2005-10-03 Jürg Billeter <j bitron ch>
+
+ * libnautilus-private/nautilus-icon-container.c: (destroy),
+ (unrealize), (button_press_event),
+ (nautilus_icon_container_search_position_func),
+ (nautilus_icon_container_real_search_enable_popdown),
+ (nautilus_icon_container_search_enable_popdown),
+ (nautilus_icon_container_search_disable_popdown),
+ (send_focus_change), (nautilus_icon_container_search_dialog_hide),
+ (nautilus_icon_container_search_entry_flush_timeout),
+ (nautilus_icon_container_search_preedit_changed),
+ (nautilus_icon_container_search_activate),
+ (nautilus_icon_container_search_delete_event),
+ (nautilus_icon_container_search_button_press_event),
+ (nautilus_icon_container_search_iter),
+ (nautilus_icon_container_search_move),
+ (nautilus_icon_container_search_scroll_event),
+ (nautilus_icon_container_search_key_press_event),
+ (nautilus_icon_container_search_init),
+ (nautilus_icon_container_ensure_interactive_directory),
+ (nautilus_icon_container_real_start_interactive_search),
+ (nautilus_icon_container_start_interactive_search),
+ (key_press_event), (nautilus_icon_container_class_init):
+ * libnautilus-private/nautilus-icon-container.h:
+ * libnautilus-private/nautilus-icon-private.h:
+ * libnautilus-private/nautilus-marshal.list:
+
+ Replace NautilusIconContainer typeahead implementation by a port of
+ GtkTreeView's interactive search.
+
2005-10-03 Alexander Larsson <alexl redhat com>
* libnautilus-extension/nautilus-file-info.c:
Index: libnautilus-private/nautilus-icon-container.c
===================================================================
RCS file: /cvs/gnome/nautilus/libnautilus-private/nautilus-icon-container.c,v
retrieving revision 1.399
diff -p -u -r1.399 nautilus-icon-container.c
--- libnautilus-private/nautilus-icon-container.c 3 Oct 2005 08:02:28 -0000 1.399
+++ libnautilus-private/nautilus-icon-container.c 3 Oct 2005 14:08:28 -0000
@@ -133,6 +133,9 @@
#define SNAP_CEIL_HORIZONTAL(x) SNAP_HORIZONTAL (ceil, x)
#define SNAP_CEIL_VERTICAL(y) SNAP_VERTICAL (ceil, y)
+/* Copied from NautilusIconContainer */
+#define NAUTILUS_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT 5000
+
enum {
ACTION_ACTIVATE,
ACTION_MENU,
@@ -237,6 +240,7 @@ enum {
ICON_ADDED,
ICON_REMOVED,
CLEARED,
+ START_INTERACTIVE_SEARCH,
LAST_SIGNAL
};
@@ -2862,95 +2866,6 @@ typedef struct {
int last_match_length;
} BestNameMatch;
-static gboolean
-match_best_name (NautilusIconContainer *container,
- NautilusIcon *start_icon,
- NautilusIcon *best_so_far,
- NautilusIcon *candidate,
- void *data)
-{
- BestNameMatch *match_state;
- const char *name;
- int match_length;
- gunichar unichar;
-
- match_state = (BestNameMatch *) data;
-
- name = nautilus_icon_canvas_item_get_editable_text (candidate->item);
-
- /* This can happen if a key event is handled really early while loading
- * the icon container, before the items have all been updated once.
- */
- if (name == NULL) {
- return FALSE;
- }
-
- for (match_length = 0; ; match_length++) {
- if (*name == 0 ||
- match_state->name[match_length] == 0) {
- break;
- }
-
- unichar = g_utf8_get_char (name);
- if (g_unichar_tolower (unichar) != match_state->name[match_length]) {
- break;
- }
- name = g_utf8_next_char (name);
- }
-
- if (match_length > match_state->last_match_length) {
- /* This is the longest pattern match sofar, remember the
- * length and return with a candidate.
- */
- match_state->last_match_length = match_length;
- return TRUE;
- }
-
- return FALSE;
-}
-
-static gboolean
-select_matching_name (NautilusIconContainer *container,
- const char *match_name)
-{
- int i;
- NautilusIcon *icon;
- BestNameMatch match_state;
-
- match_state.name = g_new (gunichar, g_utf8_strlen (match_name, -1) + 1);
- match_state.last_match_length = 0;
-
- i = 0;
- while (*match_name != 0) {
- match_state.name[i++] = g_unichar_tolower (g_utf8_get_char (match_name));
- match_name = g_utf8_next_char (match_name);
- }
- match_state.name[i++] = 0;
-
- icon = find_best_icon (container,
- NULL,
- match_best_name,
- &match_state);
- if (icon == NULL) {
- g_free (match_state.name);
- return FALSE;
- }
-
- /* Select icons and get rid of the special keyboard focus. */
- clear_keyboard_focus (container);
- clear_keyboard_rubberband_start (container);
- container->details->range_selection_base_icon = icon;
- if (select_one_unselect_others (container, icon)) {
- g_signal_emit (container,
- signals[SELECTION_CHANGED], 0);
- }
- schedule_keyboard_icon_reveal (container, icon);
-
- g_free (match_state.name);
-
- return TRUE;
-}
-
#ifndef TAB_NAVIGATION_DISABLED
static void
select_previous_or_next_icon (NautilusIconContainer *container,
@@ -3025,8 +2940,18 @@ destroy (GtkObject *object)
container->details->align_idle_id = 0;
}
-
- nautilus_icon_container_flush_typeselect_state (container);
+
+ /* destroy interactive search dialog */
+ if (container->details->search_window) {
+ gtk_widget_destroy (container->details->search_window);
+ container->details->search_window = NULL;
+ container->details->search_entry = NULL;
+ if (container->details->typeselect_flush_timeout) {
+ g_source_remove (container->details->typeselect_flush_timeout);
+ container->details->typeselect_flush_timeout = 0;
+ }
+ }
+
GTK_OBJECT_CLASS (parent_class)->destroy (object);
}
@@ -3153,6 +3078,11 @@ unrealize (GtkWidget *widget)
nautilus_icon_dnd_fini (container);
+ if (container->details->typeselect_flush_timeout) {
+ g_source_remove (container->details->typeselect_flush_timeout);
+ container->details->typeselect_flush_timeout = 0;
+ }
+
GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
}
@@ -3200,9 +3130,6 @@ button_press_event (GtkWidget *widget,
/* Forget about where we began with the arrow keys now that we're mousing. */
container->details->arrow_key_axis = AXIS_NONE;
- /* Forget the typeahead state. */
- nautilus_icon_container_flush_typeselect_state (container);
-
/* Invoke the canvas event handler and see if an item picks up the event. */
clicked_on_icon = GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, event);
@@ -3628,138 +3555,522 @@ motion_notify_event (GtkWidget *widget,
return GTK_WIDGET_CLASS (parent_class)->motion_notify_event (widget, event);
}
-void
-nautilus_icon_container_flush_typeselect_state (NautilusIconContainer *container)
+static void
+nautilus_icon_container_search_position_func (NautilusIconContainer *container,
+ GtkWidget *search_dialog)
{
- if (container->details->type_select_state == NULL) {
- return;
+ gint x, y;
+ gint cont_x, cont_y;
+ gint cont_width, cont_height;
+ GdkWindow *cont_window = GTK_WIDGET (container)->window;
+ GdkScreen *screen = gdk_drawable_get_screen (cont_window);
+ GtkRequisition requisition;
+ gint monitor_num;
+ GdkRectangle monitor;
+
+ monitor_num = gdk_screen_get_monitor_at_window (screen, cont_window);
+ gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
+
+ gtk_widget_realize (search_dialog);
+
+ gdk_window_get_origin (cont_window, &cont_x, &cont_y);
+ gdk_drawable_get_size (cont_window, &cont_width, &cont_height);
+ gtk_widget_size_request (search_dialog, &requisition);
+
+ if (cont_x + cont_width - requisition.width > gdk_screen_get_width (screen)) {
+ x = gdk_screen_get_width (screen) - requisition.width;
+ } else if (cont_x + cont_width - requisition.width < 0) {
+ x = 0;
+ } else {
+ x = cont_x + cont_width - requisition.width;
}
-
- g_free (container->details->type_select_state->type_select_pattern);
- g_free (container->details->type_select_state);
- container->details->type_select_state = NULL;
+
+ if (cont_y + cont_height > gdk_screen_get_height (screen)) {
+ y = gdk_screen_get_height (screen) - requisition.height;
+ } else if (cont_y + cont_height < 0) { /* isn't really possible ... */
+ y = 0;
+ } else {
+ y = cont_y + cont_height;
+ }
+
+ gtk_window_move (GTK_WINDOW (search_dialog), x, y);
+}
+
+static gboolean
+nautilus_icon_container_real_search_enable_popdown (gpointer data)
+{
+ NautilusIconContainer *container = (NautilusIconContainer *)data;
+
+ container->details->disable_popdown = 0;
+
+ return FALSE;
}
static void
-dave_read_cb (GnomeVFSResult result,
- GnomeVFSFileSize file_size,
- char *file_contents,
- gpointer user_data)
+nautilus_icon_container_search_enable_popdown (GtkWidget *widget,
+ gpointer data)
{
- GtkWidget *dialog;
- GtkWidget *hbox;
- GtkWidget *image;
- GtkWidget *label;
- GdkPixbuf *pixbuf;
- GdkPixbufLoader *loader;
+ g_timeout_add (200, nautilus_icon_container_real_search_enable_popdown, data);
+}
- g_return_if_fail (result == GNOME_VFS_OK);
+static void
+nautilus_icon_container_search_disable_popdown (GtkEntry *entry,
+ GtkMenu *menu,
+ gpointer data)
+{
+ NautilusIconContainer *container = (NautilusIconContainer *) data;
- loader = gdk_pixbuf_loader_new ();
- gdk_pixbuf_loader_write (loader, file_contents, file_size, NULL);
- pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+ container->details->disable_popdown = TRUE;
+ g_signal_connect (menu, "hide",
+ G_CALLBACK (nautilus_icon_container_search_enable_popdown),
+ data);
+}
+/* Cut and paste from gtkwindow.c */
+static void
+send_focus_change (GtkWidget *widget, gboolean in)
+{
+ GdkEvent *fevent = gdk_event_new (GDK_FOCUS_CHANGE);
- g_return_if_fail (pixbuf != NULL);
+ g_object_ref (widget);
- dialog = gtk_dialog_new_with_buttons ("Hello", NULL, 0,
- "_Call Now!",
- GTK_RESPONSE_OK, NULL);
+ if (in) {
+ GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS);
+ } else {
+ GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS);
+ }
- gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
+ fevent->focus_change.type = GDK_FOCUS_CHANGE;
+ fevent->focus_change.window = g_object_ref (widget->window);
+ fevent->focus_change.in = in;
- image = gtk_image_new_from_pixbuf (pixbuf);
- g_object_unref (G_OBJECT (pixbuf));
+ gtk_widget_event (widget, fevent);
- label = g_object_new (GTK_TYPE_LABEL, "label",
- "<span size=\"larger\"><b>My name is Dave Camp."
- " I am very lonely. "
- "<i>Please</i> call me at (617) 216-5250."
- " Thank you.</b></span>",
- "use_markup", TRUE, "wrap", TRUE, NULL);
+ g_object_notify (G_OBJECT (widget), "has-focus");
- hbox = gtk_hbox_new (FALSE, 6);
- gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
- gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
- gtk_widget_show_all (hbox);
-
- gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
- hbox, TRUE, TRUE, 0);
- gtk_dialog_run (GTK_DIALOG (dialog));
- gtk_widget_destroy (dialog);
- gdk_pixbuf_loader_close (loader, NULL);
+ g_object_unref (widget);
+ gdk_event_free (fevent);
}
static void
-begin_dave_bashing (void)
+nautilus_icon_container_search_dialog_hide (GtkWidget *search_dialog,
+ NautilusIconContainer *container)
{
- eel_read_entire_file_async ("http://art.gnome.org/images/icons/gnome-people/Dave.png", 10, dave_read_cb, NULL);
+ if (container->details->disable_popdown) {
+ return;
+ }
+
+ if (container->details->search_entry_changed_id) {
+ g_signal_handler_disconnect (container->details->search_entry,
+ container->details->search_entry_changed_id);
+ container->details->search_entry_changed_id = 0;
+ }
+ if (container->details->typeselect_flush_timeout) {
+ g_source_remove (container->details->typeselect_flush_timeout);
+ container->details->typeselect_flush_timeout = 0;
+ }
+
+ /* send focus-in event */
+ send_focus_change (GTK_WIDGET (container->details->search_entry), FALSE);
+ gtk_widget_hide (search_dialog);
+ gtk_entry_set_text (GTK_ENTRY (container->details->search_entry), "");
}
static gboolean
-handle_typeahead (NautilusIconContainer *container,
- GdkEventKey *event,
- gboolean *flush_typeahead)
-{
- char *new_pattern;
- gint64 now;
- gint64 time_delta;
- guint32 unichar;
- char unichar_utf8[7];
- int i;
+nautilus_icon_container_search_entry_flush_timeout (NautilusIconContainer *container)
+{
+ nautilus_icon_container_search_dialog_hide (container->details->search_window, container);
+ container->details->typeselect_flush_timeout = 0;
+
+ return TRUE;
+}
+
+/* Because we're visible but offscreen, we just set a flag in the preedit
+ * callback.
+ */
+static void
+nautilus_icon_container_search_preedit_changed (GtkIMContext *im_context,
+ NautilusIconContainer *container)
+{
+ container->details->imcontext_changed = 1;
+ if (container->details->typeselect_flush_timeout) {
+ g_source_remove (container->details->typeselect_flush_timeout);
+ container->details->typeselect_flush_timeout =
+ g_timeout_add (NAUTILUS_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT,
+ (GSourceFunc) nautilus_icon_container_search_entry_flush_timeout,
+ container);
+ }
+}
+
+static void
+nautilus_icon_container_search_activate (GtkEntry *entry,
+ NautilusIconContainer *container)
+{
+ nautilus_icon_container_search_dialog_hide (container->details->search_window,
+ container);
+
+ activate_selected_items (container);
+}
+
+static gboolean
+nautilus_icon_container_search_delete_event (GtkWidget *widget,
+ GdkEventAny *event,
+ NautilusIconContainer *container)
+{
+ g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
+
+ nautilus_icon_container_search_dialog_hide (widget, container);
+
+ return TRUE;
+}
- unichar = gdk_keyval_to_unicode (event->keyval);
- i = g_unichar_to_utf8 (unichar, unichar_utf8);
- unichar_utf8[i] = 0;
+static gboolean
+nautilus_icon_container_search_button_press_event (GtkWidget *widget,
+ GdkEventButton *event,
+ NautilusIconContainer *container)
+{
+ g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
+
+ nautilus_icon_container_search_dialog_hide (widget, container);
+
+ if (event->window == GTK_LAYOUT (container)->bin_window) {
+ button_press_event (GTK_WIDGET (container), event);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+nautilus_icon_container_search_iter (NautilusIconContainer *container,
+ const char *key, gint n)
+{
+ GList *p;
+ NautilusIcon *icon;
+ const char *name;
+ int count;
+ char *normalized_key, *case_normalized_key;
+ char *normalized_name, *case_normalized_name;
- *flush_typeahead = FALSE;
+ g_return_val_if_fail (key != NULL, FALSE);
+ g_return_val_if_fail (n >= 1, FALSE);
- if (*event->string == 0) {
- /* can be an empty string if the modifier was held down, etc. */
+ normalized_key = g_utf8_normalize (key, -1, G_NORMALIZE_ALL);
+ if (!normalized_key) {
return FALSE;
}
-
- if (!g_unichar_isprint (unichar)) {
- *flush_typeahead = TRUE;
+ case_normalized_key = g_utf8_casefold (normalized_key, -1);
+ g_free (normalized_key);
+ if (!case_normalized_key) {
return FALSE;
}
+
+ icon = NULL;
+ count = 0;
+ for (p = container->details->icons; p != NULL && count != n; p = p->next) {
+ icon = p->data;
+ name = nautilus_icon_canvas_item_get_editable_text (icon->item);
+
+ /* This can happen if a key event is handled really early while
+ * loading the icon container, before the items have all been
+ * updated once.
+ */
+ if (!name) {
+ continue;
+ }
+
+ normalized_name = g_utf8_normalize (name, -1, G_NORMALIZE_ALL);
+ if (!normalized_name) {
+ continue;
+ }
+ case_normalized_name = g_utf8_casefold (normalized_name, -1);
+ g_free (normalized_name);
+ if (!case_normalized_name) {
+ continue;
+ }
+
+ if (strncmp (case_normalized_key, case_normalized_name,
+ strlen (case_normalized_key)) == 0) {
+ count++;
+ }
+
+ g_free (case_normalized_name);
+ }
+
+ g_free (case_normalized_key);
+
+ if (count == n) {
+ if (select_one_unselect_others (container, icon)) {
+ g_signal_emit (container, signals[SELECTION_CHANGED], 0);
+ }
+ schedule_keyboard_icon_reveal (container, icon);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+nautilus_icon_container_search_move (GtkWidget *window,
+ NautilusIconContainer *container,
+ gboolean up)
+{
+ gboolean ret;
+ gint len;
+ gint count = 0;
+ const gchar *text;
+
+ text = gtk_entry_get_text (GTK_ENTRY (container->details->search_entry));
+
+ g_return_if_fail (text != NULL);
+
+ if (up && container->details->selected_iter == 1) {
+ return;
+ }
+
+ len = strlen (text);
- /* lazily allocate the typeahead state */
- if (container->details->type_select_state == NULL) {
- container->details->type_select_state = g_new0 (TypeSelectState, 1);
- }
-
- /* find out how long since last character was typed */
- now = eel_get_system_time ();
- time_delta = now - container->details->type_select_state->last_typeselect_time;
- if (time_delta < 0 || time_delta > NAUTILUS_ICON_CONTAINER_TYPESELECT_FLUSH_DELAY) {
- /* the typeselect state is too old, start with a fresh one */
- g_free (container->details->type_select_state->type_select_pattern);
- container->details->type_select_state->type_select_pattern = NULL;
- }
-
- if (container->details->type_select_state->type_select_pattern != NULL) {
- new_pattern = g_strconcat
- (container->details->type_select_state->type_select_pattern,
- unichar_utf8, NULL);
- g_free (container->details->type_select_state->type_select_pattern);
+ if (len < 1) {
+ return;
+ }
+
+ /* search */
+ unselect_all (container);
+
+ ret = nautilus_icon_container_search_iter (container, text,
+ up?((container->details->selected_iter) - 1):((container->details->selected_iter + 1)));
+
+ if (ret) {
+ /* found */
+ container->details->selected_iter += up?(-1):(1);
} else {
- new_pattern = g_strdup (unichar_utf8);
+ /* return to old iter */
+ count = 0;
+ nautilus_icon_container_search_iter (container, text,
+ container->details->selected_iter);
+ }
+}
+
+static gboolean
+nautilus_icon_container_search_scroll_event (GtkWidget *widget,
+ GdkEventScroll *event,
+ NautilusIconContainer *container)
+{
+ gboolean retval = FALSE;
+
+ if (event->direction == GDK_SCROLL_UP) {
+ nautilus_icon_container_search_move (widget, container, TRUE);
+ retval = TRUE;
+ } else if (event->direction == GDK_SCROLL_DOWN) {
+ nautilus_icon_container_search_move (widget, container, FALSE);
+ retval = TRUE;
+ }
+
+ return retval;
+}
+
+static gboolean
+nautilus_icon_container_search_key_press_event (GtkWidget *widget,
+ GdkEventKey *event,
+ NautilusIconContainer *container)
+{
+ gboolean retval = FALSE;
+
+ g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
+ g_return_val_if_fail (NAUTILUS_IS_ICON_CONTAINER (container), FALSE);
+
+ /* close window and cancel the search */
+ if (event->keyval == GDK_Escape || event->keyval == GDK_Tab) {
+ nautilus_icon_container_search_dialog_hide (widget, container);
+ return TRUE;
+ }
+
+ /* select previous matching iter */
+ if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up) {
+ nautilus_icon_container_search_move (widget, container, TRUE);
+ retval = TRUE;
+ }
+
+ /* select next matching iter */
+ if (event->keyval == GDK_Down || event->keyval == GDK_KP_Down) {
+ nautilus_icon_container_search_move (widget, container, FALSE);
+ retval = TRUE;
+ }
+
+ if ((event->state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK
+ && (event->keyval == GDK_g || event->keyval == GDK_G)) {
+ nautilus_icon_container_search_move (widget, container, FALSE);
+ retval = TRUE;
+ }
+
+ /* renew the flush timeout */
+ if (retval && container->details->typeselect_flush_timeout) {
+ g_source_remove (container->details->typeselect_flush_timeout);
+ container->details->typeselect_flush_timeout =
+ g_timeout_add (NAUTILUS_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT,
+ (GSourceFunc) nautilus_icon_container_search_entry_flush_timeout,
+ container);
+ }
+
+ return retval;
+}
+
+static void
+nautilus_icon_container_search_init (GtkWidget *entry,
+ NautilusIconContainer *container)
+{
+ gint ret;
+ gint len;
+ const gchar *text;
+
+ g_return_if_fail (GTK_IS_ENTRY (entry));
+ g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
+
+ text = gtk_entry_get_text (GTK_ENTRY (entry));
+ len = strlen (text);
+
+ /* search */
+ unselect_all (container);
+ if (container->details->typeselect_flush_timeout)
+ {
+ g_source_remove (container->details->typeselect_flush_timeout);
+ container->details->typeselect_flush_timeout =
+ g_timeout_add (NAUTILUS_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT,
+ (GSourceFunc) nautilus_icon_container_search_entry_flush_timeout,
+ container);
+ }
+
+ if (len < 1) {
+ return;
+ }
+
+ ret = nautilus_icon_container_search_iter (container, text, 1);
+
+ if (ret) {
+ container->details->selected_iter = 1;
+ }
+}
+
+static void
+nautilus_icon_container_ensure_interactive_directory (NautilusIconContainer *container)
+{
+ GtkWidget *frame, *vbox, *toplevel;
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (container));
+
+ if (container->details->search_window != NULL) {
+ return;
+ }
+
+ container->details->search_window = gtk_window_new (GTK_WINDOW_POPUP);
+
+ gtk_window_set_modal (GTK_WINDOW (container->details->search_window), TRUE);
+ g_signal_connect (container->details->search_window, "delete_event",
+ G_CALLBACK (nautilus_icon_container_search_delete_event),
+ container);
+ g_signal_connect (container->details->search_window, "key_press_event",
+ G_CALLBACK (nautilus_icon_container_search_key_press_event),
+ container);
+ g_signal_connect (container->details->search_window, "button_press_event",
+ G_CALLBACK (nautilus_icon_container_search_button_press_event),
+ container);
+ g_signal_connect (container->details->search_window, "scroll_event",
+ G_CALLBACK (nautilus_icon_container_search_scroll_event),
+ container);
+
+ 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 (container->details->search_window), frame);
+
+ vbox = gtk_vbox_new (FALSE, 0);
+ gtk_widget_show (vbox);
+ gtk_container_add (GTK_CONTAINER (frame), vbox);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox), 3);
+
+ /* add entry */
+ container->details->search_entry = gtk_entry_new ();
+ gtk_widget_show (container->details->search_entry);
+ g_signal_connect (container->details->search_entry, "populate_popup",
+ G_CALLBACK (nautilus_icon_container_search_disable_popdown),
+ container);
+ g_signal_connect (container->details->search_entry, "activate",
+ G_CALLBACK (nautilus_icon_container_search_activate),
+ container);
+ g_signal_connect (GTK_ENTRY (container->details->search_entry)->im_context,
+ "preedit-changed",
+ G_CALLBACK (nautilus_icon_container_search_preedit_changed),
+ container);
+ gtk_container_add (GTK_CONTAINER (vbox), container->details->search_entry);
+
+ gtk_widget_realize (container->details->search_entry);
+}
+
+/* Pops up the interactive search entry. If keybinding is TRUE then the user
+ * started this by typing the start_interactive_search keybinding. Otherwise, it came from
+ */
+static gboolean
+nautilus_icon_container_real_start_interactive_search (NautilusIconContainer *container,
+ gboolean keybinding)
+{
+ /* We only start interactive search if we have focus. If one of our
+ * children have focus, we don't want to start the search.
+ */
+ GtkWidgetClass *entry_parent_class;
+
+ if (container->details->search_window != NULL &&
+ GTK_WIDGET_VISIBLE (container->details->search_window)) {
+ return TRUE;
+ }
+
+ if (!GTK_WIDGET_HAS_FOCUS (container)) {
+ return FALSE;
}
- container->details->type_select_state->type_select_pattern = new_pattern;
- container->details->type_select_state->last_typeselect_time = now;
+ nautilus_icon_container_ensure_interactive_directory (container);
- if (!select_matching_name (container, new_pattern) &&
- !g_ascii_strcasecmp (new_pattern, "captain") &&
- nautilus_icon_container_get_is_desktop (container)) {
- begin_dave_bashing();
+ if (keybinding) {
+ gtk_entry_set_text (GTK_ENTRY (container->details->search_entry), "");
}
+ /* done, show it */
+ nautilus_icon_container_search_position_func (container, container->details->search_window);
+ gtk_widget_show (container->details->search_window);
+ if (container->details->search_entry_changed_id == 0)
+ {
+ container->details->search_entry_changed_id =
+ g_signal_connect (container->details->search_entry, "changed",
+ G_CALLBACK (nautilus_icon_container_search_init),
+ container);
+ }
+
+ container->details->typeselect_flush_timeout =
+ g_timeout_add (NAUTILUS_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT,
+ (GSourceFunc) nautilus_icon_container_search_entry_flush_timeout,
+ container);
+
+ /* Grab focus will select all the text. We don't want that to happen, so we
+ * call the parent instance and bypass the selection change. This is probably
+ * really non-kosher. */
+ entry_parent_class = g_type_class_peek_parent (GTK_ENTRY_GET_CLASS (container->details->search_entry));
+ (entry_parent_class->grab_focus) (container->details->search_entry);
+
+ /* send focus-in event */
+ send_focus_change (container->details->search_entry, TRUE);
+
+ /* search first matching iter */
+ nautilus_icon_container_search_init (container->details->search_entry, container);
+
return TRUE;
}
static gboolean
+nautilus_icon_container_start_interactive_search (NautilusIconContainer *container)
+{
+ return nautilus_icon_container_real_start_interactive_search (container, TRUE);
+}
+
+static gboolean
handle_popups (NautilusIconContainer *container,
GdkEventKey *event,
const char *signal)
@@ -3777,11 +4088,9 @@ key_press_event (GtkWidget *widget,
{
NautilusIconContainer *container;
gboolean handled;
- gboolean flush_typeahead;
container = NAUTILUS_ICON_CONTAINER (widget);
handled = FALSE;
- flush_typeahead = TRUE;
if (is_renaming (container) || is_renaming_pending (container)) {
switch (event->keyval) {
@@ -3872,23 +4181,70 @@ key_press_event (GtkWidget *widget,
}
break;
default:
- /* Don't use Control or Alt keys for type-selecting, because they
- * might be used for menus.
- */
- handled = (event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)) == 0 &&
- handle_typeahead (container, event, &flush_typeahead);
break;
}
}
- if (flush_typeahead) {
- /* any non-ascii key will force the typeahead state to be forgotten */
- nautilus_icon_container_flush_typeselect_state (container);
- }
-
if (!handled) {
handled = GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event);
}
+
+ /* We pass the event to the search_entry. If its text changes, then we
+ * start the typeahead find capabilities.
+ * Copied from NautilusIconContainer */
+ if (!handled) {
+ GdkEvent *new_event;
+ char *old_text;
+ const char *new_text;
+ gboolean retval;
+ GdkScreen *screen;
+ gboolean text_modified;
+ gulong popup_menu_id;
+
+ nautilus_icon_container_ensure_interactive_directory (container);
+
+ /* Make a copy of the current text */
+ old_text = g_strdup (gtk_entry_get_text (GTK_ENTRY (container->details->search_entry)));
+ new_event = gdk_event_copy ((GdkEvent *) event);
+ ((GdkEventKey *) new_event)->window = container->details->search_entry->window;
+ gtk_widget_realize (container->details->search_window);
+
+ popup_menu_id = g_signal_connect (container->details->search_entry,
+ "popup_menu", G_CALLBACK (gtk_true), NULL);
+
+ /* Move the entry off screen */
+ screen = gtk_widget_get_screen (GTK_WIDGET (container));
+ gtk_window_move (GTK_WINDOW (container->details->search_window),
+ gdk_screen_get_width (screen) + 1,
+ gdk_screen_get_height (screen) + 1);
+ gtk_widget_show (container->details->search_window);
+
+ /* Send the event to the window. If the preedit_changed signal is emitted
+ * during this event, we will set priv->imcontext_changed */
+ container->details->imcontext_changed = FALSE;
+ retval = gtk_widget_event (container->details->search_entry, new_event);
+ gtk_widget_hide (container->details->search_window);
+
+ g_signal_handler_disconnect (container->details->search_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 (container->details->search_entry));
+ text_modified = strcmp (old_text, new_text) != 0;
+ g_free (old_text);
+ if (container->details->imcontext_changed || /* we're in a preedit */
+ (retval && text_modified)) { /* ...or the text was modified */
+ if (nautilus_icon_container_real_start_interactive_search (container, FALSE)) {
+ gtk_widget_grab_focus (GTK_WIDGET (container));
+ return TRUE;
+ } else {
+ gtk_entry_set_text (GTK_ENTRY (container->details->search_entry), "");
+ return FALSE;
+ }
+ }
+ }
return handled;
}
@@ -3953,7 +4309,8 @@ nautilus_icon_container_class_init (Naut
{
GtkWidgetClass *widget_class;
EelCanvasClass *canvas_class;
-
+ GtkBindingSet *binding_set;
+
G_OBJECT_CLASS (class)->finalize = finalize;
GTK_OBJECT_CLASS (class)->destroy = destroy;
@@ -4252,6 +4609,16 @@ nautilus_icon_container_class_init (Naut
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
+ signals[START_INTERACTIVE_SEARCH]
+ = g_signal_new ("start_interactive_search",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (NautilusIconContainerClass,
+ start_interactive_search),
+ NULL, NULL,
+ nautilus_marshal_BOOLEAN__VOID,
+ G_TYPE_BOOLEAN, 0);
+
/* GtkWidget class. */
widget_class = GTK_WIDGET_CLASS (class);
@@ -4270,6 +4637,8 @@ nautilus_icon_container_class_init (Naut
canvas_class = EEL_CANVAS_CLASS (class);
canvas_class->draw_background = draw_canvas_background;
+
+ class->start_interactive_search = nautilus_icon_container_start_interactive_search;
gtk_widget_class_install_style_property (widget_class,
g_param_spec_boolean ("frame_text",
@@ -4318,6 +4687,11 @@ nautilus_icon_container_class_init (Naut
"Color used for information text against a light background",
GDK_TYPE_COLOR,
G_PARAM_READABLE));
+
+ binding_set = gtk_binding_set_by_class (class);
+
+ gtk_binding_entry_add_signal (binding_set, GDK_f, GDK_CONTROL_MASK, "start_interactive_search", 0);
+ gtk_binding_entry_add_signal (binding_set, GDK_F, GDK_CONTROL_MASK, "start_interactive_search", 0);
}
static void
Index: libnautilus-private/nautilus-icon-container.h
===================================================================
RCS file: /cvs/gnome/nautilus/libnautilus-private/nautilus-icon-container.h,v
retrieving revision 1.88
diff -p -u -r1.88 nautilus-icon-container.h
--- libnautilus-private/nautilus-icon-container.h 19 Sep 2005 09:59:50 -0000 1.88
+++ libnautilus-private/nautilus-icon-container.h 3 Oct 2005 14:08:29 -0000
@@ -194,6 +194,7 @@ typedef struct {
void (* icon_removed) (NautilusIconContainer *container,
NautilusIconData *data);
void (* cleared) (NautilusIconContainer *container);
+ gboolean (* start_interactive_search) (NautilusIconContainer *container);
} NautilusIconContainerClass;
/* GtkObject */
Index: libnautilus-private/nautilus-icon-private.h
===================================================================
RCS file: /cvs/gnome/nautilus/libnautilus-private/nautilus-icon-private.h,v
retrieving revision 1.79
diff -p -u -r1.79 nautilus-icon-private.h
--- libnautilus-private/nautilus-icon-private.h 3 Oct 2005 08:02:28 -0000 1.79
+++ libnautilus-private/nautilus-icon-private.h 3 Oct 2005 14:08:29 -0000
@@ -97,11 +97,6 @@ typedef enum {
AXIS_VERTICAL
} Axis;
-typedef struct {
- char *type_select_pattern;
- guint64 last_typeselect_time;
-} TypeSelectState;
-
enum {
LABEL_COLOR,
LABEL_COLOR_HIGHLIGHT,
@@ -171,9 +166,6 @@ struct NautilusIconContainerDetails {
GtkWidget *rename_widget; /* Editable text item */
char *original_text; /* Copy of editable text for later compare */
- /* typeahead selection state */
- TypeSelectState *type_select_state;
-
/* Idle ID. */
guint idle_id;
@@ -257,6 +249,15 @@ struct NautilusIconContainerDetails {
/* a11y items used by canvas items */
guint a11y_item_action_idle_handler;
GQueue* a11y_item_action_queue;
+
+ /* interactive search */
+ gboolean disable_popdown;
+ gboolean imcontext_changed;
+ int selected_iter;
+ GtkWidget *search_window;
+ GtkWidget *search_entry;
+ guint search_entry_changed_id;
+ guint typeselect_flush_timeout;
};
/* Private functions shared by mutiple files. */
@@ -279,7 +280,6 @@ char * nautilus_icon_container_ge
NautilusIcon *icon);
void nautilus_icon_container_update_icon (NautilusIconContainer *container,
NautilusIcon *icon);
-void nautilus_icon_container_flush_typeselect_state (NautilusIconContainer *container);
gboolean nautilus_icon_container_has_stored_icon_positions (NautilusIconContainer *container);
gboolean nautilus_icon_container_emit_preview_signal (NautilusIconContainer *view,
NautilusIcon *icon,
Index: libnautilus-private/nautilus-marshal.list
===================================================================
RCS file: /cvs/gnome/nautilus/libnautilus-private/nautilus-marshal.list,v
retrieving revision 1.9
diff -p -u -r1.9 nautilus-marshal.list
--- libnautilus-private/nautilus-marshal.list 5 Jul 2005 12:23:34 -0000 1.9
+++ libnautilus-private/nautilus-marshal.list 3 Oct 2005 14:08:29 -0000
@@ -1,6 +1,7 @@
STRING:VOID
OBJECT:BOXED
BOOLEAN:POINTER
+BOOLEAN:VOID
INT:POINTER,INT
INT:POINTER,BOOLEAN
INT:POINTER,POINTER
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]