1
/*
2
 * This file is a part of hildon
3
 *
4
 * Copyright (C) 2007-2009 Nokia Corporation.
5
 *
6
 * Based in OssoABookLiveSearch, OSSO Address Book.
7
 * Author: Joergen Scheibengruber <jorgen.scheibengruber@nokia.com>
8
 * Hildon version: Claudio Saavedra <csaavedra@igalia.com>
9
 *
10
 * This program is free software; you can redistribute it and/or modify
11
 * it under the terms of the GNU Lesser Public License as published by
12
 * the Free Software Foundation; version 2 of the license.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU Lesser Public License for more details.
18
 *
19
 */
20
21
/**
22
 * SECTION:hildon-live-search
23
 * @short_description: A widget for manipulating #GtkTreeModelFilter
24
 * instances.
25
 *
26
 * This widget provides a user interface for manipulating
27
 * #GtkTreeModelFilter instances.
28
 *
29
 * To set a #GtkTreeFilterModel to filter with, use
30
 * hildon_live_search_set_filter(). By default, #HildonLiveSearch
31
 * filters on the child model of the filter model set using a case
32
 * sensitive prefix comparison on the model's column specified by
33
 * #HildonLiveSearch:text-column. If a more refined filtering is
34
 * necessary, you can use hildon_live_search_set_visible_func() to
35
 * specify a #HildonLiveSearchVisibleFunc to use.
36
 *
37
 */
38
39
#include                                        "hildon-live-search.h"
40
41
#include                                        <hildon/hildon.h>
42
#include                                        <string.h>
43
44
G_DEFINE_TYPE (HildonLiveSearch, hildon_live_search, GTK_TYPE_TOOLBAR);
45
46
#define                                         GET_PRIVATE(o)                     \
47
                                                (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
48
                                                HILDON_TYPE_LIVE_SEARCH,           \
49
                                                HildonLiveSearchPrivate))
50
51
struct _HildonLiveSearchPrivate
52
{
53
    GtkTreeModelFilter *filter;
54
55
    GtkWidget *kb_focus_widget;
56
57
    GtkWidget *entry;
58
    GtkWidget *event_widget;
59
    GHashTable *selection_map;
60
61
    gulong key_press_id;
62
    gulong event_widget_destroy_id;
63
    gulong kb_focus_widget_destroy_id;
64
    gulong idle_filter_id;
65
66
    gchar *prefix;
67
    gint text_column;
68
69
    HildonLiveSearchVisibleFunc visible_func;
70
    gpointer visible_data;
71
    GDestroyNotify visible_destroy;
72
    gboolean visible_func_set;
73
    gboolean run_async;
74
};
75
76
enum
77
{
78
    PROP_0,
79
80
    PROP_FILTER,
81
    PROP_WIDGET,
82
    PROP_TEXT_COLUMN,
83
    PROP_TEXT
84
};
85
86
enum
87
{
88
  REFILTER,
89
  LAST_SIGNAL
90
};
91
92
static guint signals[LAST_SIGNAL] = { 0 };
93
94
static gboolean
95
visible_func                                    (GtkTreeModel *model,
96
                                                 GtkTreeIter  *iter,
97
                                                 gpointer      data);
98
99
/* Private implementation */
100
101
static guint
102
hash_func                                       (gconstpointer key)
103
{
104
    GtkTreePath *path;
105
    gchar *path_str;
106
    guint val;
107
108
    path = (GtkTreePath *) key;
109
    path_str = gtk_tree_path_to_string (path);
110
    val = g_str_hash (path_str);
111
    g_free (path_str);
112
113
    return val;
114
}
115
116
static gboolean
117
key_equal_func                                  (gconstpointer v1,
118
                                                 gconstpointer v2)
119
{
120
    return gtk_tree_path_compare ((GtkTreePath *)v1,
121
                                  (GtkTreePath *)v2) == 0;
122
}
123
124
static gboolean
125
model_get_root (GtkTreeModelFilter *filter,
126
                GtkTreeModel *base_model,
127
                GtkTreeIter *iter_child)
128
{
129
    GtkTreeIter virtual_root;
130
    GtkTreePath *virtual_root_path;
131
    gboolean walking;
132
133
    g_object_get (filter, "virtual-root", &virtual_root_path, NULL);
134
    if (virtual_root_path) {
135
        gtk_tree_model_get_iter (base_model, &virtual_root, virtual_root_path);
136
    }
137
    walking = gtk_tree_model_iter_children (base_model, iter_child,
138
                                            virtual_root_path ?
139
                                            &virtual_root : NULL);
140
141
    return walking;
142
}
143
144
/**
145
 * selection_map_create:
146
 * @priv: The private pimpl
147
 *
148
 * Adds a selection map which is useful when merging selected rows in
149
 * a treeview, when the live search widget is used.
150
 **/
151
static void
152
selection_map_create                            (HildonLiveSearchPrivate *priv)
153
{
154
    gboolean walking;
155
    GtkTreeModel *base_model;
156
    GtkTreeIter iter_child;
157
    GtkTreePath *path;
158
159
    if (!GTK_IS_TREE_VIEW (priv->kb_focus_widget))
160
        return;
161
162
    g_assert (priv->selection_map == NULL);
163
164
    base_model = gtk_tree_model_filter_get_model (priv->filter);
165
166
    priv->selection_map = g_hash_table_new_full
167
        (hash_func, key_equal_func, (GDestroyNotify) gtk_tree_path_free, NULL);
168
169
    walking = model_get_root (priv->filter, base_model, &iter_child);
170
171
    while (walking) {
172
        path = gtk_tree_model_get_path (base_model, &iter_child);
173
        g_hash_table_insert (priv->selection_map,
174
                             path, GINT_TO_POINTER (FALSE));
175
176
        walking = gtk_tree_model_iter_next (base_model, &iter_child);
177
    }
178
}
179
180
/**
181
 * selection_map_destroy:
182
 * @priv: The private pimpl
183
 *
184
 * Destroys resources associated with the selection map.
185
 **/
