empathy-contact-blocking-dialog.c \
empathy-contact-chooser.c \
empathy-contactinfo-utils.c \
- empathy-live-search.c \
empathy-contact-search-dialog.c \
empathy-contact-widget.c \
empathy-dialpad-widget.c \
empathy-chat.h \
empathy-contact-blocking-dialog.h \
empathy-contact-chooser.h \
- empathy-live-search.h \
empathy-contact-search-dialog.h \
empathy-contact-widget.h \
empathy-contactinfo-utils.h \
id = gtk_entry_get_text (entry);
- self->priv->search_words = empathy_live_search_strip_utf8_string (id);
+ self->priv->search_words = tpaw_live_search_strip_utf8_string (id);
self->priv->search_str = g_strdup (id);
add_temporary_individuals (self, id);
}
static void
-individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
+individual_view_search_text_notify_cb (TpawLiveSearch *search,
GParamSpec *pspec,
EmpathyIndividualView *view)
{
}
static void
-individual_view_search_hide_cb (EmpathyLiveSearch *search,
+individual_view_search_hide_cb (TpawLiveSearch *search,
EmpathyIndividualView *view)
{
EmpathyIndividualViewPriv *priv = GET_PRIV (view);
}
static void
-individual_view_search_show_cb (EmpathyLiveSearch *search,
+individual_view_search_show_cb (TpawLiveSearch *search,
EmpathyIndividualView *view)
{
/* block expand or collapse handlers during expand all, they would
guint event_count)
{
EmpathyIndividualViewPriv *priv = GET_PRIV (self);
- EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
+ TpawLiveSearch *live = TPAW_LIVE_SEARCH (priv->search_widget);
GeeSet *personas;
GeeIterator *iter;
gboolean is_favorite;
}
return empathy_individual_match_string (individual,
- empathy_live_search_get_text (live),
- empathy_live_search_get_words (live));
+ tpaw_live_search_get_text (live),
+ tpaw_live_search_get_words (live));
}
static gchar *
void
empathy_individual_view_set_live_search (EmpathyIndividualView *view,
- EmpathyLiveSearch *search)
+ TpawLiveSearch *search)
{
EmpathyIndividualViewPriv *priv = GET_PRIV (view);
#include <gtk/gtk.h>
#include <folks/folks.h>
+#include <tp-account-widgets/tpaw-live-search.h>
#include "empathy-individual-menu.h"
#include "empathy-individual-store.h"
-#include "empathy-live-search.h"
G_BEGIN_DECLS
#define EMPATHY_TYPE_INDIVIDUAL_VIEW (empathy_individual_view_get_type ())
GtkWidget *empathy_individual_view_get_group_menu (EmpathyIndividualView *view);
void empathy_individual_view_set_live_search (EmpathyIndividualView *view,
- EmpathyLiveSearch *search);
+ TpawLiveSearch *search);
gboolean empathy_individual_view_get_show_offline (
EmpathyIndividualView *view);
+++ /dev/null
-/*
- * Copyright (C) 2010 Collabora Ltd.
- * Copyright (C) 2007-2010 Nokia Corporation.
- *
- * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Authors: Felix Kaser <felix.kaser@collabora.co.uk>
- * Xavier Claessens <xavier.claessens@collabora.co.uk>
- * Claudio Saavedra <csaavedra@igalia.com>
- */
-
-#include "config.h"
-#include "empathy-live-search.h"
-
-#include "empathy-utils.h"
-
-G_DEFINE_TYPE (EmpathyLiveSearch, empathy_live_search, GTK_TYPE_HBOX)
-
-#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyLiveSearch)
-
-typedef struct
-{
- GtkWidget *search_entry;
- GtkWidget *hook_widget;
-
- GPtrArray *stripped_words;
-} EmpathyLiveSearchPriv;
-
-enum
-{
- PROP_0,
- PROP_HOOK_WIDGET,
- PROP_TEXT
-};
-
-enum
-{
- ACTIVATE,
- KEYNAV,
- LAST_SIGNAL
-};
-
-static guint signals[LAST_SIGNAL];
-
-static void live_search_hook_widget_destroy_cb (GtkWidget *object,
- gpointer user_data);
-
-/**
- * stripped_char:
- *
- * Returns a stripped version of @ch, removing any case, accentuation
- * mark, or any special mark on it.
- **/
-static gunichar
-stripped_char (gunichar ch)
-{
- gunichar retval = 0;
- GUnicodeType utype;
-
- utype = g_unichar_type (ch);
-
- switch (utype)
- {
- 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);
- g_unichar_fully_decompose (ch, FALSE, &retval, 1);
- }
-
- return retval;
-}
-
-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)
-{
- GPtrArray *word_array = NULL;
- GString *word = NULL;
- const gchar *p;
-
- if (EMP_STR_EMPTY (string))
- return NULL;
-
- 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)
- 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;
- }
-
- 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;
-}
-
-static gboolean
-live_search_entry_key_pressed_cb (GtkEntry *entry,
- GdkEventKey *event,
- gpointer user_data)
-{
- EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (user_data);
-
- /* if esc key pressed, hide the search */
- 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_KEY_Up || event->keyval == GDK_KEY_Down
- || event->keyval == GDK_KEY_Page_Up || event->keyval == GDK_KEY_Page_Down
- || event->keyval == GDK_KEY_Menu)
- {
- 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;
-}
-
-static void
-live_search_text_changed (GtkEntry *entry,
- gpointer user_data)
-{
- EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (user_data);
- EmpathyLiveSearchPriv *priv = GET_PRIV (self);
- const gchar *text;
-
- text = gtk_entry_get_text (entry);
-
- if (EMP_STR_EMPTY (text))
- gtk_widget_hide (GTK_WIDGET (self));
- else
- gtk_widget_show (GTK_WIDGET (self));
-
- 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");
-}
-
-static void
-live_search_close_pressed (GtkEntry *entry,
- GtkEntryIconPosition icon_pos,
- GdkEvent *event,
- gpointer user_data)
-{
- EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (user_data);
-
- gtk_widget_hide (GTK_WIDGET (self));
-}
-
-static gboolean
-live_search_key_press_event_cb (GtkWidget *widget,
- GdkEventKey *event,
- gpointer user_data)
-{
- EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (user_data);
- EmpathyLiveSearchPriv *priv = GET_PRIV (self);
- GdkEvent *new_event;
- gboolean ret;
-
- /* 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_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_KEY_Control_L ||
- event->keyval == GDK_KEY_Control_R)
- return FALSE;
-
- /* 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 ||
- event->keyval == GDK_KEY_Menu)
- 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;
- }
-
- /* Don't forward shift keys events as focusing the search entry would
- * cancel an in-progress editing on a cell renderer (like when renaming a
- * group). There is no point focusing it anyway as we don't display the
- * search entry when only a shift key is pressed. */
- if (event->keyval == GDK_KEY_Shift_L ||
- event->keyval == GDK_KEY_Shift_R)
- 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))
- {
- gtk_widget_grab_focus (priv->search_entry);
- gtk_editable_set_position (GTK_EDITABLE (priv->search_entry), -1);
- }
-
- /* forward the event to the search entry */
- new_event = gdk_event_copy ((GdkEvent *) event);
- ret = gtk_widget_event (priv->search_entry, new_event);
- gdk_event_free (new_event);
-
- return ret;
-}
-
-static void
-live_search_entry_activate_cb (GtkEntry *entry,
- EmpathyLiveSearch *self)
-{
- g_signal_emit (self, signals[ACTIVATE], 0);
-}
-
-static void
-live_search_release_hook_widget (EmpathyLiveSearch *self)
-{
- EmpathyLiveSearchPriv *priv = GET_PRIV (self);
-
- /* remove old handlers if old source was not null */
- if (priv->hook_widget != NULL)
- {
- g_signal_handlers_disconnect_by_func (priv->hook_widget,
- live_search_key_press_event_cb, self);
- g_signal_handlers_disconnect_by_func (priv->hook_widget,
- live_search_hook_widget_destroy_cb, self);
- g_object_unref (priv->hook_widget);
- priv->hook_widget = NULL;
- }
-}
-
-static void
-live_search_hook_widget_destroy_cb (GtkWidget *object,
- gpointer user_data)
-{
- EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (user_data);
-
- /* unref the hook widget and hide search */
- gtk_widget_hide (GTK_WIDGET (self));
- live_search_release_hook_widget (self);
-}
-
-static void
-live_search_dispose (GObject *obj)
-{
- EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (obj);
-
- live_search_release_hook_widget (self);
-
- if (G_OBJECT_CLASS (empathy_live_search_parent_class)->dispose != NULL)
- G_OBJECT_CLASS (empathy_live_search_parent_class)->dispose (obj);
-}
-
-static void
-live_search_finalize (GObject *obj)
-{
- EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (obj);
- EmpathyLiveSearchPriv *priv = GET_PRIV (self);
-
- 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_get_property (GObject *object,
- guint param_id,
- GValue *value,
- GParamSpec *pspec)
-{
- EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (object);
-
- switch (param_id)
- {
- case PROP_HOOK_WIDGET:
- g_value_set_object (value, empathy_live_search_get_hook_widget (self));
- break;
- case PROP_TEXT:
- g_value_set_string (value, empathy_live_search_get_text (self));
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
- break;
- }
-}
-
-static void
-live_search_set_property (GObject *object,
- guint param_id,
- const GValue *value,
- GParamSpec *pspec)
-{
- EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (object);
-
- switch (param_id) {
- case PROP_HOOK_WIDGET:
- empathy_live_search_set_hook_widget (self, g_value_get_object (value));
- break;
- case PROP_TEXT:
- empathy_live_search_set_text (self, g_value_get_string (value));
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
- break;
- };
-}
-
-static void
-live_search_unmap (GtkWidget *widget)
-{
- EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (widget);
- EmpathyLiveSearchPriv *priv = GET_PRIV (self);
-
- 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), "");
-
- if (priv->hook_widget != NULL)
- gtk_widget_grab_focus (priv->hook_widget);
-}
-
-static void
-live_search_show (GtkWidget *widget)
-{
- EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (widget);
- EmpathyLiveSearchPriv *priv = GET_PRIV (self);
-
- if (!gtk_widget_has_focus (priv->search_entry))
- gtk_widget_grab_focus (priv->search_entry);
-
- GTK_WIDGET_CLASS (empathy_live_search_parent_class)->show (widget);
-}
-
-static void
-live_search_grab_focus (GtkWidget *widget)
-{
- EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (widget);
- EmpathyLiveSearchPriv *priv = GET_PRIV (self);
-
- if (!gtk_widget_has_focus (priv->search_entry))
- {
- gtk_widget_grab_focus (priv->search_entry);
- gtk_editable_set_position (GTK_EDITABLE (priv->search_entry), -1);
- }
-}
-
-static void
-empathy_live_search_class_init (EmpathyLiveSearchClass *klass)
-{
- GObjectClass *object_class = (GObjectClass *) klass;
- GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
- GParamSpec *param_spec;
-
- object_class->finalize = live_search_finalize;
- object_class->dispose = live_search_dispose;
- object_class->get_property = live_search_get_property;
- object_class->set_property = live_search_set_property;
-
- widget_class->unmap = live_search_unmap;
- widget_class->show = live_search_show;
- widget_class->grab_focus = live_search_grab_focus;
-
- signals[ACTIVATE] = g_signal_new ("activate",
- G_TYPE_FROM_CLASS (object_class),
- G_SIGNAL_RUN_LAST,
- 0,
- NULL, NULL,
- g_cclosure_marshal_generic,
- G_TYPE_NONE, 0);
-
- signals[KEYNAV] = g_signal_new ("key-navigation",
- G_TYPE_FROM_CLASS (object_class),
- G_SIGNAL_RUN_LAST,
- 0,
- g_signal_accumulator_true_handled, NULL,
- 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 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,
- param_spec);
-
- param_spec = g_param_spec_string ("text", "Live Search Text",
- "The text of the live search entry",
- "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_TEXT, param_spec);
-
- g_type_class_add_private (klass, sizeof (EmpathyLiveSearchPriv));
-}
-
-static void
-empathy_live_search_init (EmpathyLiveSearch *self)
-{
- EmpathyLiveSearchPriv *priv =
- G_TYPE_INSTANCE_GET_PRIVATE ((self), EMPATHY_TYPE_LIVE_SEARCH,
- EmpathyLiveSearchPriv);
-
- gtk_widget_set_no_show_all (GTK_WIDGET (self), TRUE);
-
- priv->search_entry = gtk_entry_new ();
- gtk_entry_set_icon_from_stock (GTK_ENTRY (priv->search_entry),
- GTK_ENTRY_ICON_SECONDARY, GTK_STOCK_CLOSE);
- gtk_entry_set_icon_activatable (GTK_ENTRY (priv->search_entry),
- GTK_ENTRY_ICON_SECONDARY, TRUE);
- gtk_entry_set_icon_sensitive (GTK_ENTRY (priv->search_entry),
- GTK_ENTRY_ICON_SECONDARY, TRUE);
- gtk_widget_show (priv->search_entry);
-
- gtk_box_pack_start (GTK_BOX (self), priv->search_entry, TRUE, TRUE, 0);
-
- g_signal_connect (priv->search_entry, "icon_release",
- G_CALLBACK (live_search_close_pressed), self);
- g_signal_connect (priv->search_entry, "changed",
- G_CALLBACK (live_search_text_changed), self);
- g_signal_connect (priv->search_entry, "key-press-event",
- G_CALLBACK (live_search_entry_key_pressed_cb), self);
- g_signal_connect (priv->search_entry, "activate",
- G_CALLBACK (live_search_entry_activate_cb), self);
-
- priv->hook_widget = NULL;
-
- self->priv = priv;
-}
-
-GtkWidget *
-empathy_live_search_new (GtkWidget *hook)
-{
- g_return_val_if_fail (hook == NULL || GTK_IS_WIDGET (hook), NULL);
-
- return g_object_new (EMPATHY_TYPE_LIVE_SEARCH,
- "hook-widget", hook,
- NULL);
-}
-
-/* public methods */
-
-GtkWidget *
-empathy_live_search_get_hook_widget (EmpathyLiveSearch *self)
-{
- EmpathyLiveSearchPriv *priv = GET_PRIV (self);
-
- g_return_val_if_fail (EMPATHY_IS_LIVE_SEARCH (self), NULL);
-
- return priv->hook_widget;
-}
-
-void
-empathy_live_search_set_hook_widget (EmpathyLiveSearch *self,
- GtkWidget *hook)
-{
- EmpathyLiveSearchPriv *priv;
-
- g_return_if_fail (EMPATHY_IS_LIVE_SEARCH (self));
- g_return_if_fail (hook == NULL || GTK_IS_WIDGET (hook));
-
- priv = GET_PRIV (self);
-
- /* release the actual widget */
- live_search_release_hook_widget (self);
-
- /* connect handlers if new source is not null */
- if (hook != NULL)
- {
- priv->hook_widget = g_object_ref (hook);
- g_signal_connect (priv->hook_widget, "key-press-event",
- G_CALLBACK (live_search_key_press_event_cb),
- self);
- g_signal_connect (priv->hook_widget, "destroy",
- G_CALLBACK (live_search_hook_widget_destroy_cb),
- self);
- }
-}
-
-const gchar *
-empathy_live_search_get_text (EmpathyLiveSearch *self)
-{
- EmpathyLiveSearchPriv *priv = GET_PRIV (self);
-
- g_return_val_if_fail (EMPATHY_IS_LIVE_SEARCH (self), NULL);
-
- return gtk_entry_get_text (GTK_ENTRY (priv->search_entry));
-}
-
-void
-empathy_live_search_set_text (EmpathyLiveSearch *self,
- const gchar *text)
-{
- EmpathyLiveSearchPriv *priv = GET_PRIV (self);
-
- g_return_if_fail (EMPATHY_IS_LIVE_SEARCH (self));
- g_return_if_fail (text != NULL);
-
- gtk_entry_set_text (GTK_ENTRY (priv->search_entry), text);
-}
-
-/**
- * empathy_live_search_match:
- * @self: a #EmpathyLiveSearch
- * @string: a string where to search, must be valid UTF-8.
- *
- * Search if one of the words in @string string starts with the current text
- * of @self.
- *
- * Searching for "aba" in "Abasto" will match, searching in "Moraba" will not,
- * and searching in "A tool (abacus)" will do.
- *
- * The match is not case-sensitive, and regardless of the accentuation marks.
- *
- * Returns: %TRUE if a match is found, %FALSE otherwise.
- *
- **/
-gboolean
-empathy_live_search_match (EmpathyLiveSearch *self,
- const gchar *string)
-{
- EmpathyLiveSearchPriv *priv;
-
- g_return_val_if_fail (EMPATHY_IS_LIVE_SEARCH (self), FALSE);
-
- priv = GET_PRIV (self);
-
- return empathy_live_search_match_words (string, priv->stripped_words);
-}
-
-gboolean
-empathy_live_search_match_string (const gchar *string,
- const gchar *prefix)
-{
- GPtrArray *words;
- gboolean match;
-
- 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;
-}
+++ /dev/null
-/*
- * Copyright (C) 2010 Collabora Ltd.
- * Copyright (C) 2007-2010 Nokia Corporation.
- *
- * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Authors: Felix Kaser <felix.kaser@collabora.co.uk>
- * Xavier Claessens <xavier.claessens@collabora.co.uk>
- * Claudio Saavedra <csaavedra@igalia.com>
- */
-
-#ifndef __EMPATHY_LIVE_SEARCH_H__
-#define __EMPATHY_LIVE_SEARCH_H__
-
-#include <gtk/gtk.h>
-
-G_BEGIN_DECLS
-
-#define EMPATHY_TYPE_LIVE_SEARCH (empathy_live_search_get_type ())
-#define EMPATHY_LIVE_SEARCH(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EMPATHY_TYPE_LIVE_SEARCH, EmpathyLiveSearch))
-#define EMPATHY_LIVE_SEARCH_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), EMPATHY_TYPE_LIVE_SEARCH, EmpathyLiveSearchClass))
-#define EMPATHY_IS_LIVE_SEARCH(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EMPATHY_TYPE_LIVE_SEARCH))
-#define EMPATHY_IS_LIVE_SEARCH_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EMPATHY_TYPE_LIVE_SEARCH))
-#define EMPATHY_LIVE_SEARCH_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EMPATHY_TYPE_LIVE_SEARCH, EmpathyLiveSearchClass))
-
-typedef struct _EmpathyLiveSearch EmpathyLiveSearch;
-typedef struct _EmpathyLiveSearchClass EmpathyLiveSearchClass;
-
-struct _EmpathyLiveSearch {
- GtkHBox parent;
-
- /*<private>*/
- gpointer priv;
-};
-
-struct _EmpathyLiveSearchClass {
- GtkHBoxClass parent_class;
-};
-
-GType empathy_live_search_get_type (void) G_GNUC_CONST;
-GtkWidget *empathy_live_search_new (GtkWidget *hook);
-
-GtkWidget *empathy_live_search_get_hook_widget (EmpathyLiveSearch *self);
-void empathy_live_search_set_hook_widget (EmpathyLiveSearch *self,
- GtkWidget *hook);
-
-const gchar *empathy_live_search_get_text (EmpathyLiveSearch *self);
-void empathy_live_search_set_text (EmpathyLiveSearch *self,
- const gchar *text);
-
-gboolean empathy_live_search_match (EmpathyLiveSearch *self,
- const gchar *string);
-
-GPtrArray * empathy_live_search_strip_utf8_string (const gchar *string);
-
-gboolean empathy_live_search_match_words (const gchar *string,
- GPtrArray *words);
-
-GPtrArray * empathy_live_search_get_words (EmpathyLiveSearch *self);
-
-/* Made public for unit tests */
-gboolean empathy_live_search_match_string (const gchar *string,
- const gchar *prefix);
-
-G_END_DECLS
-
-#endif /* __EMPATHY_LIVE_SEARCH_H__ */
gboolean show_groups;
gboolean empty;
- EmpathyLiveSearch *search;
+ TpawLiveSearch *search;
EmpathyRosterModel *model;
};
individual = empathy_roster_contact_get_individual (contact);
return empathy_individual_match_string (individual,
- empathy_live_search_get_text (self->priv->search),
- empathy_live_search_get_words (self->priv->search));
+ tpaw_live_search_get_text (self->priv->search),
+ tpaw_live_search_get_words (self->priv->search));
}
if (self->priv->show_offline)
}
static void
-search_text_notify_cb (EmpathyLiveSearch *search,
+search_text_notify_cb (TpawLiveSearch *search,
GParamSpec *pspec,
EmpathyRosterView *self)
{
void
empathy_roster_view_set_live_search (EmpathyRosterView *self,
- EmpathyLiveSearch *search)
+ TpawLiveSearch *search)
{
if (self->priv->search != NULL)
{
#ifndef __EMPATHY_ROSTER_VIEW_H__
#define __EMPATHY_ROSTER_VIEW_H__
+#include <tp-account-widgets/tpaw-live-search.h>
+
#include "egg-list-box/egg-list-box.h"
-#include "empathy-live-search.h"
#include "empathy-roster-model.h"
G_BEGIN_DECLS
gboolean show);
void empathy_roster_view_set_live_search (EmpathyRosterView *self,
- EmpathyLiveSearch *search);
+ TpawLiveSearch *search);
gboolean empathy_roster_view_is_empty (EmpathyRosterView *self);
#include <gdk/gdkx.h>
#include <glib/gi18n-lib.h>
#include <gio/gdesktopappinfo.h>
+#include <tp-account-widgets/tpaw-live-search.h>
#include "empathy-ft-factory.h"
#include "empathy-images.h"
-#include "empathy-live-search.h"
#include "empathy-utils.h"
#define DEBUG_FLAG EMPATHY_DEBUG_OTHER
return (tp_user_action_time_from_x11 (gtk_get_current_event_time ()));
}
-/* @words = empathy_live_search_strip_utf8_string (@text);
+/* @words = tpaw_live_search_strip_utf8_string (@text);
*
* User has to pass both so we don't have to compute @words ourself each time
* this function is called. */
/* check alias name */
str = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual));
- if (empathy_live_search_match_words (str, words))
+ if (tpaw_live_search_match_words (str, words))
return TRUE;
personas = folks_individual_get_personas (individual);
if (p != NULL)
str = dup_str = g_strndup (str, p - str);
- visible = empathy_live_search_match_words (str, words);
+ visible = tpaw_live_search_match_words (str, words);
g_free (dup_str);
if (visible)
retval = TRUE;
gtk_widget_set_has_tooltip (GTK_WIDGET (self->priv->view), TRUE);
/* Set up search bar */
- self->priv->search_bar = empathy_live_search_new (
+ self->priv->search_bar = tpaw_live_search_new (
GTK_WIDGET (self->priv->view));
empathy_roster_view_set_live_search (self->priv->view,
- EMPATHY_LIVE_SEARCH (self->priv->search_bar));
+ TPAW_LIVE_SEARCH (self->priv->search_bar));
gtk_box_pack_start (GTK_BOX (search_vbox), self->priv->search_bar,
FALSE, TRUE, 0);
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
+#include <tp-account-widgets/tpaw-live-search.h>
-#include "empathy-live-search.h"
#include "test-helper.h"
#define DEBUG_FLAG EMPATHY_DEBUG_TESTS
gboolean match;
gboolean ok;
- match = empathy_live_search_match_string (tests[i].string, tests[i].prefix);
+ match = tpaw_live_search_match_string (tests[i].string, tests[i].prefix);
ok = (match == tests[i].should_match);
DEBUG ("'%s' - '%s' %s: %s", tests[i].string, tests[i].prefix,
$(DISABLE_DEPRECATED)
LDADD = \
+ $(top_builddir)/tp-account-widgets/libtp-account-widgets.la \
$(top_builddir)/libempathy-gtk/libempathy-gtk.la \
$(top_builddir)/libempathy/libempathy.la \
$(EMPATHY_LIBS)
empathy_roster_view_show_offline (EMPATHY_ROSTER_VIEW (view), show_offline);
empathy_roster_view_show_groups (EMPATHY_ROSTER_VIEW (view), show_groups);
- search = empathy_live_search_new (view);
+ search = tpaw_live_search_new (view);
empathy_roster_view_set_live_search (EMPATHY_ROSTER_VIEW (view),
- EMPATHY_LIVE_SEARCH (search));
+ TPAW_LIVE_SEARCH (search));
scrolled = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
empathy_roster_view_show_offline (EMPATHY_ROSTER_VIEW (view), show_offline);
empathy_roster_view_show_groups (EMPATHY_ROSTER_VIEW (view), show_groups);
- search = empathy_live_search_new (view);
+ search = tpaw_live_search_new (view);
empathy_roster_view_set_live_search (EMPATHY_ROSTER_VIEW (view),
- EMPATHY_LIVE_SEARCH (search));
+ TPAW_LIVE_SEARCH (search));
scrolled = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
g_object_unref (mgr);
- search = empathy_live_search_new (view);
+ search = tpaw_live_search_new (view);
empathy_roster_view_set_live_search (EMPATHY_ROSTER_VIEW (view),
- EMPATHY_LIVE_SEARCH (search));
+ TPAW_LIVE_SEARCH (search));
scrolled = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
tpaw-irc-network-manager.c \
tpaw-irc-network.c \
tpaw-irc-server.c \
+ tpaw-live-search.c \
tpaw-utils.c \
totem-subtitle-encoding.c \
$(NULL)
tpaw-irc-network-manager.h \
tpaw-irc-network.h \
tpaw-irc-server.h \
+ tpaw-live-search.h \
tpaw-utils.h \
totem-subtitle-encoding.h \
$(NULL)
#include "tpaw-irc-network-dialog.h"
#include "tpaw-irc-network-manager.h"
-#include "empathy-live-search.h"
+#include "tpaw-live-search.h"
#include "empathy-utils.h"
#define DEBUG_FLAG EMPATHY_DEBUG_ACCOUNT | EMPATHY_DEBUG_IRC
gtk_tree_model_get (model, iter, COL_NETWORK_OBJ, &network, -1);
- visible = empathy_live_search_match (EMPATHY_LIVE_SEARCH (priv->search),
+ visible = tpaw_live_search_match (TPAW_LIVE_SEARCH (priv->search),
tpaw_irc_network_get_name (network));
g_object_unref (network);
}
static void
-search_text_notify_cb (EmpathyLiveSearch *search,
+search_text_notify_cb (TpawLiveSearch *search,
GParamSpec *pspec,
TpawIrcNetworkChooserDialog *self)
{
{
const gchar *text;
- text = empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search));
+ text = tpaw_live_search_get_text (TPAW_LIVE_SEARCH (priv->search));
if (!EMP_STR_EMPTY (text))
{
/* We are doing a search, select the first matching network */
gtk_style_context_set_junction_sides (context, GTK_JUNCTION_TOP);
/* Live search */
- priv->search = empathy_live_search_new (priv->treeview);
+ priv->search = tpaw_live_search_new (priv->treeview);
gtk_box_pack_start (GTK_BOX (vbox), priv->search, FALSE, TRUE, 0);
if (priv->search != NULL)
{
- empathy_live_search_set_hook_widget (EMPATHY_LIVE_SEARCH (priv->search),
+ tpaw_live_search_set_hook_widget (TPAW_LIVE_SEARCH (priv->search),
NULL);
priv->search = NULL;
--- /dev/null
+/*
+ * Copyright (C) 2010 Collabora Ltd.
+ * Copyright (C) 2007-2010 Nokia Corporation.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Felix Kaser <felix.kaser@collabora.co.uk>
+ * Xavier Claessens <xavier.claessens@collabora.co.uk>
+ * Claudio Saavedra <csaavedra@igalia.com>
+ */
+
+#include "config.h"
+#include "tpaw-live-search.h"
+
+#include "empathy-utils.h"
+
+G_DEFINE_TYPE (TpawLiveSearch, tpaw_live_search, GTK_TYPE_HBOX)
+
+#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, TpawLiveSearch)
+
+typedef struct
+{
+ GtkWidget *search_entry;
+ GtkWidget *hook_widget;
+
+ GPtrArray *stripped_words;
+} TpawLiveSearchPriv;
+
+enum
+{
+ PROP_0,
+ PROP_HOOK_WIDGET,
+ PROP_TEXT
+};
+
+enum
+{
+ ACTIVATE,
+ KEYNAV,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static void live_search_hook_widget_destroy_cb (GtkWidget *object,
+ gpointer user_data);
+
+/**
+ * stripped_char:
+ *
+ * Returns a stripped version of @ch, removing any case, accentuation
+ * mark, or any special mark on it.
+ **/
+static gunichar
+stripped_char (gunichar ch)
+{
+ gunichar retval = 0;
+ GUnicodeType utype;
+
+ utype = g_unichar_type (ch);
+
+ switch (utype)
+ {
+ 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);
+ g_unichar_fully_decompose (ch, FALSE, &retval, 1);
+ }
+
+ return retval;
+}
+
+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 *
+tpaw_live_search_strip_utf8_string (const gchar *string)
+{
+ GPtrArray *word_array = NULL;
+ GString *word = NULL;
+ const gchar *p;
+
+ if (EMP_STR_EMPTY (string))
+ return NULL;
+
+ 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)
+ 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;
+ }
+
+ return FALSE;
+}
+
+gboolean
+tpaw_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 (TpawLiveSearch *self,
+ GdkEventKey *event)
+{
+ gboolean ret;
+
+ g_signal_emit (self, signals[KEYNAV], 0, event, &ret);
+ return ret;
+}
+
+static gboolean
+live_search_entry_key_pressed_cb (GtkEntry *entry,
+ GdkEventKey *event,
+ gpointer user_data)
+{
+ TpawLiveSearch *self = TPAW_LIVE_SEARCH (user_data);
+
+ /* if esc key pressed, hide the search */
+ 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_KEY_Up || event->keyval == GDK_KEY_Down
+ || event->keyval == GDK_KEY_Page_Up || event->keyval == GDK_KEY_Page_Down
+ || event->keyval == GDK_KEY_Menu)
+ {
+ 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;
+}
+
+static void
+live_search_text_changed (GtkEntry *entry,
+ gpointer user_data)
+{
+ TpawLiveSearch *self = TPAW_LIVE_SEARCH (user_data);
+ TpawLiveSearchPriv *priv = GET_PRIV (self);
+ const gchar *text;
+
+ text = gtk_entry_get_text (entry);
+
+ if (EMP_STR_EMPTY (text))
+ gtk_widget_hide (GTK_WIDGET (self));
+ else
+ gtk_widget_show (GTK_WIDGET (self));
+
+ if (priv->stripped_words != NULL)
+ g_ptr_array_unref (priv->stripped_words);
+
+ priv->stripped_words = tpaw_live_search_strip_utf8_string (text);
+
+ g_object_notify (G_OBJECT (self), "text");
+}
+
+static void
+live_search_close_pressed (GtkEntry *entry,
+ GtkEntryIconPosition icon_pos,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ TpawLiveSearch *self = TPAW_LIVE_SEARCH (user_data);
+
+ gtk_widget_hide (GTK_WIDGET (self));
+}
+
+static gboolean
+live_search_key_press_event_cb (GtkWidget *widget,
+ GdkEventKey *event,
+ gpointer user_data)
+{
+ TpawLiveSearch *self = TPAW_LIVE_SEARCH (user_data);
+ TpawLiveSearchPriv *priv = GET_PRIV (self);
+ GdkEvent *new_event;
+ gboolean ret;
+
+ /* 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_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_KEY_Control_L ||
+ event->keyval == GDK_KEY_Control_R)
+ return FALSE;
+
+ /* 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 ||
+ event->keyval == GDK_KEY_Menu)
+ 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;
+ }
+
+ /* Don't forward shift keys events as focusing the search entry would
+ * cancel an in-progress editing on a cell renderer (like when renaming a
+ * group). There is no point focusing it anyway as we don't display the
+ * search entry when only a shift key is pressed. */
+ if (event->keyval == GDK_KEY_Shift_L ||
+ event->keyval == GDK_KEY_Shift_R)
+ 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))
+ {
+ gtk_widget_grab_focus (priv->search_entry);
+ gtk_editable_set_position (GTK_EDITABLE (priv->search_entry), -1);
+ }
+
+ /* forward the event to the search entry */
+ new_event = gdk_event_copy ((GdkEvent *) event);
+ ret = gtk_widget_event (priv->search_entry, new_event);
+ gdk_event_free (new_event);
+
+ return ret;
+}
+
+static void
+live_search_entry_activate_cb (GtkEntry *entry,
+ TpawLiveSearch *self)
+{
+ g_signal_emit (self, signals[ACTIVATE], 0);
+}
+
+static void
+live_search_release_hook_widget (TpawLiveSearch *self)
+{
+ TpawLiveSearchPriv *priv = GET_PRIV (self);
+
+ /* remove old handlers if old source was not null */
+ if (priv->hook_widget != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (priv->hook_widget,
+ live_search_key_press_event_cb, self);
+ g_signal_handlers_disconnect_by_func (priv->hook_widget,
+ live_search_hook_widget_destroy_cb, self);
+ g_object_unref (priv->hook_widget);
+ priv->hook_widget = NULL;
+ }
+}
+
+static void
+live_search_hook_widget_destroy_cb (GtkWidget *object,
+ gpointer user_data)
+{
+ TpawLiveSearch *self = TPAW_LIVE_SEARCH (user_data);
+
+ /* unref the hook widget and hide search */
+ gtk_widget_hide (GTK_WIDGET (self));
+ live_search_release_hook_widget (self);
+}
+
+static void
+live_search_dispose (GObject *obj)
+{
+ TpawLiveSearch *self = TPAW_LIVE_SEARCH (obj);
+
+ live_search_release_hook_widget (self);
+
+ if (G_OBJECT_CLASS (tpaw_live_search_parent_class)->dispose != NULL)
+ G_OBJECT_CLASS (tpaw_live_search_parent_class)->dispose (obj);
+}
+
+static void
+live_search_finalize (GObject *obj)
+{
+ TpawLiveSearch *self = TPAW_LIVE_SEARCH (obj);
+ TpawLiveSearchPriv *priv = GET_PRIV (self);
+
+ if (priv->stripped_words != NULL)
+ g_ptr_array_unref (priv->stripped_words);
+
+ if (G_OBJECT_CLASS (tpaw_live_search_parent_class)->finalize != NULL)
+ G_OBJECT_CLASS (tpaw_live_search_parent_class)->finalize (obj);
+}
+
+static void
+live_search_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TpawLiveSearch *self = TPAW_LIVE_SEARCH (object);
+
+ switch (param_id)
+ {
+ case PROP_HOOK_WIDGET:
+ g_value_set_object (value, tpaw_live_search_get_hook_widget (self));
+ break;
+ case PROP_TEXT:
+ g_value_set_string (value, tpaw_live_search_get_text (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+live_search_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TpawLiveSearch *self = TPAW_LIVE_SEARCH (object);
+
+ switch (param_id) {
+ case PROP_HOOK_WIDGET:
+ tpaw_live_search_set_hook_widget (self, g_value_get_object (value));
+ break;
+ case PROP_TEXT:
+ tpaw_live_search_set_text (self, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+static void
+live_search_unmap (GtkWidget *widget)
+{
+ TpawLiveSearch *self = TPAW_LIVE_SEARCH (widget);
+ TpawLiveSearchPriv *priv = GET_PRIV (self);
+
+ GTK_WIDGET_CLASS (tpaw_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), "");
+
+ if (priv->hook_widget != NULL)
+ gtk_widget_grab_focus (priv->hook_widget);
+}
+
+static void
+live_search_show (GtkWidget *widget)
+{
+ TpawLiveSearch *self = TPAW_LIVE_SEARCH (widget);
+ TpawLiveSearchPriv *priv = GET_PRIV (self);
+
+ if (!gtk_widget_has_focus (priv->search_entry))
+ gtk_widget_grab_focus (priv->search_entry);
+
+ GTK_WIDGET_CLASS (tpaw_live_search_parent_class)->show (widget);
+}
+
+static void
+live_search_grab_focus (GtkWidget *widget)
+{
+ TpawLiveSearch *self = TPAW_LIVE_SEARCH (widget);
+ TpawLiveSearchPriv *priv = GET_PRIV (self);
+
+ if (!gtk_widget_has_focus (priv->search_entry))
+ {
+ gtk_widget_grab_focus (priv->search_entry);
+ gtk_editable_set_position (GTK_EDITABLE (priv->search_entry), -1);
+ }
+}
+
+static void
+tpaw_live_search_class_init (TpawLiveSearchClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
+ GParamSpec *param_spec;
+
+ object_class->finalize = live_search_finalize;
+ object_class->dispose = live_search_dispose;
+ object_class->get_property = live_search_get_property;
+ object_class->set_property = live_search_set_property;
+
+ widget_class->unmap = live_search_unmap;
+ widget_class->show = live_search_show;
+ widget_class->grab_focus = live_search_grab_focus;
+
+ signals[ACTIVATE] = g_signal_new ("activate",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 0);
+
+ signals[KEYNAV] = g_signal_new ("key-navigation",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ g_signal_accumulator_true_handled, NULL,
+ 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 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,
+ param_spec);
+
+ param_spec = g_param_spec_string ("text", "Live Search Text",
+ "The text of the live search entry",
+ "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TEXT, param_spec);
+
+ g_type_class_add_private (klass, sizeof (TpawLiveSearchPriv));
+}
+
+static void
+tpaw_live_search_init (TpawLiveSearch *self)
+{
+ TpawLiveSearchPriv *priv =
+ G_TYPE_INSTANCE_GET_PRIVATE ((self), TPAW_TYPE_LIVE_SEARCH,
+ TpawLiveSearchPriv);
+
+ gtk_widget_set_no_show_all (GTK_WIDGET (self), TRUE);
+
+ priv->search_entry = gtk_entry_new ();
+ gtk_entry_set_icon_from_stock (GTK_ENTRY (priv->search_entry),
+ GTK_ENTRY_ICON_SECONDARY, GTK_STOCK_CLOSE);
+ gtk_entry_set_icon_activatable (GTK_ENTRY (priv->search_entry),
+ GTK_ENTRY_ICON_SECONDARY, TRUE);
+ gtk_entry_set_icon_sensitive (GTK_ENTRY (priv->search_entry),
+ GTK_ENTRY_ICON_SECONDARY, TRUE);
+ gtk_widget_show (priv->search_entry);
+
+ gtk_box_pack_start (GTK_BOX (self), priv->search_entry, TRUE, TRUE, 0);
+
+ g_signal_connect (priv->search_entry, "icon_release",
+ G_CALLBACK (live_search_close_pressed), self);
+ g_signal_connect (priv->search_entry, "changed",
+ G_CALLBACK (live_search_text_changed), self);
+ g_signal_connect (priv->search_entry, "key-press-event",
+ G_CALLBACK (live_search_entry_key_pressed_cb), self);
+ g_signal_connect (priv->search_entry, "activate",
+ G_CALLBACK (live_search_entry_activate_cb), self);
+
+ priv->hook_widget = NULL;
+
+ self->priv = priv;
+}
+
+GtkWidget *
+tpaw_live_search_new (GtkWidget *hook)
+{
+ g_return_val_if_fail (hook == NULL || GTK_IS_WIDGET (hook), NULL);
+
+ return g_object_new (TPAW_TYPE_LIVE_SEARCH,
+ "hook-widget", hook,
+ NULL);
+}
+
+/* public methods */
+
+GtkWidget *
+tpaw_live_search_get_hook_widget (TpawLiveSearch *self)
+{
+ TpawLiveSearchPriv *priv = GET_PRIV (self);
+
+ g_return_val_if_fail (TPAW_IS_LIVE_SEARCH (self), NULL);
+
+ return priv->hook_widget;
+}
+
+void
+tpaw_live_search_set_hook_widget (TpawLiveSearch *self,
+ GtkWidget *hook)
+{
+ TpawLiveSearchPriv *priv;
+
+ g_return_if_fail (TPAW_IS_LIVE_SEARCH (self));
+ g_return_if_fail (hook == NULL || GTK_IS_WIDGET (hook));
+
+ priv = GET_PRIV (self);
+
+ /* release the actual widget */
+ live_search_release_hook_widget (self);
+
+ /* connect handlers if new source is not null */
+ if (hook != NULL)
+ {
+ priv->hook_widget = g_object_ref (hook);
+ g_signal_connect (priv->hook_widget, "key-press-event",
+ G_CALLBACK (live_search_key_press_event_cb),
+ self);
+ g_signal_connect (priv->hook_widget, "destroy",
+ G_CALLBACK (live_search_hook_widget_destroy_cb),
+ self);
+ }
+}
+
+const gchar *
+tpaw_live_search_get_text (TpawLiveSearch *self)
+{
+ TpawLiveSearchPriv *priv = GET_PRIV (self);
+
+ g_return_val_if_fail (TPAW_IS_LIVE_SEARCH (self), NULL);
+
+ return gtk_entry_get_text (GTK_ENTRY (priv->search_entry));
+}
+
+void
+tpaw_live_search_set_text (TpawLiveSearch *self,
+ const gchar *text)
+{
+ TpawLiveSearchPriv *priv = GET_PRIV (self);
+
+ g_return_if_fail (TPAW_IS_LIVE_SEARCH (self));
+ g_return_if_fail (text != NULL);
+
+ gtk_entry_set_text (GTK_ENTRY (priv->search_entry), text);
+}
+
+/**
+ * tpaw_live_search_match:
+ * @self: a #TpawLiveSearch
+ * @string: a string where to search, must be valid UTF-8.
+ *
+ * Search if one of the words in @string string starts with the current text
+ * of @self.
+ *
+ * Searching for "aba" in "Abasto" will match, searching in "Moraba" will not,
+ * and searching in "A tool (abacus)" will do.
+ *
+ * The match is not case-sensitive, and regardless of the accentuation marks.
+ *
+ * Returns: %TRUE if a match is found, %FALSE otherwise.
+ *
+ **/
+gboolean
+tpaw_live_search_match (TpawLiveSearch *self,
+ const gchar *string)
+{
+ TpawLiveSearchPriv *priv;
+
+ g_return_val_if_fail (TPAW_IS_LIVE_SEARCH (self), FALSE);
+
+ priv = GET_PRIV (self);
+
+ return tpaw_live_search_match_words (string, priv->stripped_words);
+}
+
+gboolean
+tpaw_live_search_match_string (const gchar *string,
+ const gchar *prefix)
+{
+ GPtrArray *words;
+ gboolean match;
+
+ words = tpaw_live_search_strip_utf8_string (prefix);
+ match = tpaw_live_search_match_words (string, words);
+ if (words != NULL)
+ g_ptr_array_unref (words);
+
+ return match;
+}
+
+GPtrArray *
+tpaw_live_search_get_words (TpawLiveSearch *self)
+{
+ TpawLiveSearchPriv *priv = GET_PRIV (self);
+
+ return priv->stripped_words;
+}
--- /dev/null
+/*
+ * Copyright (C) 2010 Collabora Ltd.
+ * Copyright (C) 2007-2010 Nokia Corporation.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Felix Kaser <felix.kaser@collabora.co.uk>
+ * Xavier Claessens <xavier.claessens@collabora.co.uk>
+ * Claudio Saavedra <csaavedra@igalia.com>
+ */
+
+#ifndef __TPAW_LIVE_SEARCH_H__
+#define __TPAW_LIVE_SEARCH_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define TPAW_TYPE_LIVE_SEARCH (tpaw_live_search_get_type ())
+#define TPAW_LIVE_SEARCH(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TPAW_TYPE_LIVE_SEARCH, TpawLiveSearch))
+#define TPAW_LIVE_SEARCH_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), TPAW_TYPE_LIVE_SEARCH, TpawLiveSearchClass))
+#define TPAW_IS_LIVE_SEARCH(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TPAW_TYPE_LIVE_SEARCH))
+#define TPAW_IS_LIVE_SEARCH_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), TPAW_TYPE_LIVE_SEARCH))
+#define TPAW_LIVE_SEARCH_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TPAW_TYPE_LIVE_SEARCH, TpawLiveSearchClass))
+
+typedef struct _TpawLiveSearch TpawLiveSearch;
+typedef struct _TpawLiveSearchClass TpawLiveSearchClass;
+
+struct _TpawLiveSearch {
+ GtkHBox parent;
+
+ /*<private>*/
+ gpointer priv;
+};
+
+struct _TpawLiveSearchClass {
+ GtkHBoxClass parent_class;
+};
+
+GType tpaw_live_search_get_type (void) G_GNUC_CONST;
+GtkWidget *tpaw_live_search_new (GtkWidget *hook);
+
+GtkWidget *tpaw_live_search_get_hook_widget (TpawLiveSearch *self);
+void tpaw_live_search_set_hook_widget (TpawLiveSearch *self,
+ GtkWidget *hook);
+
+const gchar *tpaw_live_search_get_text (TpawLiveSearch *self);
+void tpaw_live_search_set_text (TpawLiveSearch *self,
+ const gchar *text);
+
+gboolean tpaw_live_search_match (TpawLiveSearch *self,
+ const gchar *string);
+
+GPtrArray * tpaw_live_search_strip_utf8_string (const gchar *string);
+
+gboolean tpaw_live_search_match_words (const gchar *string,
+ GPtrArray *words);
+
+GPtrArray * tpaw_live_search_get_words (TpawLiveSearch *self);
+
+/* Made public for unit tests */
+gboolean tpaw_live_search_match_string (const gchar *string,
+ const gchar *prefix);
+
+G_END_DECLS
+
+#endif /* __TPAW_LIVE_SEARCH_H__ */