#include <libempathy/empathy-utils.h>
#include "empathy-live-search.h"
-#include "empathy-gtk-marshal.h"
G_DEFINE_TYPE (EmpathyLiveSearch, empathy_live_search, GTK_TYPE_HBOX)
GtkWidget *search_entry;
GtkWidget *hook_widget;
- gunichar *text_stripped;
+ GPtrArray *stripped_words;
} EmpathyLiveSearchPriv;
enum
static guint signals[LAST_SIGNAL];
-static void live_search_hook_widget_destroy_cb (GtkObject *object,
+static void live_search_hook_widget_destroy_cb (GtkWidget *object,
gpointer user_data);
/**
{
gunichar retval = 0;
GUnicodeType utype;
- gunichar *decomp;
- gsize dlen;
utype = g_unichar_type (ch);
case G_UNICODE_CONTROL:
case G_UNICODE_FORMAT:
case G_UNICODE_UNASSIGNED:
+ case G_UNICODE_NON_SPACING_MARK:
case G_UNICODE_COMBINING_MARK:
+ case G_UNICODE_ENCLOSING_MARK:
/* Ignore those */
break;
+ case G_UNICODE_PRIVATE_USE:
+ case G_UNICODE_SURROGATE:
+ case G_UNICODE_LOWERCASE_LETTER:
+ case G_UNICODE_MODIFIER_LETTER:
+ case G_UNICODE_OTHER_LETTER:
+ case G_UNICODE_TITLECASE_LETTER:
+ case G_UNICODE_UPPERCASE_LETTER:
+ case G_UNICODE_DECIMAL_NUMBER:
+ case G_UNICODE_LETTER_NUMBER:
+ case G_UNICODE_OTHER_NUMBER:
+ case G_UNICODE_CONNECT_PUNCTUATION:
+ case G_UNICODE_DASH_PUNCTUATION:
+ case G_UNICODE_CLOSE_PUNCTUATION:
+ case G_UNICODE_FINAL_PUNCTUATION:
+ case G_UNICODE_INITIAL_PUNCTUATION:
+ case G_UNICODE_OTHER_PUNCTUATION:
+ case G_UNICODE_OPEN_PUNCTUATION:
+ case G_UNICODE_CURRENCY_SYMBOL:
+ case G_UNICODE_MODIFIER_SYMBOL:
+ case G_UNICODE_MATH_SYMBOL:
+ case G_UNICODE_OTHER_SYMBOL:
+ case G_UNICODE_LINE_SEPARATOR:
+ case G_UNICODE_PARAGRAPH_SEPARATOR:
+ case G_UNICODE_SPACE_SEPARATOR:
default:
ch = g_unichar_tolower (ch);
- decomp = g_unicode_canonical_decomposition (ch, &dlen);
- if (decomp != NULL)
- {
- retval = decomp[0];
- g_free (decomp);
- }
+ g_unichar_fully_decompose (ch, FALSE, &retval, 1);
}
return retval;
}
-static gunichar *
-strip_utf8_string (const gchar *string)
+static void
+append_word (GPtrArray **word_array,
+ GString **word)
+{
+ if (*word != NULL)
+ {
+ if (*word_array == NULL)
+ *word_array = g_ptr_array_new_with_free_func (g_free);
+ g_ptr_array_add (*word_array, g_string_free (*word, FALSE));
+ *word = NULL;
+ }
+}
+
+GPtrArray *
+empathy_live_search_strip_utf8_string (const gchar *string)
{
- gunichar *ret;
- gint ret_len;
+ GPtrArray *word_array = NULL;
+ GString *word = NULL;
const gchar *p;
if (EMP_STR_EMPTY (string))
return NULL;
- ret = g_malloc (sizeof (gunichar) * (strlen (string) + 1));
- ret_len = 0;
+ for (p = string; *p != '\0'; p = g_utf8_next_char (p))
+ {
+ gunichar sc;
+
+ /* Make the char lower-case, remove its accentuation marks, and ignore it
+ * if it is just unicode marks */
+ sc = stripped_char (g_utf8_get_char (p));
+ if (sc == 0)
+ continue;
+
+ /* If it is not alpha-num, it is separator between words */
+ if (!g_unichar_isalnum (sc))
+ {
+ append_word (&word_array, &word);
+ continue;
+ }
+
+ /* It is alpha-num, append this char to current word, or start new word */
+ if (word == NULL)
+ word = g_string_new (NULL);
+ g_string_append_unichar (word, sc);
+ }
+ append_word (&word_array, &word);
+
+ return word_array;
+}
+
+static gboolean
+live_search_match_prefix (const gchar *string,
+ const gchar *prefix)
+{
+ const gchar *p;
+ const gchar *prefix_p;
+ gboolean next_word = FALSE;
+
+ if (prefix == NULL || prefix[0] == 0)
+ return TRUE;
+
+ if (EMP_STR_EMPTY (string))
+ return FALSE;
+
+ prefix_p = prefix;
for (p = string; *p != '\0'; p = g_utf8_next_char (p))
{
gunichar sc;
+ /* Make the char lower-case, remove its accentuation marks, and ignore it
+ * if it is just unicode marks */
sc = stripped_char (g_utf8_get_char (p));
- if (sc != 0)
- ret[ret_len++] = sc;
+ if (sc == 0)
+ continue;
+
+ /* If we want to go to next word, ignore alpha-num chars */
+ if (next_word && g_unichar_isalnum (sc))
+ continue;
+ next_word = FALSE;
+
+ /* Ignore word separators */
+ if (!g_unichar_isalnum (sc))
+ continue;
+
+ /* If this char does not match prefix_p, go to next word and start again
+ * from the beginning of prefix */
+ if (sc != g_utf8_get_char (prefix_p))
+ {
+ next_word = TRUE;
+ prefix_p = prefix;
+ continue;
+ }
+
+ /* prefix_p match, verify to next char. If this was the last of prefix,
+ * it means it completely machted and we are done. */
+ prefix_p = g_utf8_next_char (prefix_p);
+ if (*prefix_p == '\0')
+ return TRUE;
}
- ret[ret_len] = 0;
+ return FALSE;
+}
+
+gboolean
+empathy_live_search_match_words (const gchar *string,
+ GPtrArray *words)
+{
+ guint i;
+
+ if (words == NULL)
+ return TRUE;
+
+ for (i = 0; i < words->len; i++)
+ if (!live_search_match_prefix (string, g_ptr_array_index (words, i)))
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+fire_key_navigation_sig (EmpathyLiveSearch *self,
+ GdkEventKey *event)
+{
+ gboolean ret;
+ g_signal_emit (self, signals[KEYNAV], 0, event, &ret);
return ret;
}
gpointer user_data)
{
EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (user_data);
- gboolean ret;
/* if esc key pressed, hide the search */
- if (event->keyval == GDK_Escape)
+ if (event->keyval == GDK_KEY_Escape)
{
gtk_widget_hide (GTK_WIDGET (self));
return TRUE;
}
/* emit key navigation signal, so other widgets can respond to it properly */
- if (event->keyval == GDK_Up || event->keyval == GDK_Down
- || event->keyval == GDK_Left || event->keyval == GDK_Right)
+ if (event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_Down
+ || event->keyval == GDK_KEY_Page_Up || event->keyval == GDK_KEY_Page_Down)
{
- g_signal_emit (self, signals[KEYNAV], 0, event, &ret);
- return ret;
+ return fire_key_navigation_sig (self, event);
}
+ if (event->keyval == GDK_KEY_Home || event->keyval == GDK_KEY_End ||
+ event->keyval == GDK_KEY_space)
+ {
+ /* If the live search is visible, the entry should catch the Home/End
+ * and space events */
+ if (!gtk_widget_get_visible (GTK_WIDGET (self)))
+ {
+ return fire_key_navigation_sig (self, event);
+ }
+ }
+
return FALSE;
}
else
gtk_widget_show (GTK_WIDGET (self));
- g_free (priv->text_stripped);
- priv->text_stripped = strip_utf8_string (text);
+ if (priv->stripped_words != NULL)
+ g_ptr_array_unref (priv->stripped_words);
+
+ priv->stripped_words = empathy_live_search_strip_utf8_string (text);
+
g_object_notify (G_OBJECT (self), "text");
}
/* dont forward this event to the entry, else the event is consumed by the
* entry and does not close the window */
if (!gtk_widget_get_visible (GTK_WIDGET (self)) &&
- event->keyval == GDK_Escape)
+ event->keyval == GDK_KEY_Escape)
return FALSE;
/* do not show the search if CTRL and/or ALT are pressed with a key
* this is needed, because otherwise the CTRL + F accel would not work,
* because the entry consumes it */
if (event->state & (GDK_MOD1_MASK | GDK_CONTROL_MASK) ||
- event->keyval == GDK_Control_L ||
- event->keyval == GDK_Control_R)
+ event->keyval == GDK_KEY_Control_L ||
+ event->keyval == GDK_KEY_Control_R)
return FALSE;
- /* dont forward the up and down arrow keys to the entry, they are needed for
- * navigation in the treeview and are not needed in the search entry */
- if (event->keyval == GDK_Up || event->keyval == GDK_Down)
+ /* dont forward the up/down and Page Up/Down arrow keys to the entry,
+ * they are needed for navigation in the treeview and are not needed in
+ * the search entry */
+ if (event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_Down ||
+ event->keyval == GDK_KEY_Page_Up || event->keyval == GDK_KEY_Page_Down)
return FALSE;
+ if (event->keyval == GDK_KEY_Home || event->keyval == GDK_KEY_End ||
+ event->keyval == GDK_KEY_space)
+ {
+ /* Home/End and space keys have to be forwarded to the entry only if
+ * the live search is visible (to move the cursor inside the entry). */
+ if (!gtk_widget_get_visible (GTK_WIDGET (self)))
+ return FALSE;
+ }
+
/* realize the widget if it is not realized yet */
gtk_widget_realize (priv->search_entry);
if (!gtk_widget_has_focus (priv->search_entry))
}
static void
-live_search_hook_widget_destroy_cb (GtkObject *object,
+live_search_hook_widget_destroy_cb (GtkWidget *object,
gpointer user_data)
{
EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (user_data);
EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (obj);
EmpathyLiveSearchPriv *priv = GET_PRIV (self);
- g_free (priv->text_stripped);
+ if (priv->stripped_words != NULL)
+ g_ptr_array_unref (priv->stripped_words);
if (G_OBJECT_CLASS (empathy_live_search_parent_class)->finalize != NULL)
G_OBJECT_CLASS (empathy_live_search_parent_class)->finalize (obj);
}
static void
-live_search_hide (GtkWidget *widget)
+live_search_unmap (GtkWidget *widget)
{
EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (widget);
EmpathyLiveSearchPriv *priv = GET_PRIV (self);
- GTK_WIDGET_CLASS (empathy_live_search_parent_class)->hide (widget);
+ GTK_WIDGET_CLASS (empathy_live_search_parent_class)->unmap (widget);
+
+ /* unmap can happen if a parent gets hidden, in that case we want to hide
+ * the live search as well, so when it gets mapped again, the live search
+ * won't be shown. */
+ gtk_widget_hide (widget);
gtk_entry_set_text (GTK_ENTRY (priv->search_entry), "");
gtk_widget_grab_focus (priv->hook_widget);
object_class->get_property = live_search_get_property;
object_class->set_property = live_search_set_property;
- widget_class->hide = live_search_hide;
+ widget_class->unmap = live_search_unmap;
widget_class->show = live_search_show;
widget_class->grab_focus = live_search_grab_focus;
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
- g_cclosure_marshal_VOID__VOID,
+ g_cclosure_marshal_generic,
G_TYPE_NONE, 0);
signals[KEYNAV] = g_signal_new ("key-navigation",
G_SIGNAL_RUN_LAST,
0,
g_signal_accumulator_true_handled, NULL,
- _empathy_gtk_marshal_BOOLEAN__BOXED,
+ g_cclosure_marshal_generic,
G_TYPE_BOOLEAN, 1, GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
- param_spec = g_param_spec_object ("hook-widget", "Live Searchs Hook Widget",
+ param_spec = g_param_spec_object ("hook-widget", "Live Search Hook Widget",
"The live search catches key-press-events on this widget",
GTK_TYPE_WIDGET, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_HOOK_WIDGET,
gtk_entry_set_text (GTK_ENTRY (priv->search_entry), text);
}
-static gboolean
-live_search_match_string (const gchar *string,
- const gunichar *prefix)
-{
- const gchar *p;
-
- if (prefix == NULL || prefix[0] == 0)
- return TRUE;
-
- if (EMP_STR_EMPTY (string))
- return FALSE;
-
- for (p = string; *p != '\0'; p = g_utf8_next_char (p))
- {
- guint i = 0;
-
- /* Search the start of the word (skip non alpha-num chars) */
- while (*p != '\0' && !g_unichar_isalnum (g_utf8_get_char (p)))
- p = g_utf8_next_char (p);
-
- /* Check if this word match prefix */
- while (*p != '\0')
- {
- gunichar sc;
-
- sc = stripped_char (g_utf8_get_char (p));
- if (sc != 0)
- {
- /* If the char does not match, stop */
- if (sc != prefix[i])
- break;
-
- /* The char matched. If it was the last of prefix, stop */
- if (prefix[++i] == 0)
- return TRUE;
- }
-
- p = g_utf8_next_char (p);
- }
-
- /* This word didn't match, go to next one (skip alpha-num chars) */
- while (*p != '\0' && g_unichar_isalnum (g_utf8_get_char (p)))
- p = g_utf8_next_char (p);
-
- if (*p == '\0')
- break;
- }
-
- return FALSE;
-}
-
/**
* empathy_live_search_match:
* @self: a #EmpathyLiveSearch
priv = GET_PRIV (self);
- return live_search_match_string (string, priv->text_stripped);
+ return empathy_live_search_match_words (string, priv->stripped_words);
}
gboolean
empathy_live_search_match_string (const gchar *string,
const gchar *prefix)
{
- gunichar *stripped;
+ GPtrArray *words;
gboolean match;
- stripped = strip_utf8_string (prefix);
- match = live_search_match_string (string, stripped);
- g_free (stripped);
+ words = empathy_live_search_strip_utf8_string (prefix);
+ match = empathy_live_search_match_words (string, words);
+ if (words != NULL)
+ g_ptr_array_unref (words);
return match;
}
+GPtrArray *
+empathy_live_search_get_words (EmpathyLiveSearch *self)
+{
+ EmpathyLiveSearchPriv *priv = GET_PRIV (self);
+
+ return priv->stripped_words;
+}