186
static void
187
selection_map_destroy                           (HildonLiveSearchPrivate *priv)
188
{
189
    if (priv->selection_map != NULL) {
190
        g_hash_table_destroy (priv->selection_map);
191
        priv->selection_map = NULL;
192
    }
193
}
194
195
static GtkTreePath *
196
convert_child_path_to_path (GtkTreeModel *model,
197
                            GtkTreeModel *base_model,
198
                            GtkTreePath *path)
199
{
200
  g_return_val_if_fail (model != NULL, NULL);
201
  g_return_val_if_fail (base_model != NULL, NULL);
202
  g_return_val_if_fail (path != NULL, NULL);
203
204
  if (model == base_model)
205
    return gtk_tree_path_copy (path);
206
207
  g_assert (gtk_tree_model_sort_get_model (model) == base_model);
208
  g_assert (GTK_IS_TREE_MODEL_SORT (model));
209
210
  return gtk_tree_model_sort_convert_child_path_to_path
211
      (GTK_TREE_MODEL_SORT (model), path);
212
}
213
214
/**
215
 * selection_map_update_map_from_selection:
216
 * @priv: The private pimpl
217
 *
218
 * Find out which rows are visible in filter, and mark them as selected
219
 * or unselected from treeview to selection map.
220
 **/
221
static void
222
selection_map_update_map_from_selection         (HildonLiveSearchPrivate *priv)
223
{
224
    gboolean walking;
225
    GtkTreeModel *base_model;
226
    GtkTreeSelection *selection;
227
    GtkTreeIter iter_child;
228
229
    if (!GTK_IS_TREE_VIEW (priv->kb_focus_widget))
230
        return;
231
232
    base_model = gtk_tree_model_filter_get_model (priv->filter);
233
    selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->kb_focus_widget));
234
235
    walking = model_get_root (priv->filter, base_model, &iter_child);
236
237
    while (walking) {
238
        GtkTreePath *base_path, *filter_path;
239
        base_path = gtk_tree_model_get_path (base_model, &iter_child);
240
        filter_path = gtk_tree_model_filter_convert_child_path_to_path
241
          (priv->filter, base_path);
242
243
        if (filter_path) {
244
            GtkTreePath *view_path;
245
            view_path = convert_child_path_to_path (
246
                gtk_tree_view_get_model (GTK_TREE_VIEW (priv->kb_focus_widget)),
247
                GTK_TREE_MODEL (priv->filter), filter_path);
248
            if (gtk_tree_selection_path_is_selected
249
                (selection, view_path)) {
250
                g_hash_table_replace
251
                    (priv->selection_map,
252
                     base_path,
253
                     GINT_TO_POINTER (TRUE));
254
            } else {
255
                g_hash_table_replace
256
                    (priv->selection_map,
257
                     base_path,
258
                     GINT_TO_POINTER (FALSE));
259
            }
260
            gtk_tree_path_free (view_path);
261
            gtk_tree_path_free (filter_path);
262
        } else {
263
            gtk_tree_path_free (base_path);
264
        }
265
        walking = gtk_tree_model_iter_next (base_model, &iter_child);
266
    }
267
}
268
269
/**
270
 * selection_map_update_selection_from_map:
271
 * @priv: The private pimpl
272
 *
273
 * For currently visible rows in filter, set selection from selection
274
 * map to treeview.
275
 **/
276
static void
277
selection_map_update_selection_from_map         (HildonLiveSearchPrivate *priv)
278
{
279
    gboolean walking;
280
    GtkTreeModel *base_model;
281
    GtkTreeSelection *selection;
282
    GtkTreeIter iter_child;
283
284
    if (!GTK_IS_TREE_VIEW (priv->kb_focus_widget))
285
        return;
286
287
    base_model = gtk_tree_model_filter_get_model (priv->filter);
288
    selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->kb_focus_widget));
289
290
    walking = model_get_root (priv->filter, base_model, &iter_child);
291
292
    while (walking) {
293
        GtkTreePath *base_path, *filter_path;
294
        base_path = gtk_tree_model_get_path (base_model, &iter_child);
295
        filter_path = gtk_tree_model_filter_convert_child_path_to_path
296
          (priv->filter, base_path);
297
298
        if (filter_path) {
299
            gboolean selected;
300
            GtkTreePath *view_path;
301
            view_path = convert_child_path_to_path (
302
                gtk_tree_view_get_model (GTK_TREE_VIEW (priv->kb_focus_widget)),
303
                GTK_TREE_MODEL (priv->filter), filter_path);
304
305
            selected = GPOINTER_TO_INT
306
                (g_hash_table_lookup
307
                 (priv->selection_map, base_path));
308
309
            if (selected) {
310
                gtk_tree_selection_select_path
311
                    (selection, view_path);
312
            } else {
313
                gtk_tree_selection_unselect_path
314
                    (selection, view_path);
315
            }
316
            gtk_tree_path_free (view_path);
317
            gtk_tree_path_free (filter_path);
318
        }
319
        gtk_tree_path_free (base_path);
320
        walking = gtk_tree_model_iter_next (base_model, &iter_child);
321
    }
