2 * Copyright (C) 2010 Collabora Ltd.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 * Authors: Felix Kaser <felix.kaser@collabora.co.uk>
25 #include <gdk/gdkkeysyms.h>
27 #include <libempathy/empathy-utils.h>
29 #include "empathy-live-search.h"
31 G_DEFINE_TYPE (EmpathyLiveSearch, empathy_live_search, GTK_TYPE_HBOX)
33 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyLiveSearch)
37 GtkWidget *search_entry;
38 GtkWidget *hook_widget;
40 gunichar *text_stripped;
41 } EmpathyLiveSearchPriv;
50 static void live_search_hook_widget_destroy_cb (GtkObject *object,
56 * Returns a stripped version of @ch, removing any case, accentuation
57 * mark, or any special mark on it.
60 stripped_char (gunichar ch)
67 utype = g_unichar_type (ch);
71 case G_UNICODE_CONTROL:
72 case G_UNICODE_FORMAT:
73 case G_UNICODE_UNASSIGNED:
74 case G_UNICODE_COMBINING_MARK:
78 ch = g_unichar_tolower (ch);
79 decomp = g_unicode_canonical_decomposition (ch, &dlen);
91 strip_utf8_string (const gchar *string)
97 if (EMP_STR_EMPTY (string))
100 ret = g_malloc (sizeof (gunichar) * (strlen (string) + 1));
103 for (p = string; *p != '\0'; p = g_utf8_next_char (p))
107 sc = stripped_char (g_utf8_get_char (p));
118 live_search_entry_key_pressed_cb (GtkEntry *entry,
122 EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (user_data);
124 /* if esc key pressed, hide the search */
125 if (event->keyval == GDK_Escape)
127 gtk_widget_hide (GTK_WIDGET (self));
135 live_search_text_changed (GtkEntry *entry,
138 EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (user_data);
139 EmpathyLiveSearchPriv *priv = GET_PRIV (self);
142 text = gtk_entry_get_text (entry);
144 if (EMP_STR_EMPTY (text))
145 gtk_widget_hide (GTK_WIDGET (self));
147 gtk_widget_show (GTK_WIDGET (self));
149 g_free (priv->text_stripped);
150 priv->text_stripped = strip_utf8_string (text);
151 g_object_notify (G_OBJECT (self), "text");
155 live_search_close_pressed (GtkEntry *entry,
156 GtkEntryIconPosition icon_pos,
160 EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (user_data);
162 gtk_widget_hide (GTK_WIDGET (self));
166 live_search_key_press_event_cb (GtkWidget *widget,
170 EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (user_data);
171 EmpathyLiveSearchPriv *priv = GET_PRIV (self);
175 /* dont forward this event to the entry, else the event is consumed by the
176 * entry and does not close the window */
177 if (!gtk_widget_get_visible (GTK_WIDGET (self)) &&
178 event->keyval == GDK_Escape)
181 /* do not show the search if CTRL and/or ALT are pressed with a key
182 * this is needed, because otherwise the CTRL + F accel would not work,
183 * because the entry consumes it */
184 if (event->state & (GDK_MOD1_MASK | GDK_CONTROL_MASK) ||
185 event->keyval == GDK_Control_L ||
186 event->keyval == GDK_Control_R)
189 /* realize the widget if it is not realized yet */
190 gtk_widget_realize (priv->search_entry);
191 if (!gtk_widget_has_focus (priv->search_entry))
193 gtk_widget_grab_focus (priv->search_entry);
194 gtk_editable_set_position (GTK_EDITABLE (priv->search_entry), -1);
197 /* forward the event to the search entry */
198 new_event = gdk_event_copy ((GdkEvent *) event);
199 ret = gtk_widget_event (priv->search_entry, new_event);
200 gdk_event_free (new_event);
206 live_search_release_hook_widget (EmpathyLiveSearch *self)
208 EmpathyLiveSearchPriv *priv = GET_PRIV (self);
210 /* remove old handlers if old source was not null */
211 if (priv->hook_widget != NULL)
213 g_signal_handlers_disconnect_by_func (priv->hook_widget,
214 live_search_key_press_event_cb, self);
215 g_signal_handlers_disconnect_by_func (priv->hook_widget,
216 live_search_hook_widget_destroy_cb, self);
217 g_object_unref (priv->hook_widget);
218 priv->hook_widget = NULL;
223 live_search_hook_widget_destroy_cb (GtkObject *object,
226 EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (user_data);
228 /* unref the hook widget and hide search */
229 live_search_release_hook_widget (self);
230 gtk_widget_hide (GTK_WIDGET (self));
234 live_search_dispose (GObject *obj)
236 EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (obj);
238 live_search_release_hook_widget (self);
240 if (G_OBJECT_CLASS (empathy_live_search_parent_class)->dispose != NULL)
241 G_OBJECT_CLASS (empathy_live_search_parent_class)->dispose (obj);
245 live_search_finalize (GObject *obj)
247 EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (obj);
248 EmpathyLiveSearchPriv *priv = GET_PRIV (self);
250 g_free (priv->text_stripped);
252 if (G_OBJECT_CLASS (empathy_live_search_parent_class)->finalize != NULL)
253 G_OBJECT_CLASS (empathy_live_search_parent_class)->finalize (obj);
257 live_search_get_property (GObject *object,
262 EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (object);
266 case PROP_HOOK_WIDGET:
267 g_value_set_object (value, empathy_live_search_get_hook_widget (self));
270 g_value_set_string (value, empathy_live_search_get_text (self));
273 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
279 live_search_set_property (GObject *object,
284 EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (object);
287 case PROP_HOOK_WIDGET:
288 empathy_live_search_set_hook_widget (self, g_value_get_object (value));
291 empathy_live_search_set_text (self, g_value_get_string (value));
294 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
300 live_search_hide (GtkWidget *widget)
302 EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (widget);
303 EmpathyLiveSearchPriv *priv = GET_PRIV (self);
305 GTK_WIDGET_CLASS (empathy_live_search_parent_class)->hide (widget);
307 gtk_entry_set_text (GTK_ENTRY (priv->search_entry), "");
308 gtk_widget_grab_focus (priv->hook_widget);
312 live_search_show (GtkWidget *widget)
314 EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (widget);
315 EmpathyLiveSearchPriv *priv = GET_PRIV (self);
317 if (!gtk_widget_has_focus (priv->search_entry))
318 gtk_widget_grab_focus (priv->search_entry);
320 GTK_WIDGET_CLASS (empathy_live_search_parent_class)->show (widget);
324 live_search_grab_focus (GtkWidget *widget)
326 EmpathyLiveSearch *self = EMPATHY_LIVE_SEARCH (widget);
327 EmpathyLiveSearchPriv *priv = GET_PRIV (self);
329 if (!gtk_widget_has_focus (priv->search_entry))
330 gtk_widget_grab_focus (priv->search_entry);
334 empathy_live_search_class_init (EmpathyLiveSearchClass *klass)
336 GObjectClass *object_class = (GObjectClass *) klass;
337 GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
338 GParamSpec *param_spec;
340 object_class->finalize = live_search_finalize;
341 object_class->dispose = live_search_dispose;
342 object_class->get_property = live_search_get_property;
343 object_class->set_property = live_search_set_property;
345 widget_class->hide = live_search_hide;
346 widget_class->show = live_search_show;
347 widget_class->grab_focus = live_search_grab_focus;
349 param_spec = g_param_spec_object ("hook-widget", "Live Searchs Hook Widget",
350 "The live search catches key-press-events on this widget",
351 GTK_TYPE_WIDGET, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
352 g_object_class_install_property (object_class, PROP_HOOK_WIDGET,
355 param_spec = g_param_spec_string ("text", "Live Search Text",
356 "The text of the live search entry",
357 "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
358 g_object_class_install_property (object_class, PROP_TEXT, param_spec);
360 g_type_class_add_private (klass, sizeof (EmpathyLiveSearchPriv));
364 empathy_live_search_init (EmpathyLiveSearch *self)
366 EmpathyLiveSearchPriv *priv =
367 G_TYPE_INSTANCE_GET_PRIVATE ((self), EMPATHY_TYPE_LIVE_SEARCH,
368 EmpathyLiveSearchPriv);
370 gtk_widget_set_no_show_all (GTK_WIDGET (self), TRUE);
372 priv->search_entry = gtk_entry_new ();
373 gtk_entry_set_icon_from_stock (GTK_ENTRY (priv->search_entry),
374 GTK_ENTRY_ICON_SECONDARY, GTK_STOCK_CLOSE);
375 gtk_entry_set_icon_activatable (GTK_ENTRY (priv->search_entry),
376 GTK_ENTRY_ICON_SECONDARY, TRUE);
377 gtk_entry_set_icon_sensitive (GTK_ENTRY (priv->search_entry),
378 GTK_ENTRY_ICON_SECONDARY, TRUE);
379 gtk_widget_show (priv->search_entry);
381 gtk_box_pack_start (GTK_BOX (self), priv->search_entry, TRUE, TRUE, 0);
383 g_signal_connect (priv->search_entry, "icon_release",
384 G_CALLBACK (live_search_close_pressed), self);
385 g_signal_connect (priv->search_entry, "changed",
386 G_CALLBACK (live_search_text_changed), self);
387 g_signal_connect (priv->search_entry, "key-press-event",
388 G_CALLBACK (live_search_entry_key_pressed_cb), self);
390 priv->hook_widget = NULL;
396 empathy_live_search_new (GtkWidget *hook)
398 g_return_val_if_fail (hook == NULL || GTK_IS_WIDGET (hook), NULL);
400 return g_object_new (EMPATHY_TYPE_LIVE_SEARCH,
408 empathy_live_search_get_hook_widget (EmpathyLiveSearch *self)
410 EmpathyLiveSearchPriv *priv = GET_PRIV (self);
412 g_return_val_if_fail (EMPATHY_IS_LIVE_SEARCH (self), NULL);
414 return priv->hook_widget;
418 empathy_live_search_set_hook_widget (EmpathyLiveSearch *self,
421 EmpathyLiveSearchPriv *priv;
423 g_return_if_fail (EMPATHY_IS_LIVE_SEARCH (self));
424 g_return_if_fail (hook == NULL || GTK_IS_WIDGET (hook));
426 priv = GET_PRIV (self);
428 /* release the actual widget */
429 live_search_release_hook_widget (self);
431 /* connect handlers if new source is not null */
434 priv->hook_widget = g_object_ref (hook);
435 g_signal_connect (priv->hook_widget, "key-press-event",
436 G_CALLBACK (live_search_key_press_event_cb),
438 g_signal_connect (priv->hook_widget, "destroy",
439 G_CALLBACK (live_search_hook_widget_destroy_cb),
445 empathy_live_search_get_text (EmpathyLiveSearch *self)
447 EmpathyLiveSearchPriv *priv = GET_PRIV (self);
449 g_return_val_if_fail (EMPATHY_IS_LIVE_SEARCH (self), NULL);
451 return gtk_entry_get_text (GTK_ENTRY (priv->search_entry));
455 empathy_live_search_set_text (EmpathyLiveSearch *self,
458 EmpathyLiveSearchPriv *priv = GET_PRIV (self);
460 g_return_if_fail (EMPATHY_IS_LIVE_SEARCH (self));
461 g_return_if_fail (text != NULL);
463 gtk_entry_set_text (GTK_ENTRY (priv->search_entry), text);
467 live_search_match_string (const gchar *string,
468 const gunichar *prefix)
472 if (prefix == NULL || prefix[0] == 0)
475 if (EMP_STR_EMPTY (string))
478 for (p = string; *p != '\0'; p = g_utf8_next_char (p))
482 /* Search the start of the word (skip non alpha-num chars) */
483 while (*p != '\0' && !g_unichar_isalnum (g_utf8_get_char (p)))
484 p = g_utf8_next_char (p);
486 /* Check if this word match prefix */
491 sc = stripped_char (g_utf8_get_char (p));
494 /* If the char does not match, stop */
498 /* The char matched. If it was the last of prefix, stop */
499 if (prefix[++i] == 0)
503 p = g_utf8_next_char (p);
506 /* This word didn't match, go to next one (skip alpha-num chars) */
507 while (*p != '\0' && g_unichar_isalnum (g_utf8_get_char (p)))
508 p = g_utf8_next_char (p);
518 * empathy_live_search_match:
519 * @self: a #EmpathyLiveSearch
520 * @string: a string where to search, must be valid UTF-8.
522 * Search if one of the words in @string string starts with the current text
525 * Searching for "aba" in "Abasto" will match, searching in "Moraba" will not,
526 * and searching in "A tool (abacus)" will do.
528 * The match is not case-sensitive, and regardless of the accentuation marks.
530 * Returns: %TRUE if a match is found, %FALSE otherwise.
534 empathy_live_search_match (EmpathyLiveSearch *self,
537 EmpathyLiveSearchPriv *priv;
539 g_return_val_if_fail (EMPATHY_IS_LIVE_SEARCH (self), FALSE);
541 priv = GET_PRIV (self);
543 return live_search_match_string (string, priv->text_stripped);
547 empathy_live_search_match_string (const gchar *string,
553 stripped = strip_utf8_string (prefix);
554 match = live_search_match_string (string, stripped);