GtkSearchEntry: Delay the changed signal by default

Emit the "changed" signal after 150 msecs, so that searching
through big lists, or doing online searches feels more responsive.

This is something already done in various applications to make
search-as-you type more responsive (gnome-shell, gnome-documents,
gnome-control-center, etc.). The 150 msecs is the value currently
used by gnome-shell, so keep it (invisibly) consistent.

https://bugzilla.gnome.org/show_bug.cgi?id=700229
This commit is contained in:
Bastien Nocera 2013-05-16 18:20:19 +02:00
parent 9d5b4f8030
commit 51e238653d
4 changed files with 167 additions and 6 deletions

View File

@ -42,6 +42,7 @@ demos = \
revealer.c \
rotated_text.c \
search_entry.c \
search_entry2.c \
sizegroup.c \
spinner.c \
stack.c \

View File

@ -116,6 +116,7 @@
<file>revealer.c</file>
<file>rotated_text.c</file>
<file>search_entry.c</file>
<file>search_entry2.c</file>
<file>sizegroup.c</file>
<file>stack.c</file>
<file>spinner.c</file>

View File

@ -0,0 +1,96 @@
/* Entry/Delayed Search Entry
*
* GtkSearchEntry sets up GtkEntries ready for search. Search entries
* have their "changed" signal delayed and should be used
* when the searched operation is slow such as loads of entries
* to search, or online searches.
*/
#include <gtk/gtk.h>
static GtkWidget *window = NULL;
static void
search_entry_destroyed (GtkWidget *widget)
{
window = NULL;
}
static void
search_changed_cb (GtkSearchEntry *entry,
GtkLabel *result_label)
{
const char *text;
text = gtk_entry_get_text (GTK_ENTRY (entry));
g_message ("search changed: %s", text);
gtk_label_set_text (result_label, text ? text : "");
}
GtkWidget *
do_search_entry2 (GtkWidget *do_widget)
{
GtkWidget *content_area;
GtkWidget *vbox;
GtkWidget *hbox;
GtkWidget *label;
GtkWidget *entry;
GtkWidget *button;
if (!window)
{
window = gtk_dialog_new_with_buttons ("Search Entry #2",
GTK_WINDOW (do_widget),
0,
GTK_STOCK_CLOSE,
GTK_RESPONSE_NONE,
NULL);
gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
g_signal_connect (window, "response",
G_CALLBACK (gtk_widget_destroy), NULL);
g_signal_connect (window, "destroy",
G_CALLBACK (search_entry_destroyed), &window);
content_area = gtk_dialog_get_content_area (GTK_DIALOG (window));
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5);
gtk_box_pack_start (GTK_BOX (content_area), vbox, TRUE, TRUE, 0);
gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
label = gtk_label_new (NULL);
gtk_label_set_markup (GTK_LABEL (label), "Search entry demo #2");
gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
/* Create our entry */
entry = gtk_search_entry_new ();
gtk_box_pack_start (GTK_BOX (vbox), entry, FALSE, FALSE, 0);
/* Result */
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
gtk_container_set_border_width (GTK_CONTAINER (hbox), 0);
label = gtk_label_new ("Result:");
gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
label = gtk_label_new ("");
gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
g_signal_connect (entry, "changed",
G_CALLBACK (search_changed_cb), label);
/* Give the focus to the close button */
button = gtk_dialog_get_widget_for_response (GTK_DIALOG (window), GTK_RESPONSE_NONE);
gtk_widget_grab_focus (button);
}
if (!gtk_widget_get_visible (window))
gtk_widget_show_all (window);
else
{
gtk_widget_destroy (window);
window = NULL;
}
return window;
}

View File

@ -51,9 +51,37 @@
G_DEFINE_TYPE (GtkSearchEntry, gtk_search_entry, GTK_TYPE_ENTRY)
typedef struct {
guint delayed_changed_id;
gboolean in_timeout;
} GtkSearchEntryPrivate;
/* 150 mseconds of delay */
#define DELAYED_TIMEOUT_ID 150
/* This widget got created without a private structure, meaning
* that we cannot now have one without breaking ABI */
#define GET_PRIV(e) G_TYPE_INSTANCE_GET_PRIVATE (e, GTK_TYPE_SEARCH_ENTRY, GtkSearchEntryPrivate)
static void
gtk_search_entry_finalize (GObject *object)
{
GtkSearchEntryPrivate *priv = GET_PRIV (object);
if (priv->delayed_changed_id > 0)
g_source_remove (priv->delayed_changed_id);
G_OBJECT_CLASS (gtk_search_entry_parent_class)->finalize (object);
}
static void
gtk_search_entry_class_init (GtkSearchEntryClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gtk_search_entry_finalize;
g_type_class_add_private (klass, sizeof (GtkSearchEntryPrivate));
}
static void
@ -63,14 +91,42 @@ search_entry_clear_cb (GtkEntry *entry,
gtk_entry_set_text (entry, "");
}
static void
search_entry_changed_cb (GtkEntry *entry,
gpointer user_data)
static gboolean
gtk_search_entry_changed_timeout_cb (gpointer user_data)
{
GtkSearchEntry *entry = user_data;
GtkSearchEntryPrivate *priv = GET_PRIV (entry);
priv->in_timeout = TRUE;
g_signal_emit_by_name (entry, "changed");
priv->delayed_changed_id = 0;
priv->in_timeout = FALSE;
return G_SOURCE_REMOVE;
}
static void
reset_timeout (GtkSearchEntry *entry)
{
GtkSearchEntryPrivate *priv = GET_PRIV (entry);
if (priv->delayed_changed_id > 0)
g_source_remove (priv->delayed_changed_id);
priv->delayed_changed_id = g_timeout_add (DELAYED_TIMEOUT_ID,
gtk_search_entry_changed_timeout_cb,
entry);
}
static void
search_entry_changed_cb (GtkSearchEntry *entry,
gpointer user_data)
{
GtkSearchEntryPrivate *priv = GET_PRIV (entry);
const char *str, *icon_name;
gboolean active;
str = gtk_entry_get_text (entry);
/* Update the icons first */
str = gtk_entry_get_text (GTK_ENTRY (entry));
if (str == NULL || *str == '\0')
{
@ -91,6 +147,15 @@ search_entry_changed_cb (GtkEntry *entry,
"secondary-icon-activatable", active,
"secondary-icon-sensitive", active,
NULL);
/* Don't stop the emission if it's the timeout
* emitting the signal, otherwise we'll get in a loop */
if (priv->in_timeout)
return;
/* Queue up the timeout */
reset_timeout (entry);
g_signal_stop_emission_by_name (entry, "changed");
}
static void
@ -106,8 +171,6 @@ gtk_search_entry_init (GtkSearchEntry *entry)
"primary-icon-activatable", FALSE,
"primary-icon-sensitive", FALSE,
NULL);
search_entry_changed_cb (GTK_ENTRY (entry), NULL);
}
/**