322
}
323
324
static void
325
refilter (HildonLiveSearch *livesearch)
326
{
327
    HildonLiveSearchPrivate *priv = livesearch->priv;
328
    gboolean handled = FALSE;
329
    gboolean needs_mapping;
330
331
    needs_mapping = GTK_IS_TREE_VIEW (priv->kb_focus_widget) &&
332
        gtk_tree_selection_get_mode (gtk_tree_view_get_selection (
333
                                         GTK_TREE_VIEW (priv->kb_focus_widget))) != GTK_SELECTION_NONE;
334
335
    /* This is not pretty code, but it should fix some warnings in the case we
336
       attempt to refilter before the treeview actually has a model. */
337
    if (needs_mapping && !gtk_tree_view_get_model (GTK_TREE_VIEW (priv->kb_focus_widget)))
338
        return;
339
340
    /* Create/update selection map from current selection */
341
    if (needs_mapping) {
342
        if (priv->selection_map == NULL)
343
            selection_map_create (priv);
344
        selection_map_update_map_from_selection (priv);
345
    }
346
347
    /* Filter the model */
348
    g_signal_emit (livesearch, signals[REFILTER], 0, &handled);
349
    if (!handled && priv->filter)
350
        gtk_tree_model_filter_refilter (priv->filter);
351
352
    /* Restore selection from mapping */
353
    if (needs_mapping)
354
        selection_map_update_selection_from_map (priv);
355
}
356
357
static gboolean
358
on_idle_refilter (HildonLiveSearch *livesearch)
359
{
360
    refilter (livesearch);
361
362
    if (livesearch->priv->prefix == NULL)
363
        selection_map_destroy (livesearch->priv);
364
365
    livesearch->priv->idle_filter_id = 0;
366
367
    return FALSE;
368
}
369
370
static void
371
on_entry_changed                                (GtkEntry *entry,
372
                                                 gpointer  user_data)
373
{
374
    HildonLiveSearch *livesearch = user_data;
375
    HildonLiveSearchPrivate *priv;
376
    const char *text;
377
    glong len;
378
379
    g_return_if_fail (HILDON_IS_LIVE_SEARCH (livesearch));
380
    priv = livesearch->priv;
381
382
    text = gtk_entry_get_text (GTK_ENTRY (entry));
383
    len = g_utf8_strlen (text, -1);
384
    if (len < 1) {
385
        text = NULL;
386
    }
387
388
    g_free (priv->prefix);
389
    priv->prefix = g_strdup (text);
390
391
    if (priv->run_async) {
392
        if (priv->idle_filter_id == 0) {
393
            priv->idle_filter_id = gdk_threads_add_idle ((GSourceFunc) on_idle_refilter, livesearch);
394
        }
395
    } else {
396
        if (priv->idle_filter_id != 0) {
397
            g_source_remove (priv->idle_filter_id);
398
            priv->idle_filter_id = 0;
399
        }
400
        on_idle_refilter (livesearch);
401
    }
402
403
    /* Show the livesearch only if there is text in it */
404
    if (priv->prefix == NULL) {
405
        gtk_widget_hide (GTK_WIDGET (livesearch));
406
    } else {
407
        gtk_widget_show (GTK_WIDGET (livesearch));
408
    }
409
410
    /* Any change in the entry implies a change in HildonLiveSearch:text. */
411
    g_object_notify (G_OBJECT (livesearch), "text");
412
}
413
414
/**
415
 * hildon_live_search_append_text:
416
 * @livesearch: An #HildonLiveSearch widget
417
 * @text: Text to append. @text is copied internally
418
 * can be freed later by the caller.
419
 *
420
 * Appends a string to the entry text in the live search widget.
421
 *
422
 * Since: 2.2.4
423
 **/
424
void
425
hildon_live_search_append_text                  (HildonLiveSearch *livesearch,
426
                                                 const char       *text)
427
{
428
    GtkEditable *editable;
429
    int pos, start, end;
430
431
    g_return_if_fail (HILDON_IS_LIVE_SEARCH (livesearch));
432
    g_return_if_fail (NULL != text);
433
434
    editable = GTK_EDITABLE (livesearch->priv->entry);
435
436
    if (gtk_editable_get_selection_bounds (editable, &start, &end)) {
437
        gtk_editable_delete_text (editable, start, end);
438
    }
439
    pos = gtk_editable_get_position (editable);
440
    gtk_editable_insert_text (editable, text, strlen (text), &pos);
441
    gtk_editable_set_position (editable, pos + 1);
442
}
443
444
/**
445
 * hildon_live_search_set_text:
446
 * @livesearch: An #HildonLiveSearch widget
447
 * @text: Text to set. @text is copied internally
448
 * can be freed later by the caller.
449
 *
450
 * Sets a string to the entry text in the live search widget.
451
 *
452
 * Since: 2.2.15
453
 **/
454
void
455
hildon_live_search_set_text                  (HildonLiveSearch *livesearch,
456
                                              const char       *text)
457
{
458
    g_return_if_fail (HILDON_IS_LIVE_SEARCH (livesearch));
459
    g_return_if_fail (NULL != text);
460
461
    livesearch->priv->run_async = FALSE;
462
    gtk_entry_set_text (GTK_ENTRY (livesearch->priv->entry),
463
                        text);
464
    livesearch->priv->run_async = TRUE;
465
466
    /* GObject::notify::text for HildonLiveSearch:text emitted in the
467
       handler for GtkEntry::changed. */
468
}
469
470
/**
471
 * hildon_live_search_get_text:
472
 * @livesearch: An #HildonLiveSearch widget
473
 *
474
 * Retrieves the text contents of the #HildonLiveSearch widget.
475
 *
476
 * Returns: a pointer to the text contents of the widget as a
477
 * string. This string should not be freed, modified, or stored.
478
 *
479
 * Since: 2.2.4
480
 **/
