|
Packit |
b00eeb |
/*
|
|
Packit |
b00eeb |
* Copyright (C) 2011 Collabora Ltd.
|
|
Packit |
b00eeb |
* Copyright (C) 2010 Collabora Ltd.
|
|
Packit |
b00eeb |
* Copyright (C) 2007-2010 Nokia Corporation.
|
|
Packit |
b00eeb |
*
|
|
Packit |
b00eeb |
* This library is free software; you can redistribute it and/or
|
|
Packit |
b00eeb |
* modify it under the terms of the GNU Lesser General Public
|
|
Packit |
b00eeb |
* License as published by the Free Software Foundation; either
|
|
Packit |
b00eeb |
* version 2.1 of the License, or (at your option) any later version.
|
|
Packit |
b00eeb |
*
|
|
Packit |
b00eeb |
* This library is distributed in the hope that it will be useful,
|
|
Packit |
b00eeb |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
Packit |
b00eeb |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Packit |
b00eeb |
* Lesser General Public License for more details.
|
|
Packit |
b00eeb |
*
|
|
Packit |
b00eeb |
* You should have received a copy of the GNU Lesser General Public
|
|
Packit |
b00eeb |
* License along with this library; if not, write to the Free Software
|
|
Packit |
b00eeb |
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
Packit |
b00eeb |
*
|
|
Packit |
b00eeb |
* Authors: Felix Kaser <felix.kaser@collabora.co.uk>
|
|
Packit |
b00eeb |
* Xavier Claessens <xavier.claessens@collabora.co.uk>
|
|
Packit |
b00eeb |
* Claudio Saavedra <csaavedra@igalia.com>
|
|
Packit |
b00eeb |
* Stef Walter <stefw@collabora.co.uk>
|
|
Packit |
b00eeb |
*/
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
/* Code borrowed from Empathy */
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
#include "config.h"
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
#include "gcr-live-search.h"
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
#include "gcr/gcr-marshal.h"
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
#include <gtk/gtk.h>
|
|
Packit |
b00eeb |
#include <gdk/gdkkeysyms.h>
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
#include <string.h>
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
G_DEFINE_TYPE (GcrLiveSearch, _gcr_live_search, GTK_TYPE_BOX)
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
struct _GcrLiveSearchPrivate {
|
|
Packit |
b00eeb |
GtkWidget *search_entry;
|
|
Packit |
b00eeb |
GtkWidget *hook_widget;
|
|
Packit |
b00eeb |
GPtrArray *stripped_words;
|
|
Packit |
b00eeb |
};
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
enum {
|
|
Packit |
b00eeb |
PROP_0,
|
|
Packit |
b00eeb |
PROP_HOOK_WIDGET,
|
|
Packit |
b00eeb |
PROP_TEXT
|
|
Packit |
b00eeb |
};
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
enum {
|
|
Packit |
b00eeb |
ACTIVATE,
|
|
Packit |
b00eeb |
KEYNAV,
|
|
Packit |
b00eeb |
LAST_SIGNAL
|
|
Packit |
b00eeb |
};
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
static guint signals[LAST_SIGNAL];
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
static void on_hook_widget_destroy (GtkWidget *object, gpointer user_data);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
static gunichar
|
|
Packit |
b00eeb |
stripped_char (gunichar ch)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
gunichar retval = 0;
|
|
Packit |
b00eeb |
GUnicodeType utype;
|
|
Packit |
b00eeb |
gunichar decomp[4];
|
|
Packit |
b00eeb |
gsize dlen;
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
utype = g_unichar_type (ch);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
switch (utype) {
|
|
Packit |
b00eeb |
case G_UNICODE_CONTROL:
|
|
Packit |
b00eeb |
case G_UNICODE_FORMAT:
|
|
Packit |
b00eeb |
case G_UNICODE_UNASSIGNED:
|
|
Packit |
b00eeb |
case G_UNICODE_NON_SPACING_MARK:
|
|
Packit |
b00eeb |
case G_UNICODE_SPACING_MARK:
|
|
Packit |
b00eeb |
case G_UNICODE_ENCLOSING_MARK:
|
|
Packit |
b00eeb |
/* Ignore those */
|
|
Packit |
b00eeb |
break;
|
|
Packit |
b00eeb |
case G_UNICODE_PRIVATE_USE:
|
|
Packit |
b00eeb |
case G_UNICODE_SURROGATE:
|
|
Packit |
b00eeb |
case G_UNICODE_LOWERCASE_LETTER:
|
|
Packit |
b00eeb |
case G_UNICODE_MODIFIER_LETTER:
|
|
Packit |
b00eeb |
case G_UNICODE_OTHER_LETTER:
|
|
Packit |
b00eeb |
case G_UNICODE_TITLECASE_LETTER:
|
|
Packit |
b00eeb |
case G_UNICODE_UPPERCASE_LETTER:
|
|
Packit |
b00eeb |
case G_UNICODE_DECIMAL_NUMBER:
|
|
Packit |
b00eeb |
case G_UNICODE_LETTER_NUMBER:
|
|
Packit |
b00eeb |
case G_UNICODE_OTHER_NUMBER:
|
|
Packit |
b00eeb |
case G_UNICODE_CONNECT_PUNCTUATION:
|
|
Packit |
b00eeb |
case G_UNICODE_DASH_PUNCTUATION:
|
|
Packit |
b00eeb |
case G_UNICODE_CLOSE_PUNCTUATION:
|
|
Packit |
b00eeb |
case G_UNICODE_FINAL_PUNCTUATION:
|
|
Packit |
b00eeb |
case G_UNICODE_INITIAL_PUNCTUATION:
|
|
Packit |
b00eeb |
case G_UNICODE_OTHER_PUNCTUATION:
|
|
Packit |
b00eeb |
case G_UNICODE_OPEN_PUNCTUATION:
|
|
Packit |
b00eeb |
case G_UNICODE_CURRENCY_SYMBOL:
|
|
Packit |
b00eeb |
case G_UNICODE_MODIFIER_SYMBOL:
|
|
Packit |
b00eeb |
case G_UNICODE_MATH_SYMBOL:
|
|
Packit |
b00eeb |
case G_UNICODE_OTHER_SYMBOL:
|
|
Packit |
b00eeb |
case G_UNICODE_LINE_SEPARATOR:
|
|
Packit |
b00eeb |
case G_UNICODE_PARAGRAPH_SEPARATOR:
|
|
Packit |
b00eeb |
case G_UNICODE_SPACE_SEPARATOR:
|
|
Packit |
b00eeb |
default:
|
|
Packit |
b00eeb |
ch = g_unichar_tolower (ch);
|
|
Packit |
b00eeb |
dlen = g_unichar_fully_decompose (ch, FALSE, decomp, 4);
|
|
Packit |
b00eeb |
if (dlen > 0)
|
|
Packit |
b00eeb |
retval = decomp[0];
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
return retval;
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
static void
|
|
Packit |
b00eeb |
append_word (GPtrArray **word_array, GString **word)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
if (*word != NULL) {
|
|
Packit |
b00eeb |
if (*word_array == NULL)
|
|
Packit |
b00eeb |
*word_array = g_ptr_array_new_with_free_func (g_free);
|
|
Packit |
b00eeb |
g_ptr_array_add (*word_array, g_string_free (*word, FALSE));
|
|
Packit |
b00eeb |
*word = NULL;
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
GPtrArray *
|
|
Packit |
b00eeb |
_gcr_live_search_strip_utf8_string (const gchar *string)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
GPtrArray *word_array = NULL;
|
|
Packit |
b00eeb |
GString *word = NULL;
|
|
Packit |
b00eeb |
const gchar *p;
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
if (string == NULL || *string == '\0')
|
|
Packit |
b00eeb |
return NULL;
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
for (p = string; *p != '\0'; p = g_utf8_next_char (p)) {
|
|
Packit |
b00eeb |
gunichar sc;
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
/* Make the char lower-case, remove its accentuation marks, and ignore it
|
|
Packit |
b00eeb |
* if it is just unicode marks */
|
|
Packit |
b00eeb |
sc = stripped_char (g_utf8_get_char (p));
|
|
Packit |
b00eeb |
if (sc == 0)
|
|
Packit |
b00eeb |
continue;
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
/* If it is not alpha-num, it is separator between words */
|
|
Packit |
b00eeb |
if (!g_unichar_isalnum (sc)) {
|
|
Packit |
b00eeb |
append_word (&word_array, &word);
|
|
Packit |
b00eeb |
continue;
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
/* It is alpha-num, append this char to current word, or start new word */
|
|
Packit |
b00eeb |
if (word == NULL)
|
|
Packit |
b00eeb |
word = g_string_new (NULL);
|
|
Packit |
b00eeb |
g_string_append_unichar (word, sc);
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
append_word (&word_array, &word);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
return word_array;
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
static gboolean
|
|
Packit |
b00eeb |
live_search_match_prefix (const gchar *string, const gchar *prefix)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
const gchar *p;
|
|
Packit |
b00eeb |
const gchar *prefix_p;
|
|
Packit |
b00eeb |
gboolean next_word = FALSE;
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
if (prefix == NULL || prefix[0] == 0)
|
|
Packit |
b00eeb |
return TRUE;
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
if (string == NULL || *string == '\0')
|
|
Packit |
b00eeb |
return FALSE;
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
prefix_p = prefix;
|
|
Packit |
b00eeb |
for (p = string; *p != '\0'; p = g_utf8_next_char (p)) {
|
|
Packit |
b00eeb |
gunichar sc;
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
/* Make the char lower-case, remove its accentuation marks, and ignore it
|
|
Packit |
b00eeb |
* if it is just unicode marks */
|
|
Packit |
b00eeb |
sc = stripped_char (g_utf8_get_char (p));
|
|
Packit |
b00eeb |
if (sc == 0)
|
|
Packit |
b00eeb |
continue;
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
/* If we want to go to next word, ignore alpha-num chars */
|
|
Packit |
b00eeb |
if (next_word && g_unichar_isalnum (sc))
|
|
Packit |
b00eeb |
continue;
|
|
Packit |
b00eeb |
next_word = FALSE;
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
/* Ignore word separators */
|
|
Packit |
b00eeb |
if (!g_unichar_isalnum (sc))
|
|
Packit |
b00eeb |
continue;
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
/* If this char does not match prefix_p, go to next word and start again
|
|
Packit |
b00eeb |
* from the beginning of prefix */
|
|
Packit |
b00eeb |
if (sc != g_utf8_get_char (prefix_p)) {
|
|
Packit |
b00eeb |
next_word = TRUE;
|
|
Packit |
b00eeb |
prefix_p = prefix;
|
|
Packit |
b00eeb |
continue;
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
/* prefix_p match, verify to next char. If this was the last of prefix,
|
|
Packit |
b00eeb |
* it means it completely machted and we are done. */
|
|
Packit |
b00eeb |
prefix_p = g_utf8_next_char (prefix_p);
|
|
Packit |
b00eeb |
if (*prefix_p == '\0')
|
|
Packit |
b00eeb |
return TRUE;
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
return FALSE;
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
gboolean
|
|
Packit |
b00eeb |
_gcr_live_search_match_words (const gchar *string, GPtrArray *words)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
guint i;
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
if (words == NULL)
|
|
Packit |
b00eeb |
return TRUE;
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
for (i = 0; i < words->len; i++)
|
|
Packit |
b00eeb |
if (!live_search_match_prefix (string, g_ptr_array_index (words, i)))
|
|
Packit |
b00eeb |
return FALSE;
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
return TRUE;
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
static gboolean
|
|
Packit |
b00eeb |
fire_key_navigation_sig (GcrLiveSearch *self, GdkEventKey *event)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
gboolean ret;
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
g_signal_emit (self, signals[KEYNAV], 0, event, &ret;;
|
|
Packit |
b00eeb |
return ret;
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
static gboolean
|
|
Packit |
b00eeb |
on_search_entry_key_pressed (GtkEntry *entry, GdkEventKey *event, gpointer user_data)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
GcrLiveSearch *self = GCR_LIVE_SEARCH (user_data);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
/* if esc key pressed, hide the search */
|
|
Packit |
b00eeb |
if (event->keyval == GDK_KEY_Escape) {
|
|
Packit |
b00eeb |
gtk_widget_hide (GTK_WIDGET (self));
|
|
Packit |
b00eeb |
return TRUE;
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
/* emit key navigation signal, so other widgets can respond to it properly */
|
|
Packit |
b00eeb |
if (event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_Down
|
|
Packit |
b00eeb |
|| event->keyval == GDK_KEY_Page_Up || event->keyval == GDK_KEY_Page_Down) {
|
|
Packit |
b00eeb |
return fire_key_navigation_sig (self, event);
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
if (event->keyval == GDK_KEY_Home || event->keyval == GDK_KEY_End ||
|
|
Packit |
b00eeb |
event->keyval == GDK_KEY_space) {
|
|
Packit |
b00eeb |
/* If the live search is visible, the entry should catch the Home/End
|
|
Packit |
b00eeb |
* and space events */
|
|
Packit |
b00eeb |
if (!gtk_widget_get_visible (GTK_WIDGET (self))) {
|
|
Packit |
b00eeb |
return fire_key_navigation_sig (self, event);
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
return FALSE;
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
static void
|
|
Packit |
b00eeb |
on_search_entry_text_changed (GtkEntry *entry, gpointer user_data)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
GcrLiveSearch *self = GCR_LIVE_SEARCH (user_data);
|
|
Packit |
b00eeb |
const gchar *text;
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
text = gtk_entry_get_text (entry);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
if (text == NULL || *text == '\0')
|
|
Packit |
b00eeb |
gtk_widget_hide (GTK_WIDGET (self));
|
|
Packit |
b00eeb |
else
|
|
Packit |
b00eeb |
gtk_widget_show (GTK_WIDGET (self));
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
if (self->pv->stripped_words != NULL)
|
|
Packit |
b00eeb |
g_ptr_array_unref (self->pv->stripped_words);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
self->pv->stripped_words = _gcr_live_search_strip_utf8_string (text);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
g_object_notify (G_OBJECT (self), "text");
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
static void
|
|
Packit |
b00eeb |
on_search_entry_close_pressed (GtkEntry *entry, GtkEntryIconPosition icon_pos,
|
|
Packit |
b00eeb |
GdkEvent *event, gpointer user_data)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
GcrLiveSearch *self = GCR_LIVE_SEARCH (user_data);
|
|
Packit |
b00eeb |
gtk_widget_hide (GTK_WIDGET (self));
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
static gboolean
|
|
Packit |
b00eeb |
on_hook_widget_key_press_event (GtkWidget *widget, GdkEventKey *event,
|
|
Packit |
b00eeb |
gpointer user_data)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
GcrLiveSearch *self = GCR_LIVE_SEARCH (user_data);
|
|
Packit |
b00eeb |
GdkEvent *new_event;
|
|
Packit |
b00eeb |
gboolean ret;
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
/* dont forward this event to the entry, else the event is consumed by the
|
|
Packit |
b00eeb |
* entry and does not close the window */
|
|
Packit |
b00eeb |
if (!gtk_widget_get_visible (GTK_WIDGET (self)) &&
|
|
Packit |
b00eeb |
event->keyval == GDK_KEY_Escape)
|
|
Packit |
b00eeb |
return FALSE;
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
/* do not show the search if CTRL and/or ALT are pressed with a key
|
|
Packit |
b00eeb |
* this is needed, because otherwise the CTRL + F accel would not work,
|
|
Packit |
b00eeb |
* because the entry consumes it */
|
|
Packit |
b00eeb |
if (event->state & (GDK_MOD1_MASK | GDK_CONTROL_MASK) ||
|
|
Packit |
b00eeb |
event->keyval == GDK_KEY_Control_L ||
|
|
Packit |
b00eeb |
event->keyval == GDK_KEY_Control_R)
|
|
Packit |
b00eeb |
return FALSE;
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
/* dont forward the up/down and Page Up/Down arrow keys to the entry,
|
|
Packit |
b00eeb |
* they are needed for navigation in the treeview and are not needed in
|
|
Packit |
b00eeb |
* the search entry */
|
|
Packit |
b00eeb |
if (event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_Down ||
|
|
Packit |
b00eeb |
event->keyval == GDK_KEY_Page_Up || event->keyval == GDK_KEY_Page_Down)
|
|
Packit |
b00eeb |
return FALSE;
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
if (event->keyval == GDK_KEY_Home || event->keyval == GDK_KEY_End ||
|
|
Packit |
b00eeb |
event->keyval == GDK_KEY_space) {
|
|
Packit |
b00eeb |
/* Home/End and space keys have to be forwarded to the entry only if
|
|
Packit |
b00eeb |
* the live search is visible (to move the cursor inside the entry). */
|
|
Packit |
b00eeb |
if (!gtk_widget_get_visible (GTK_WIDGET (self)))
|
|
Packit |
b00eeb |
return FALSE;
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
/* realize the widget if it is not realized yet */
|
|
Packit |
b00eeb |
gtk_widget_realize (self->pv->search_entry);
|
|
Packit |
b00eeb |
if (!gtk_widget_has_focus (self->pv->search_entry)) {
|
|
Packit |
b00eeb |
gtk_widget_grab_focus (self->pv->search_entry);
|
|
Packit |
b00eeb |
gtk_editable_set_position (GTK_EDITABLE (self->pv->search_entry), -1);
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
/* forward the event to the search entry */
|
|
Packit |
b00eeb |
new_event = gdk_event_copy ((GdkEvent *) event);
|
|
Packit |
b00eeb |
ret = gtk_widget_event (self->pv->search_entry, new_event);
|
|
Packit |
b00eeb |
gdk_event_free (new_event);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
return ret;
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
static void
|
|
Packit |
b00eeb |
on_search_entry_activate (GtkEntry *entry, GcrLiveSearch *self)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
g_signal_emit (self, signals[ACTIVATE], 0);
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
static void
|
|
Packit |
b00eeb |
live_search_release_hook_widget (GcrLiveSearch *self)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
/* remove old handlers if old source was not null */
|
|
Packit |
b00eeb |
if (self->pv->hook_widget != NULL) {
|
|
Packit |
b00eeb |
g_signal_handlers_disconnect_by_func (self->pv->hook_widget,
|
|
Packit |
b00eeb |
on_hook_widget_key_press_event, self);
|
|
Packit |
b00eeb |
g_signal_handlers_disconnect_by_func (self->pv->hook_widget,
|
|
Packit |
b00eeb |
on_hook_widget_destroy, self);
|
|
Packit |
b00eeb |
g_object_unref (self->pv->hook_widget);
|
|
Packit |
b00eeb |
self->pv->hook_widget = NULL;
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
static void
|
|
Packit |
b00eeb |
on_hook_widget_destroy (GtkWidget *object, gpointer user_data)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
GcrLiveSearch *self = GCR_LIVE_SEARCH (user_data);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
/* unref the hook widget and hide search */
|
|
Packit |
b00eeb |
gtk_widget_hide (GTK_WIDGET (self));
|
|
Packit |
b00eeb |
live_search_release_hook_widget (self);
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
static void
|
|
Packit |
b00eeb |
live_search_dispose (GObject *obj)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
GcrLiveSearch *self = GCR_LIVE_SEARCH (obj);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
live_search_release_hook_widget (self);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
G_OBJECT_CLASS (_gcr_live_search_parent_class)->dispose (obj);
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
static void
|
|
Packit |
b00eeb |
live_search_finalize (GObject *obj)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
GcrLiveSearch *self = GCR_LIVE_SEARCH (obj);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
if (self->pv->stripped_words != NULL)
|
|
Packit |
b00eeb |
g_ptr_array_unref (self->pv->stripped_words);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
G_OBJECT_CLASS (_gcr_live_search_parent_class)->finalize (obj);
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
static void
|
|
Packit |
b00eeb |
live_search_get_property (GObject *object, guint param_id,
|
|
Packit |
b00eeb |
GValue *value, GParamSpec *pspec)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
GcrLiveSearch *self = GCR_LIVE_SEARCH (object);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
switch (param_id) {
|
|
Packit |
b00eeb |
case PROP_HOOK_WIDGET:
|
|
Packit |
b00eeb |
g_value_set_object (value, _gcr_live_search_get_hook_widget (self));
|
|
Packit |
b00eeb |
break;
|
|
Packit |
b00eeb |
case PROP_TEXT:
|
|
Packit |
b00eeb |
g_value_set_string (value, _gcr_live_search_get_text (self));
|
|
Packit |
b00eeb |
break;
|
|
Packit |
b00eeb |
default:
|
|
Packit |
b00eeb |
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
|
|
Packit |
b00eeb |
break;
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
static void
|
|
Packit |
b00eeb |
live_search_set_property (GObject *object, guint param_id,
|
|
Packit |
b00eeb |
const GValue *value, GParamSpec *pspec)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
GcrLiveSearch *self = GCR_LIVE_SEARCH (object);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
switch (param_id) {
|
|
Packit |
b00eeb |
case PROP_HOOK_WIDGET:
|
|
Packit |
b00eeb |
_gcr_live_search_set_hook_widget (self, g_value_get_object (value));
|
|
Packit |
b00eeb |
break;
|
|
Packit |
b00eeb |
case PROP_TEXT:
|
|
Packit |
b00eeb |
_gcr_live_search_set_text (self, g_value_get_string (value));
|
|
Packit |
b00eeb |
break;
|
|
Packit |
b00eeb |
default:
|
|
Packit |
b00eeb |
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
|
|
Packit |
b00eeb |
break;
|
|
Packit |
b00eeb |
};
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
static void
|
|
Packit |
b00eeb |
live_search_unmap (GtkWidget *widget)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
GcrLiveSearch *self = GCR_LIVE_SEARCH (widget);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
GTK_WIDGET_CLASS (_gcr_live_search_parent_class)->unmap (widget);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
/* unmap can happen if a parent gets hidden, in that case we want to hide
|
|
Packit |
b00eeb |
* the live search as well, so when it gets mapped again, the live search
|
|
Packit |
b00eeb |
* won't be shown. */
|
|
Packit |
b00eeb |
gtk_widget_hide (widget);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
gtk_entry_set_text (GTK_ENTRY (self->pv->search_entry), "");
|
|
Packit |
b00eeb |
gtk_widget_grab_focus (self->pv->hook_widget);
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
static void
|
|
Packit |
b00eeb |
live_search_show (GtkWidget *widget)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
GcrLiveSearch *self = GCR_LIVE_SEARCH (widget);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
if (!gtk_widget_has_focus (self->pv->search_entry))
|
|
Packit |
b00eeb |
gtk_widget_grab_focus (self->pv->search_entry);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
GTK_WIDGET_CLASS (_gcr_live_search_parent_class)->show (widget);
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
static void
|
|
Packit |
b00eeb |
live_search_grab_focus (GtkWidget *widget)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
GcrLiveSearch *self = GCR_LIVE_SEARCH (widget);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
if (!gtk_widget_has_focus (self->pv->search_entry)) {
|
|
Packit |
b00eeb |
gtk_widget_grab_focus (self->pv->search_entry);
|
|
Packit |
b00eeb |
gtk_editable_set_position (GTK_EDITABLE (self->pv->search_entry), -1);
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
static void
|
|
Packit |
b00eeb |
_gcr_live_search_class_init (GcrLiveSearchClass *klass)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
GObjectClass *object_class = (GObjectClass *) klass;
|
|
Packit |
b00eeb |
GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
|
|
Packit |
b00eeb |
GParamSpec *param_spec;
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
object_class->finalize = live_search_finalize;
|
|
Packit |
b00eeb |
object_class->dispose = live_search_dispose;
|
|
Packit |
b00eeb |
object_class->get_property = live_search_get_property;
|
|
Packit |
b00eeb |
object_class->set_property = live_search_set_property;
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
widget_class->unmap = live_search_unmap;
|
|
Packit |
b00eeb |
widget_class->show = live_search_show;
|
|
Packit |
b00eeb |
widget_class->grab_focus = live_search_grab_focus;
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
signals[ACTIVATE] = g_signal_new ("activate",
|
|
Packit |
b00eeb |
G_TYPE_FROM_CLASS (object_class),
|
|
Packit |
b00eeb |
G_SIGNAL_RUN_LAST,
|
|
Packit |
b00eeb |
0,
|
|
Packit |
b00eeb |
NULL, NULL,
|
|
Packit |
b00eeb |
g_cclosure_marshal_VOID__VOID,
|
|
Packit |
b00eeb |
G_TYPE_NONE, 0);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
signals[KEYNAV] = g_signal_new ("key-navigation",
|
|
Packit |
b00eeb |
G_TYPE_FROM_CLASS (object_class),
|
|
Packit |
b00eeb |
G_SIGNAL_RUN_LAST,
|
|
Packit |
b00eeb |
0,
|
|
Packit |
b00eeb |
g_signal_accumulator_true_handled, NULL,
|
|
Packit |
b00eeb |
_gcr_marshal_BOOLEAN__BOXED,
|
|
Packit |
b00eeb |
G_TYPE_BOOLEAN, 1, GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
param_spec = g_param_spec_object ("hook-widget", "Live Search Hook Widget",
|
|
Packit |
b00eeb |
"The live search catches key-press-events on this widget",
|
|
Packit |
b00eeb |
GTK_TYPE_WIDGET, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
|
Packit |
b00eeb |
g_object_class_install_property (object_class, PROP_HOOK_WIDGET, param_spec);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
param_spec = g_param_spec_string ("text", "Live Search Text",
|
|
Packit |
b00eeb |
"The text of the live search entry",
|
|
Packit |
b00eeb |
"", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
|
Packit |
b00eeb |
g_object_class_install_property (object_class, PROP_TEXT, param_spec);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
g_type_class_add_private (klass, sizeof (GcrLiveSearchPrivate));
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
static void
|
|
Packit |
b00eeb |
_gcr_live_search_init (GcrLiveSearch *self)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
self->pv = G_TYPE_INSTANCE_GET_PRIVATE ((self), GCR_TYPE_LIVE_SEARCH,
|
|
Packit |
b00eeb |
GcrLiveSearchPrivate);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
gtk_widget_set_no_show_all (GTK_WIDGET (self), TRUE);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
self->pv->search_entry = gtk_entry_new ();
|
|
Packit |
b00eeb |
gtk_entry_set_icon_from_icon_name (GTK_ENTRY (self->pv->search_entry),
|
|
Packit |
b00eeb |
GTK_ENTRY_ICON_SECONDARY, "window-close");
|
|
Packit |
b00eeb |
gtk_entry_set_icon_activatable (GTK_ENTRY (self->pv->search_entry),
|
|
Packit |
b00eeb |
GTK_ENTRY_ICON_SECONDARY, TRUE);
|
|
Packit |
b00eeb |
gtk_entry_set_icon_sensitive (GTK_ENTRY (self->pv->search_entry),
|
|
Packit |
b00eeb |
GTK_ENTRY_ICON_SECONDARY, TRUE);
|
|
Packit |
b00eeb |
gtk_widget_show (self->pv->search_entry);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
gtk_box_pack_start (GTK_BOX (self), self->pv->search_entry, TRUE, TRUE, 0);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
g_signal_connect (self->pv->search_entry, "icon_release",
|
|
Packit |
b00eeb |
G_CALLBACK (on_search_entry_close_pressed), self);
|
|
Packit |
b00eeb |
g_signal_connect (self->pv->search_entry, "changed",
|
|
Packit |
b00eeb |
G_CALLBACK (on_search_entry_text_changed), self);
|
|
Packit |
b00eeb |
g_signal_connect (self->pv->search_entry, "key-press-event",
|
|
Packit |
b00eeb |
G_CALLBACK (on_search_entry_key_pressed), self);
|
|
Packit |
b00eeb |
g_signal_connect (self->pv->search_entry, "activate",
|
|
Packit |
b00eeb |
G_CALLBACK (on_search_entry_activate), self);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
self->pv->hook_widget = NULL;
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
/**
|
|
Packit |
b00eeb |
* _gcr_live_search_new:
|
|
Packit |
b00eeb |
* @hook: (allow-none): the widget to hook
|
|
Packit |
b00eeb |
*
|
|
Packit |
b00eeb |
* Create a new #GcrLiveSearch.
|
|
Packit |
b00eeb |
*
|
|
Packit |
b00eeb |
* Returns: (transfer full) (type GcrUi.LiveSearch): The new widget
|
|
Packit |
b00eeb |
*/
|
|
Packit |
b00eeb |
GtkWidget *
|
|
Packit |
b00eeb |
_gcr_live_search_new (GtkWidget *hook)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
g_return_val_if_fail (hook == NULL || GTK_IS_WIDGET (hook), NULL);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
return g_object_new (GCR_TYPE_LIVE_SEARCH,
|
|
Packit |
b00eeb |
"hook-widget", hook,
|
|
Packit |
b00eeb |
NULL);
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
/* public methods */
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
GtkWidget *
|
|
Packit |
b00eeb |
_gcr_live_search_get_hook_widget (GcrLiveSearch *self)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
g_return_val_if_fail (GCR_IS_LIVE_SEARCH (self), NULL);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
return self->pv->hook_widget;
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
void
|
|
Packit |
b00eeb |
_gcr_live_search_set_hook_widget (GcrLiveSearch *self, GtkWidget *hook)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
g_return_if_fail (GCR_IS_LIVE_SEARCH (self));
|
|
Packit |
b00eeb |
g_return_if_fail (hook == NULL || GTK_IS_WIDGET (hook));
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
/* release the actual widget */
|
|
Packit |
b00eeb |
live_search_release_hook_widget (self);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
/* connect handlers if new source is not null */
|
|
Packit |
b00eeb |
if (hook != NULL) {
|
|
Packit |
b00eeb |
self->pv->hook_widget = g_object_ref (hook);
|
|
Packit |
b00eeb |
g_signal_connect (self->pv->hook_widget, "key-press-event",
|
|
Packit |
b00eeb |
G_CALLBACK (on_hook_widget_key_press_event),
|
|
Packit |
b00eeb |
self);
|
|
Packit |
b00eeb |
g_signal_connect (self->pv->hook_widget, "destroy",
|
|
Packit |
b00eeb |
G_CALLBACK (on_hook_widget_destroy),
|
|
Packit |
b00eeb |
self);
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
const gchar *
|
|
Packit |
b00eeb |
_gcr_live_search_get_text (GcrLiveSearch *self)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
g_return_val_if_fail (GCR_IS_LIVE_SEARCH (self), NULL);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
return gtk_entry_get_text (GTK_ENTRY (self->pv->search_entry));
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
void
|
|
Packit |
b00eeb |
_gcr_live_search_set_text (GcrLiveSearch *self, const gchar *text)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
g_return_if_fail (GCR_IS_LIVE_SEARCH (self));
|
|
Packit |
b00eeb |
g_return_if_fail (text != NULL);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
gtk_entry_set_text (GTK_ENTRY (self->pv->search_entry), text);
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
/**
|
|
Packit |
b00eeb |
* _gcr_live_search_match:
|
|
Packit |
b00eeb |
* @self: a #GcrLiveSearch
|
|
Packit |
b00eeb |
* @string: a string where to search, must be valid UTF-8.
|
|
Packit |
b00eeb |
*
|
|
Packit |
b00eeb |
* Search if one of the words in @string string starts with the current text
|
|
Packit |
b00eeb |
* of @self.
|
|
Packit |
b00eeb |
*
|
|
Packit |
b00eeb |
* Searching for "aba" in "Abasto" will match, searching in "Moraba" will not,
|
|
Packit |
b00eeb |
* and searching in "A tool (abacus)" will do.
|
|
Packit |
b00eeb |
*
|
|
Packit |
b00eeb |
* The match is not case-sensitive, and regardless of the accentuation marks.
|
|
Packit |
b00eeb |
*
|
|
Packit |
b00eeb |
* Returns: %TRUE if a match is found, %FALSE otherwise.
|
|
Packit |
b00eeb |
*
|
|
Packit |
b00eeb |
**/
|
|
Packit |
b00eeb |
gboolean
|
|
Packit |
b00eeb |
_gcr_live_search_match (GcrLiveSearch *self, const gchar *string)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
g_return_val_if_fail (GCR_IS_LIVE_SEARCH (self), FALSE);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
return _gcr_live_search_match_words (string, self->pv->stripped_words);
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
gboolean
|
|
Packit |
b00eeb |
_gcr_live_search_match_string (const gchar *string, const gchar *prefix)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
GPtrArray *words;
|
|
Packit |
b00eeb |
gboolean match;
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
words = _gcr_live_search_strip_utf8_string (prefix);
|
|
Packit |
b00eeb |
match = _gcr_live_search_match_words (string, words);
|
|
Packit |
b00eeb |
if (words != NULL)
|
|
Packit |
b00eeb |
g_ptr_array_unref (words);
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
return match;
|
|
Packit |
b00eeb |
}
|
|
Packit |
b00eeb |
|
|
Packit |
b00eeb |
GPtrArray *
|
|
Packit |
b00eeb |
_gcr_live_search_get_words (GcrLiveSearch *self)
|
|
Packit |
b00eeb |
{
|
|
Packit |
b00eeb |
return self->pv->stripped_words;
|
|
Packit |
b00eeb |
}
|