481
const char *
482
hildon_live_search_get_text                     (HildonLiveSearch *livesearch)
483
{
484
    g_return_val_if_fail (HILDON_IS_LIVE_SEARCH (livesearch), NULL);
485
486
    return gtk_entry_get_text (GTK_ENTRY (livesearch->priv->entry));
487
}
488
489
/*
490
 * Key press handler. This takes key presses from the source widget and
491
 * manipulate the entry. This manipulation then calls #on_entry_changed.
492
 */
493
static gboolean
494
on_key_press_event                              (GtkWidget        *widget,
495
                                                 GdkEventKey      *event,
496
                                                 HildonLiveSearch *live_search)
497
{
498
    HildonLiveSearchPrivate *priv;
499
    gboolean handled = FALSE;
500
501
    g_return_val_if_fail (HILDON_IS_LIVE_SEARCH (live_search), FALSE);
502
    priv = live_search->priv;
503
504
    if (GTK_WIDGET_VISIBLE (priv->kb_focus_widget)) {
505
        /* If the live search is hidden, Ctrl+whatever is always
506
         * passed to the focus widget, with the exception of
507
         * Ctrl + Space, which is given to the entry, so that the input method
508
         * is allowed to switch the keyboard layout. */
509
        if (GTK_WIDGET_VISIBLE (live_search) ||
510
            !(event->state & GDK_CONTROL_MASK ||
511
              event->keyval == GDK_Control_L ||
512
              event->keyval == GDK_Control_R) ||
513
            (event->state & GDK_CONTROL_MASK &&
514
             event->keyval == GDK_space)) {
515
            GdkEvent *new_event;
516
517
            /* If the entry is realized and has focus, it is enough to catch events.
518
             * This assumes that the toolbar is a child of the hook widget. */
519
            gtk_widget_realize (priv->entry);
520
            if (!GTK_WIDGET_HAS_FOCUS (priv->entry))
521
                gtk_widget_grab_focus (priv->entry);
522
523
            new_event = gdk_event_copy ((GdkEvent *)event);
524
            handled = gtk_widget_event (priv->entry, new_event);
525
            gdk_event_free (new_event);
526
        } else if (!GTK_WIDGET_HAS_FOCUS (priv->kb_focus_widget)) {
527
            gtk_widget_grab_focus (GTK_WIDGET (priv->kb_focus_widget));
528
        }
529
    }
530
531
    return handled;
532
}
533
534
static void
535
on_hide_cb                                      (GtkWidget        *widget,
536
                                                 HildonLiveSearch *live_search)
537
{
538
    hildon_live_search_set_text (live_search, "");
539
    gtk_widget_grab_focus (live_search->priv->kb_focus_widget);
540
}
541
542
/* GObject methods */
543
544
static void
545
hildon_live_search_get_property                 (GObject    *object,
546
                                                 guint       property_id,
547
                                                 GValue     *value,
548
                                                 GParamSpec *pspec)
549
{
550
    HildonLiveSearch *livesearch = HILDON_LIVE_SEARCH (object);
551
552
    switch (property_id) {
553
    case PROP_FILTER:
554
        g_value_set_object (value, livesearch->priv->filter);
555
        break;
556
    case PROP_TEXT_COLUMN:
557
        g_value_set_int (value, livesearch->priv->text_column);
558
        break;
559
    case PROP_TEXT:
560
        g_value_set_string (value, livesearch->priv->prefix);
561
        break;
562
    default:
563
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
564
    }
565
}
566
567
static void
568
hildon_live_search_set_property                 (GObject      *object,
569
                                                 guint         property_id,
570
                                                 const GValue *value,
571
                                                 GParamSpec   *pspec)
572
{
573
    HildonLiveSearch *livesearch = HILDON_LIVE_SEARCH (object);
574
575
    switch (property_id) {
576
    case PROP_FILTER:
577
        hildon_live_search_set_filter (livesearch,
578
                                       g_value_get_object (value));
579
        break;
580
    case PROP_TEXT_COLUMN:
581
        hildon_live_search_set_text_column (livesearch,
582
                                            g_value_get_int (value));
583
        break;
584
    case PROP_TEXT:
585
        hildon_live_search_set_text (livesearch,
586
                                     g_value_get_string (value));
587
        break;
588
    default:
589
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
590
    }
591
}
592
593
static void
594
hildon_live_search_dispose                      (GObject *object)
595
{
596
    HildonLiveSearchPrivate *priv = HILDON_LIVE_SEARCH (object)->priv;
597
598
    hildon_live_search_widget_unhook (HILDON_LIVE_SEARCH (object));
599
600
    if (priv->filter) {
601
        selection_map_destroy (priv);
602
        g_object_unref (priv->filter);
603
        priv->filter = NULL;
604
    }
605
606
    if (priv->prefix) {
607
        g_free (priv->prefix);
608
        priv->prefix = NULL;
609
    }
610
611
    if (priv->visible_destroy) {
612
        priv->visible_destroy (priv->visible_data);
613
        priv->visible_destroy = NULL;
614
    }
615
616
    if (priv->idle_filter_id) {
617
        g_source_remove (priv->idle_filter_id);
618
        priv->idle_filter_id = 0;
619
    }
620
621
    G_OBJECT_CLASS (hildon_live_search_parent_class)->dispose (object);
622
}
623
624
static void
625
hildon_live_search_class_init                   (HildonLiveSearchClass *klass)
626
{
627
    GObjectClass *object_class = G_OBJECT_CLASS (klass);
628
629
    g_type_class_add_private (klass, sizeof (HildonLiveSearchPrivate));
630
631
    object_class->get_property = hildon_live_search_get_property;
632
    object_class->set_property = hildon_live_search_set_property;
633
    object_class->dispose = hildon_live_search_dispose;
634
635
    /**
636
     * HildonLiveSearch:filter:
637
     *
638
     * The #GtkTreeModelFilter to filter.
639
     *
640
     * Since: 2.2.4
641
     */
642
    g_object_class_install_property (object_class,
643
                                     PROP_FILTER,
644
                                     g_param_spec_object ("filter",
645
                                                          "Filter",
646
                                                          "Model filter",
647
                                                          GTK_TYPE_TREE_MODEL_FILTER,
648
                                                          G_PARAM_READWRITE |
649
                                                          G_PARAM_STATIC_NICK |
650
                                                          G_PARAM_STATIC_NAME |
651
                                                          G_PARAM_STATIC_BLURB));
652
653
    /**
654
     * HildonLiveSearch:text-column:
655
     *
656
     * A %G_TYPE_STRING column in the child model of #HildonLiveSearch:filter,
657
     * to be used by the default filtering function of #HildonLiveSearch.
658
     *
659
     * Since: 2.2.4
660
     */
661
    g_object_class_install_property (object_class,
662
                                     PROP_TEXT_COLUMN,
663
                                     g_param_spec_int ("text-column",
664
                                                       "Text column",
665
                                                       "Column to use to filter "
666
                                                       "elements from the #GtkTreeModelFilter",
667
                                                       -1, G_MAXINT, -1,
668
                                                       G_PARAM_READWRITE |
669
                                                       G_PARAM_STATIC_STRINGS));
670
671
    /**
672
     * HildonLiveSearch:text:
673
     *
674
     * The text used to filter
675
     *
676
     * Since: 2.2.15
677
     */
678
    g_object_class_install_property (object_class,
679
                                     PROP_TEXT,
680
                                     g_param_spec_string ("text",
681
                                                       "Text",
682
                                                       "Text to use as a filter",
683
                                                       "",
684
                                                       G_PARAM_READWRITE |
685
                                                       G_PARAM_STATIC_STRINGS));
686
687
  /**
688
   * HildonLiveSearch::refilter:
689
   * @livesearch: the object which received the signal
690
   *
691
   * The "refilter" signal is emitted when the text in the entry changed and a
692
   * refilter is needed.
693
   *
694
   * If this signal is not stopped, gtk_tree_model_filter_refilter() will be
695
   * called on the filter model. Otherwise the handler is responsible to
696
   * refilter it.
697
   *
698
   * Returns: %TRUE to stop other handlers from being invoked for the event.
699
   * %FALSE to propagate the event further.
700
   *
701
   * Since: 2.2.5
702
   */
703
  signals[REFILTER] =
704
    g_signal_new ("refilter",
705
                  HILDON_TYPE_LIVE_SEARCH,
706
                  G_SIGNAL_RUN_LAST, 0,
707
                  g_signal_accumulator_true_handled, NULL,
708
                  _hildon_marshal_BOOLEAN__VOID, G_TYPE_BOOLEAN, 0);
709
}
710
711
static void
712
close_button_clicked_cb                         (GtkWidget *button,
713
                                                 gpointer   user_data)
714
{
715
    gtk_widget_hide (GTK_WIDGET (user_data));
716
}
717
718
static HildonGtkInputMode
719
filter_input_mode                               (HildonGtkInputMode imode)
720
{
721
    return imode & ~(HILDON_GTK_INPUT_MODE_AUTOCAP |
722
                     HILDON_GTK_INPUT_MODE_DICTIONARY);
723
}
724
725
static void
726
hildon_live_search_init                         (HildonLiveSearch *self)
727
{
728
    HildonLiveSearchPrivate *priv;
729
    GtkWidget *close_button_alignment;
730
    GtkToolItem *close_button_container;
731
    GtkWidget *close;
732
    GtkToolItem *close_button;
733
    GtkToolItem *entry_container;
734
    GtkWidget *entry_hbox;
735
    HildonGtkInputMode imode;
736
737
    self->priv = priv = GET_PRIVATE (self);
738
739
    gtk_toolbar_set_style (GTK_TOOLBAR (self), GTK_TOOLBAR_ICONS);
740
    gtk_container_set_border_width (GTK_CONTAINER (self), 0);
741
742
    priv->kb_focus_widget = NULL;
743
    priv->prefix = NULL;
744
745
    priv->visible_func = NULL;
746
    priv->visible_data = NULL;
747
    priv->visible_destroy = NULL;
748
    priv->visible_func_set = FALSE;
749
    priv->idle_filter_id = 0;
750
751
    priv->selection_map = NULL;
752
    priv->run_async = TRUE;
753
754
    priv->text_column = -1;
755
756
    entry_container = gtk_tool_item_new ();
757
    gtk_tool_item_set_expand (entry_container, TRUE);
758
759
    entry_hbox = gtk_hbox_new (FALSE, 0);
760
    gtk_container_add (GTK_CONTAINER (entry_container), entry_hbox);
761
762
    priv->entry = hildon_entry_new (HILDON_SIZE_FINGER_HEIGHT);
763
764
    /* Unset the autocap and dictionary input flags from the
765
       HildonEntry. */
766
    imode = hildon_gtk_entry_get_input_mode (GTK_ENTRY (priv->entry));
767
    hildon_gtk_entry_set_input_mode (GTK_ENTRY (priv->entry),
768
                                     filter_input_mode (imode));
769
770
    gtk_widget_set_name (GTK_WIDGET (priv->entry),
771
                         "HildonLiveSearchEntry");
772
773
    gtk_box_pack_start (GTK_BOX (entry_hbox), priv->entry, TRUE, TRUE,
774
                        HILDON_MARGIN_DEFAULT);
775
776
    gtk_toolbar_insert (GTK_TOOLBAR (self), entry_container, 0);
777
    gtk_widget_show_all (GTK_WIDGET (entry_container));
778
779
    close = gtk_image_new_from_icon_name ("general_close",
780
                                          HILDON_ICON_SIZE_FINGER);
781
    gtk_misc_set_padding (GTK_MISC (close), 0, 0);
782
    close_button = gtk_tool_button_new (close, NULL);
783
    GTK_WIDGET_UNSET_FLAGS (close_button, GTK_CAN_FOCUS);
784
785
    close_button_alignment = gtk_alignment_new (0.0f, 0.0f, 1.0f, 1.0f);
786
    gtk_alignment_set_padding (GTK_ALIGNMENT (close_button_alignment),
787
                               0, 0,
788
                               0, HILDON_MARGIN_DEFAULT);
789
    gtk_container_add (GTK_CONTAINER (close_button_alignment),
790
                       GTK_WIDGET (close_button));
791
792
    close_button_container = gtk_tool_item_new ();
793
    gtk_container_add (GTK_CONTAINER (close_button_container),
794
                       close_button_alignment);
795
796
    gtk_toolbar_insert (GTK_TOOLBAR (self), close_button_container, -1);
797
    gtk_widget_show_all (GTK_WIDGET (close_button_container));
798
799
    g_signal_connect (G_OBJECT (close_button), "clicked",
800
                      G_CALLBACK (close_button_clicked_cb), self);
801
802
    g_signal_connect (G_OBJECT (priv->entry), "changed",
803
                      G_CALLBACK (on_entry_changed), self);
804
805
    g_signal_connect (self, "hide",
806
                      G_CALLBACK (on_hide_cb), self);
807
808
    gtk_widget_set_no_show_all (GTK_WIDGET (self), TRUE);
809
}
810
811
/* Public interface */
812
813
/**
814
 * hildon_live_search_new:
815
 *
816
 * Creates and returns a new #HildonLiveSearch widget.
817
 *
818
 * Returns: The newly created live search widget.
819
 *
820
 * Since: 2.2.4
821
 **/
822
GtkWidget *
823
hildon_live_search_new                          (void)
824
{
825
    return g_object_new (HILDON_TYPE_LIVE_SEARCH, NULL);
826
}
827
828
static gboolean
829
visible_func                                    (GtkTreeModel *model,
830
                                                 GtkTreeIter  *iter,
831
                                                 gpointer      data)
832
{
833
    HildonLiveSearchPrivate *priv;
834
    gchar *string;
835
    gboolean visible = FALSE;
836
837
    priv = (HildonLiveSearchPrivate *) data;
838
839
    if (priv->prefix == NULL)
840
        return TRUE;
841
842
    if (priv->visible_func == NULL && priv->text_column == -1)
843
        return TRUE;
844
845
    if (priv->visible_func) {
846
        visible = (priv->visible_func) (model, iter,
847
                                        priv->prefix,
848
                                        priv->visible_data);
849
    } else {
850
        gtk_tree_model_get (model, iter, priv->text_column, &string, -1);
851
        visible = (string != NULL && g_str_has_prefix (string, priv->prefix));
852
        g_free (string);
853
    }
854
855
    return visible;
856
}
857
858
/**
859
 * hildon_live_search_set_filter:
860
 * @livesearch: An #HildonLiveSearch widget
861
 * @filter: a #GtkTreeModelFilter, or %NULL
862
 *
863
 * Sets a filter for @livesearch.
864
 *
865
 * Since: 2.2.4
866
 */
867
void
868
hildon_live_search_set_filter                   (HildonLiveSearch   *livesearch,
869
                                                 GtkTreeModelFilter *filter)
870
{
871
    HildonLiveSearchPrivate *priv;
872
873
    g_return_if_fail (HILDON_IS_LIVE_SEARCH (livesearch));
874
    g_return_if_fail (filter == NULL || GTK_IS_TREE_MODEL_FILTER (filter));
875
876
    priv = livesearch->priv;
877
878
    if (filter == priv->filter)
879
        return;
880
881
    if (filter)
882
        g_object_ref (filter);
883
884
    if (priv->filter)
885
        g_object_unref (priv->filter);
886
887
    priv->filter = filter;
888
889
    if (priv->visible_func_set == FALSE &&
890
        (priv->text_column != -1 || priv->visible_func)) {
891
        gtk_tree_model_filter_set_visible_func (filter,
892
                                                visible_func,
893
                                                priv,
894
                                                NULL);
895
        priv->visible_func_set = TRUE;
896
    }
897
898
    refilter (livesearch);
899
900
    g_object_notify (G_OBJECT (livesearch), "filter");
901
}
902
903
/**
904
 * hildon_live_search_get_filter:
905
 * @livesearch: An #HildonLiveSearch widget
906
 *
907
 * Returns: The #GtkTreeModelFilter set with hildon_live_search_set_filter()
908
 *
909
 * Since: 2.2.5
910
 */
911
GtkTreeModelFilter *
912
hildon_live_search_get_filter (HildonLiveSearch *livesearch)
913
{
914
    HildonLiveSearchPrivate *priv;
915
916
    g_return_val_if_fail (HILDON_IS_LIVE_SEARCH (livesearch), NULL);
917
918
    priv = livesearch->priv;
919
920
    return priv->filter;
921
}
922
923
/**
924
 * hildon_live_search_set_text_column:
925
 * @livesearch: a #HildonLiveSearch
926
 * @text_column: the column in the model of @livesearch to get the strings
927
 * to filter from
928
 *
929
 * Sets the column to be used by the default filtering method.
930
 * This column must be of type %G_TYPE_STRING.
931
 *
932
 * Calling this method will trigger filtering of the model, so use
933
 * with moderation. Note that you can only use either
934
 * #HildonLiveSearch:text-column or
935
 * hildon_live_search_set_visible_func().
936
 *
937
 * Since: 2.2.4
938
 **/
939
void
940
hildon_live_search_set_text_column              (HildonLiveSearch *livesearch,
941
                                                 gint              text_column)
942
{
943
    HildonLiveSearchPrivate *priv;
944
945
    g_return_if_fail (HILDON_IS_LIVE_SEARCH (livesearch));
946
    g_return_if_fail (-1 <= text_column);
947
948
    priv = livesearch->priv;
949
950
    g_return_if_fail (text_column < gtk_tree_model_get_n_columns (gtk_tree_model_filter_get_model (priv->filter)));
951
    g_return_if_fail (priv->visible_func == NULL);
952
953
    if (priv->text_column == text_column)
954
        return;
955
956
    priv->text_column = text_column;
957
958
    if (priv->visible_func_set == FALSE) {
959
        gtk_tree_model_filter_set_visible_func (priv->filter,
960
                                                visible_func,
961
                                                priv,
962
                                                NULL);
963
        priv->visible_func_set = TRUE;
964
    }
965
966
    refilter (livesearch);
967
}
968
969
static void
970
on_widget_destroy                               (GtkObject *object,
971
                                                 gpointer   user_data)
972
{
973
    hildon_live_search_widget_unhook (HILDON_LIVE_SEARCH (user_data));
974
}
975
976
/**
977
 * hildon_live_search_widget_hook:
978
 * @livesearch: An #HildonLiveSearch widget
979
 * @hook_widget: A widget on which we listen for key events
980
 * @kb_focus: The widget which we grab focus on
981
 *
982
 * This function must be called after a #HildonLiveSearch widget is
983
 * constructed to set the hook widget and the focus widget for
984
 * @livesearch. After that, the #HildonLiveSearch widget can be
985
 * packed into a container and used.
986
 *
987
 * Since: 2.2.4
988
 **/
989
void
990
hildon_live_search_widget_hook                  (HildonLiveSearch *livesearch,
991
                                                 GtkWidget        *hook_widget,
992
                                                 GtkWidget        *kb_focus)
993
{
994
    HildonLiveSearchPrivate *priv;
995
996
    g_return_if_fail (HILDON_IS_LIVE_SEARCH (livesearch));
997
    g_return_if_fail (GTK_IS_WIDGET (hook_widget));
998
    g_return_if_fail (GTK_IS_WIDGET (kb_focus));
999
1000
    priv = livesearch->priv;
1001
1002
    g_return_if_fail (priv->event_widget == NULL);
1003
1004
    priv->event_widget = g_object_ref (hook_widget);
1005
    priv->kb_focus_widget = g_object_ref (kb_focus);
1006
1007
    priv->key_press_id =
1008
        g_signal_connect (hook_widget, "key-press-event",
1009
                          G_CALLBACK (on_key_press_event), livesearch);
1010
1011
    priv->event_widget_destroy_id =
1012
        g_signal_connect (hook_widget, "destroy",
1013
                          G_CALLBACK (on_widget_destroy), livesearch);
1014
1015
    priv->kb_focus_widget_destroy_id =
1016
        g_signal_connect (kb_focus, "destroy",
1017
                          G_CALLBACK (on_widget_destroy), livesearch);
1018
}
1019
1020
/**
1021
 * hildon_live_search_widget_unhook:
1022
 * @livesearch: An #HildonLiveSearch widget
1023
 *
1024
 * This function unsets the hook and focus widgets which were set
1025
 * earlier using hildon_live_search_widget_hook().
1026
 *
1027
 * Since: 2.2.4
1028
 **/
1029
void
1030
hildon_live_search_widget_unhook                (HildonLiveSearch *livesearch)
1031
{
1032
    HildonLiveSearchPrivate *priv;
1033
1034
    g_return_if_fail (HILDON_IS_LIVE_SEARCH (livesearch));
1035
    priv = livesearch->priv;
1036
1037
    if (priv->event_widget == NULL)
1038
        return;
1039
1040
    /* All these variables are set together on hildon_live_search_widget_hook(),
1041
     * so event_widget != NULL implies that all these are non-NULL too */
1042
    g_return_if_fail (priv->kb_focus_widget != NULL);
1043
    g_return_if_fail (priv->key_press_id != 0);
1044
    g_return_if_fail (priv->event_widget_destroy_id != 0);
1045
    g_return_if_fail (priv->kb_focus_widget_destroy_id != 0);
1046
1047
    g_signal_handler_disconnect (priv->event_widget, priv->key_press_id);
1048
    g_signal_handler_disconnect (priv->event_widget, priv->event_widget_destroy_id);
1049
    g_signal_handler_disconnect (priv->kb_focus_widget, priv->kb_focus_widget_destroy_id);
1050
1051
    g_object_unref (priv->kb_focus_widget);
1052
    g_object_unref (priv->event_widget);
1053
1054
    priv->key_press_id = 0;
1055
    priv->event_widget_destroy_id = 0;
1056
    priv->kb_focus_widget_destroy_id = 0;
1057
1058
    priv->kb_focus_widget = NULL;
1059
    priv->event_widget = NULL;
1060
}
1061
1062
/**
1063
 * hildon_live_search_save_state:
1064
 * @livesearch: An #HildonLiveSearch widget
1065
 * @key_file: The key file to save to
1066
 *
1067
 * Saves the live search text to a #GKeyFile.
1068
 *
1069
 * Since: 2.2.4
1070
 **/
1071
void
1072
hildon_live_search_save_state                   (HildonLiveSearch *livesearch,
1073
                                                 GKeyFile         *key_file)
1074
{
1075
    const char *text;
1076
1077
    g_return_if_fail (HILDON_IS_LIVE_SEARCH (livesearch));
1078
1079
    text = gtk_entry_get_text (GTK_ENTRY (livesearch->priv->entry));
1080
    if (text) {
1081
        g_key_file_set_string (key_file,
1082
                               "LiveSearch",
1083
                               "Text",
1084
                               text);
1085
    }
1086
}
1087
1088
/**
1089
 * hildon_live_search_restore_state:
1090
 * @livesearch: An #HildonLiveSearch widget
1091
 * @key_file: The key file to read from
1092
 *
1093
 * Restores a live search widget's text from a #GKeyFile.
1094
 *
1095
 * Since: 2.2.4
1096
 **/
1097
void
1098
hildon_live_search_restore_state                (HildonLiveSearch *livesearch,
1099
                                                 GKeyFile         *key_file)
1100
{
1101
    char *text;
1102
1103
    g_return_if_fail (HILDON_IS_LIVE_SEARCH (livesearch));
1104
1105
    text = g_key_file_get_string (key_file,
1106
                                  "LiveSearch",
1107
                                  "Text",
1108
                                  NULL);
1109
    if (text) {
1110
        gtk_entry_set_text (GTK_ENTRY (livesearch->priv->entry), text);
1111
        g_free (text);
1112
    }
1113
}
1114
1115
/**
1116
 * hildon_live_search_set_visible_func:
1117
 * @livesearch: a HildonLiveSearch
1118
 * @func: a #HildonLiveSearchVisibleFunc
1119
 * @data: user data to pass to @func or %NULL
1120
 * @destroy: Destroy notifier of @data, or %NULL.
1121
 *
1122
 * Sets the function to use to determine whether a row should be
1123
 * visible when the text in the entry changes. Internally,
1124
 * gtk_tree_model_filter_set_visible_func() is used.
1125
 *
1126
 * This is convenience API to replace #GtkTreeModelFilter's visible function
1127
 * by one that gives the current text in the entry.
1128
 *
1129
 * If this function is unset, #HildonLiveSearch:text-column is used.
1130
 *
1131
 * Since: 2.2.5
1132
 **/
1133
void
1134
hildon_live_search_set_visible_func             (HildonLiveSearch           *livesearch,
1135
                                                 HildonLiveSearchVisibleFunc func,
1136
                                                 gpointer                    data,
1137
                                                 GDestroyNotify              destroy)
1138
{
1139
    HildonLiveSearchPrivate *priv;
1140
1141
    g_return_if_fail (HILDON_IS_LIVE_SEARCH (livesearch));
1142
    g_return_if_fail (func != NULL);
1143
1144
    priv = livesearch->priv;
1145
1146
    g_return_if_fail (priv->text_column == -1);
1147
1148
    if (priv->visible_destroy) {
1149
        priv->visible_destroy (priv->visible_data);
1150
    }
1151
1152
    priv->visible_func = func;
1153
    priv->visible_data = data;
1154
    priv->visible_destroy = destroy;
1155
1156
    if (priv->visible_func_set == FALSE) {
1157
        gtk_tree_model_filter_set_visible_func (priv->filter,
1158
                                                visible_func,
1159
                                                priv,
1160
                                                NULL);
1161
        priv->visible_func_set = TRUE;
1162
    }
1163
1164
    refilter (livesearch);
1165
}
1166
1167
/**
1168
 * hildon_live_search_clean_selection_map:
1169
 * @livesearch: a #HildonLiveSearch
1170
 *
1171
 * Cleans the selection map maintained by
1172
 * @livesearch. When used together with a #GtkTreeView,
1173
 * #HildonLiveSearch maintains internally a selection
1174
 * map, to make sure that selection is invariant to filtering.
1175
 *
1176
 * In some cases, you might want to clean this selection mapping, to
1177
 * ensure that after removing the entered text from @livesearch, the
1178
 * selection is not restored. This is useful in particular when you
1179
 * are using @livesearch with a #GtkTreeModel in a #GtkTreeView with
1180
 * #GtkSelectionMode set to %GTK_SELECTION_SINGLE.
1181
 *
1182
 * Since: 2.2.10
1183
 **/
1184
void
1185
hildon_live_search_clean_selection_map (HildonLiveSearch * livesearch)
1186
{
1187
    g_return_if_fail (HILDON_IS_LIVE_SEARCH (livesearch));
1188
1189
    if (livesearch->priv->selection_map) {
1190
        selection_map_destroy (livesearch->priv);
1191
        selection_map_create (livesearch->priv);
1192
        selection_map_update_map_from_selection (livesearch->priv);
1193
    }
1194
}