Merge lp:~alecu/ubuntuone-client/highlight-search into lp:ubuntuone-client

Proposed by Alejandro J. Cura
Status: Merged
Approved by: Rodrigo Moya
Approved revision: 586
Merged at revision: 588
Proposed branch: lp:~alecu/ubuntuone-client/highlight-search
Merge into: lp:ubuntuone-client
Diff against target: 554 lines (+325/-14)
6 files modified
.bzrignore (+1/-0)
nautilus/Makefile.am (+13/-1)
nautilus/contacts-view.c (+28/-13)
nautilus/highlight.c (+161/-0)
nautilus/highlight.h (+29/-0)
nautilus/test-highlight.c (+93/-0)
To merge this branch: bzr merge lp:~alecu/ubuntuone-client/highlight-search
Reviewer Review Type Date Requested Status
Rodrigo Moya (community) Approve
John Lenton (community) Approve
Review via email: mp+30434@code.launchpad.net

Commit message

Highlight the strings that were found in the contacts search

Description of the change

Highlight the strings that were found in the contacts search

To post a comment you must log in.
Revision history for this message
John Lenton (chipaca) wrote :

Yes, it does!

review: Approve
Revision history for this message
Rodrigo Moya (rodrigo-moya) wrote :

Looks great, very good code! Only one thing:

379 + while (*needle != NULL) {
380 + glong needle_len = g_utf8_strlen (*needle, -1);
381 + if (needle_len < 1) {
382 + needle++;
383 + continue;
384 + }
385 + gchar *search_start = folded_haystack;
386 + gchar *start_ptr = g_strstr_len (search_start,
387 + -1,
388 + *needle);

Don't declare variables after executed code, since that would make it fail when running strict compilation (C99). But not holding this branch for this, so fix it in another one, please

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2010-07-14 19:51:54 +0000
3+++ .bzrignore 2010-07-20 16:05:59 +0000
4@@ -49,6 +49,7 @@
5 ubuntuone-marshallers.[ch]
6
7 nautilus/test-contacts-picker
8+nautilus/test-highlight
9
10 libsyncdaemon/syncdaemon-marshal.[ch]
11 libsyncdaemon/test-libsyncdaemon
12
13=== modified file 'nautilus/Makefile.am'
14--- nautilus/Makefile.am 2010-06-18 12:27:03 +0000
15+++ nautilus/Makefile.am 2010-07-20 16:05:59 +0000
16@@ -27,6 +27,8 @@
17 error-dialog.c \
18 location-widget.c \
19 location-widget.h \
20+ highlight.c \
21+ highlight.h \
22 u1-contacts-picker.c \
23 u1-contacts-picker.h \
24 ubuntuone-marshallers.c \
25@@ -40,7 +42,8 @@
26 $(NAUTILUS_LIBS) \
27 $(top_builddir)/libsyncdaemon/libsyncdaemon-1.0.la
28
29-noinst_PROGRAMS = test-contacts-picker
30+noinst_PROGRAMS = test-contacts-picker test-highlight
31+
32 test_contacts_picker_CFLAGS = \
33 $(NAUTILUS_CFLAGS)
34 test_contacts_picker_SOURCES = \
35@@ -49,10 +52,19 @@
36 add-contact-dialog.h \
37 contacts-view.c \
38 contacts-view.h \
39+ highlight.c \
40+ highlight.h \
41 u1-contacts-picker.c \
42 u1-contacts-picker.h
43 test_contacts_picker_LDADD = $(NAUTILUS_LIBS)
44
45+test_highlight_CFLAGS = $(NAUTILUS_CFLAGS)
46+test_highlight_SOURCES = \
47+ highlight.c \
48+ highlight.h \
49+ test-highlight.c
50+test_highlight_LDADD = $(NAUTILUS_LIBS)
51+
52 EXTRA_DIST = \
53 ubuntuone-marshallers.list
54
55
56=== modified file 'nautilus/contacts-view.c'
57--- nautilus/contacts-view.c 2010-07-15 10:17:37 +0000
58+++ nautilus/contacts-view.c 2010-07-20 16:05:59 +0000
59@@ -22,6 +22,7 @@
60 #include "config.h"
61 #include <glib/gi18n.h>
62 #include "contacts-view.h"
63+#include "highlight.h"
64
65 #ifdef BUILD_GRID_VIEW
66 #define AVATAR_SIZE 64
67@@ -40,6 +41,7 @@
68
69 typedef struct {
70 gchar *name;
71+ gchar *markedup_name;
72 gchar *email;
73 GdkPixbuf *pixbuf;
74 } SelectedContactInfo;
75@@ -166,6 +168,7 @@
76
77 sci = g_new0 (SelectedContactInfo, 1);
78 sci->name = g_strdup (name);
79+ sci->markedup_name = g_markup_escape_text (name, -1);
80 sci->email = g_strdup (email);
81 sci->pixbuf = gdk_pixbuf_ref (icon);
82 g_hash_table_insert (cv->selection, g_strdup (name), sci);
83@@ -188,7 +191,7 @@
84 static void
85 add_one_contact (ContactsView *cv,
86 const gchar *name,
87- const gchar *markup,
88+ const gchar *markedup_name,
89 const gchar *email,
90 EContact *contact,
91 GHashTable *selection_hash)
92@@ -197,7 +200,6 @@
93 EContactPhoto *photo = NULL;
94 GtkTreeModel *model;
95 GdkPixbuf *pixbuf;
96- gchar *escaped_markup;
97
98 /* Get the pixbuf for this contact */
99 if (contact != NULL) {
100@@ -293,18 +295,16 @@
101 } else
102 gtk_list_store_append (GTK_LIST_STORE (model), &new_row);
103
104- escaped_markup = g_markup_escape_text (markup, strlen (markup));
105 gtk_list_store_set (GTK_LIST_STORE (model), &new_row,
106 CONTACTS_VIEW_COLUMN_NAME, name,
107- CONTACTS_VIEW_COLUMN_MARKUP, escaped_markup,
108+ CONTACTS_VIEW_COLUMN_MARKUP, markedup_name,
109 CONTACTS_VIEW_COLUMN_EMAIL, email,
110 CONTACTS_VIEW_COLUMN_PIXBUF, pixbuf,
111 -1);
112- g_free (escaped_markup);
113 }
114
115 static void
116-add_contacts (ContactsView *cv, GList *contacts, GHashTable *selection_hash)
117+add_contacts (ContactsView *cv, GList *contacts, GHashTable *selection_hash, gchar *search_string)
118 {
119 GList *l;
120 GtkTreeModel *model;
121@@ -315,6 +315,7 @@
122 GList *emails_list, *al;
123 EContactName *contact_name;
124 gchar *full_name = NULL;
125+ gchar *markedup_name = NULL;
126
127 contact_name = (EContactName *) e_contact_get (contact, E_CONTACT_NAME);
128 if (contact_name != NULL)
129@@ -336,10 +337,12 @@
130 g_warning ("Contact without name or email addresses");
131 }
132
133+ markedup_name = highlight_result (search_string, full_name);
134+
135 /* We add the selected items when searching, so ignore them here */
136 if (!g_hash_table_lookup (selection_hash, (gconstpointer) full_name)) {
137 if (email != NULL) {
138- add_one_contact (cv, full_name, full_name, email, contact, selection_hash);
139+ add_one_contact (cv, full_name, markedup_name, email, contact, selection_hash);
140 cv->matched_contacts += 1;
141 }
142 }
143@@ -347,6 +350,7 @@
144 g_list_foreach (emails_list, (GFunc) e_vcard_attribute_free, NULL);
145 g_list_free (emails_list);
146 e_contact_name_free (contact_name);
147+ g_free (markedup_name);
148 g_free (full_name);
149 }
150
151@@ -361,7 +365,11 @@
152 }
153
154 static void
155-append_selected_to_model (GtkWidget *view, const gchar *contact_name, const gchar *contact_email, GdkPixbuf *pixbuf)
156+append_selected_to_model (GtkWidget *view,
157+ const gchar *contact_name,
158+ const gchar *contact_markedup_name,
159+ const gchar *contact_email,
160+ GdkPixbuf *pixbuf)
161 {
162 GtkTreeIter new_row;
163 GtkListStore *model;
164@@ -375,7 +383,7 @@
165 gtk_list_store_prepend (model, &new_row);
166 gtk_list_store_set (GTK_LIST_STORE (model), &new_row,
167 CONTACTS_VIEW_COLUMN_NAME, contact_name,
168- CONTACTS_VIEW_COLUMN_MARKUP, contact_name,
169+ CONTACTS_VIEW_COLUMN_MARKUP, contact_markedup_name,
170 CONTACTS_VIEW_COLUMN_EMAIL, contact_email,
171 CONTACTS_VIEW_COLUMN_PIXBUF, pixbuf,
172 -1);
173@@ -399,13 +407,14 @@
174 if (g_hash_table_lookup (cv->selection, sci->name) != NULL)
175 return;
176
177- append_selected_to_model (cv->contacts_list, sci->name, sci->email, sci->pixbuf);
178+ append_selected_to_model (cv->contacts_list, sci->name, sci->markedup_name, sci->email, sci->pixbuf);
179 }
180
181 typedef struct {
182 ContactsView *cv;
183 GHashTable *selection_hash;
184 EBookQuery *query;
185+ gchar *search_string;
186 } GetContactsCallbackData;
187
188 static void
189@@ -414,7 +423,7 @@
190 GetContactsCallbackData *gccd = user_data;
191
192 if (status == E_BOOK_ERROR_OK) {
193- add_contacts (gccd->cv, contacts, gccd->selection_hash);
194+ add_contacts (gccd->cv, contacts, gccd->selection_hash, gccd->search_string);
195 if (gccd->selection_hash != gccd->cv->selection) {
196 /* If it's a separate selection, add all contacts to the views */
197 g_hash_table_foreach (gccd->selection_hash, (GHFunc) foreach_selected_to_model_cb, gccd->cv);
198@@ -426,6 +435,7 @@
199 }
200
201 e_book_query_unref (gccd->query);
202+ g_free (gccd->search_string);
203 g_free (gccd);
204 }
205
206@@ -437,6 +447,7 @@
207 gccd = g_new0 (GetContactsCallbackData, 1);
208 gccd->cv = cv;
209 gccd->query = e_book_query_any_field_contains (search_string);
210+ gccd->search_string = g_strdup (search_string);
211 if (selection_hash == cv->selection)
212 gccd->selection_hash = selection_hash;
213 else
214@@ -471,6 +482,8 @@
215 if (sci != NULL) {
216 if (sci->name != NULL)
217 g_free (sci->name);
218+ if (sci->markedup_name != NULL)
219+ g_free (sci->markedup_name);
220 if (sci->email != NULL)
221 g_free (sci->email);
222 if (sci->pixbuf != NULL)
223@@ -534,7 +547,7 @@
224 gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (cv->contacts_list), -1,
225 "Name",
226 gtk_cell_renderer_text_new (),
227- "text", CONTACTS_VIEW_COLUMN_NAME,
228+ "markup", CONTACTS_VIEW_COLUMN_MARKUP,
229 NULL);
230 gtk_tree_selection_set_mode (gtk_tree_view_get_selection (GTK_TREE_VIEW (cv->contacts_list)),
231 GTK_SELECTION_MULTIPLE);
232@@ -601,6 +614,7 @@
233
234 new_sci = g_new0 (SelectedContactInfo, 1);
235 new_sci->name = g_strdup (old_sci->name);
236+ new_sci->markedup_name = g_markup_escape_text (old_sci->name, -1);
237 new_sci->email = g_strdup (old_sci->email);
238 new_sci->pixbuf = gdk_pixbuf_ref (old_sci->pixbuf);
239 g_hash_table_insert (tmp_selection, g_strdup (old_sci->name), new_sci);
240@@ -686,6 +700,7 @@
241 /* First add the new contact to the list of selected ones */
242 sci = g_new0 (SelectedContactInfo, 1);
243 sci->name = g_strdup (contact_name);
244+ sci->markedup_name = g_markup_escape_text (contact_name, -1);
245 sci->email = g_strdup (contact_email);
246 pixbuf = gtk_icon_theme_load_icon (icon_theme, "avatar-default", AVATAR_SIZE, 0, NULL);
247 sci->pixbuf = g_object_ref (pixbuf);
248@@ -697,7 +712,7 @@
249 save_recently_used_list (cv);
250
251 /* And now add it to the icon views */
252- append_selected_to_model (cv->contacts_list, contact_name, contact_email, pixbuf);
253+ append_selected_to_model (cv->contacts_list, contact_name, sci->markedup_name, contact_email, pixbuf);
254
255 g_object_unref (pixbuf);
256
257
258=== added file 'nautilus/highlight.c'
259--- nautilus/highlight.c 1970-01-01 00:00:00 +0000
260+++ nautilus/highlight.c 2010-07-20 16:05:59 +0000
261@@ -0,0 +1,161 @@
262+/*
263+ * Ubuntu One Nautilus plugin
264+ *
265+ * Authors: Alejandro J. Cura <alecu@canonical.com>
266+ *
267+ * Copyright 2010 Canonical Ltd.
268+ *
269+ * This program is free software: you can redistribute it and/or modify it
270+ * under the terms of the GNU General Public License version 3, as published
271+ * by the Free Software Foundation.
272+ *
273+ * This program is distributed in the hope that it will be useful, but
274+ * WITHOUT ANY WARRANTY; without even the implied warranties of
275+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
276+ * PURPOSE. See the GNU General Public License for more details.
277+ *
278+ * You should have received a copy of the GNU General Public License along
279+ * with this program. If not, see <http://www.gnu.org/licenses/>.
280+ *
281+ */
282+
283+#include <string.h>
284+#include <glib.h>
285+
286+static gint
287+compare_glongs (gconstpointer a,
288+ gconstpointer b,
289+ gpointer user_data)
290+{
291+ return (glong) a - (glong) b;
292+}
293+
294+
295+static void
296+tree_of_arrays_insert (GTree *tree,
297+ gpointer key,
298+ gpointer value)
299+{
300+ GPtrArray *array = g_tree_lookup (tree, key);
301+ if (array == NULL) {
302+ array = g_ptr_array_new ();
303+ g_tree_insert (tree, key, array);
304+ }
305+ g_ptr_array_add (array, value);
306+}
307+
308+
309+static void
310+destroy_tree_array (gpointer data)
311+{
312+ g_ptr_array_free (data, TRUE);
313+}
314+
315+typedef struct {
316+ GString *built_string;
317+ gchar *source_string;
318+ gchar *source_cursor;
319+} BuildResultsData;
320+
321+
322+static void
323+append_array_strings (gpointer data,
324+ gpointer user_data)
325+{
326+ g_string_append (user_data, data);
327+}
328+
329+
330+static gboolean
331+build_results (gpointer key,
332+ gpointer value,
333+ gpointer data)
334+{
335+ BuildResultsData* results_data = data;
336+ glong tag_start = (glong)key;
337+ gchar *tag_start_ptr = g_utf8_offset_to_pointer (results_data->source_string,
338+ tag_start);
339+ glong len = tag_start_ptr - results_data->source_cursor;
340+
341+ gchar *escaped_str = g_markup_escape_text (results_data->source_cursor,
342+ len);
343+
344+ g_string_append (results_data->built_string,
345+ escaped_str);
346+ g_free (escaped_str);
347+ results_data->source_cursor += len;
348+
349+ g_ptr_array_foreach (value,
350+ append_array_strings,
351+ results_data->built_string);
352+ return FALSE;
353+}
354+
355+
356+gchar *
357+highlight_result(gchar *needles, gchar *haystack)
358+{
359+ gchar **split_needles;
360+ GTree *result_parts;
361+ gchar *folded_needles;
362+ gchar *folded_haystack;
363+ BuildResultsData results_data;
364+
365+ folded_needles = g_utf8_casefold (needles, -1);
366+ folded_haystack = g_utf8_casefold (haystack, -1);
367+
368+ results_data.built_string = g_string_new ("");
369+ results_data.source_string = haystack;
370+ results_data.source_cursor = haystack;
371+
372+ result_parts = g_tree_new_full (compare_glongs,
373+ NULL,
374+ NULL,
375+ destroy_tree_array);
376+
377+ split_needles = g_strsplit (folded_needles, " ", 0);
378+ gchar **needle = split_needles;
379+ while (*needle != NULL) {
380+ glong needle_len = g_utf8_strlen (*needle, -1);
381+ if (needle_len < 1) {
382+ needle++;
383+ continue;
384+ }
385+ gchar *search_start = folded_haystack;
386+ gchar *start_ptr = g_strstr_len (search_start,
387+ -1,
388+ *needle);
389+ while (start_ptr != NULL) {
390+ glong start = g_utf8_pointer_to_offset (folded_haystack,
391+ start_ptr);
392+ glong end = start + g_utf8_strlen (*needle, -1);
393+ tree_of_arrays_insert (result_parts,
394+ (gpointer) start,
395+ "<b>");
396+ tree_of_arrays_insert (result_parts,
397+ (gpointer) end,
398+ "</b>");
399+ search_start = g_utf8_next_char (start_ptr);
400+ start_ptr = g_strstr_len (search_start,
401+ -1,
402+ *needle);
403+ }
404+ needle++;
405+ }
406+ g_free (folded_needles);
407+ g_free (folded_haystack);
408+
409+
410+ g_tree_foreach (result_parts, build_results, &results_data);
411+
412+ gchar *escaped_str = g_markup_escape_text (results_data.source_cursor,
413+ -1);
414+ g_string_append (results_data.built_string,
415+ escaped_str);
416+ g_free (escaped_str);
417+
418+ g_tree_destroy (result_parts);
419+ g_strfreev (split_needles);
420+ return g_string_free (results_data.built_string,
421+ FALSE);
422+}
423
424=== added file 'nautilus/highlight.h'
425--- nautilus/highlight.h 1970-01-01 00:00:00 +0000
426+++ nautilus/highlight.h 2010-07-20 16:05:59 +0000
427@@ -0,0 +1,29 @@
428+/*
429+ * Ubuntu One Nautilus plugin
430+ *
431+ * Authors: Alejandro J. Cura <alecu@canonical.com>
432+ *
433+ * Copyright 2010 Canonical Ltd.
434+ *
435+ * This program is free software: you can redistribute it and/or modify it
436+ * under the terms of the GNU General Public License version 3, as published
437+ * by the Free Software Foundation.
438+ *
439+ * This program is distributed in the hope that it will be useful, but
440+ * WITHOUT ANY WARRANTY; without even the implied warranties of
441+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
442+ * PURPOSE. See the GNU General Public License for more details.
443+ *
444+ * You should have received a copy of the GNU General Public License along
445+ * with this program. If not, see <http://www.gnu.org/licenses/>.
446+ *
447+ */
448+
449+#ifndef __HIGHLIGHT_H__
450+#define __HIGHLIGHT_H__
451+
452+#include <glib.h>
453+
454+gchar *highlight_result(gchar *search_string, gchar *result);
455+
456+#endif
457
458=== added file 'nautilus/test-highlight.c'
459--- nautilus/test-highlight.c 1970-01-01 00:00:00 +0000
460+++ nautilus/test-highlight.c 2010-07-20 16:05:59 +0000
461@@ -0,0 +1,93 @@
462+/*
463+ * Authors: Alejandro J. Cura <alecu@canonical.com>
464+ *
465+ * Copyright 2010 Canonical Ltd.
466+ *
467+ * This program is free software: you can redistribute it and/or modify it
468+ * under the terms of the GNU General Public License version 3, as published
469+ * by the Free Software Foundation.
470+ *
471+ * This program is distributed in the hope that it will be useful, but
472+ * WITHOUT ANY WARRANTY; without even the implied warranties of
473+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
474+ * PURPOSE. See the GNU General Public License for more details.
475+ *
476+ * You should have received a copy of the GNU General Public License along
477+ * with this program. If not, see <http://www.gnu.org/licenses/>.
478+ *
479+ */
480+
481+#include "highlight.h"
482+
483+#include <glib.h>
484+
485+#define ASSERT_HIGHLIGHT(a,b,c) \
486+ ret = highlight_result (a, b); \
487+ g_assert_cmpstr (ret, ==, c); \
488+ g_free (ret);
489+
490+static void
491+test_highlight_matches (void)
492+{
493+ gchar *ret = NULL;
494+ // match at beginning
495+ ASSERT_HIGHLIGHT ("john", "john q. public", "<b>john</b> q. public");
496+
497+ // match in the middle
498+ ASSERT_HIGHLIGHT ("john", "andrew john q. public", "andrew <b>john</b> q. public");
499+
500+ // no match
501+ ASSERT_HIGHLIGHT ("john", "andrew q. public", "andrew q. public");
502+
503+ // double match
504+ ASSERT_HIGHLIGHT ("jo sm", "john smith", "<b>jo</b>hn <b>sm</b>ith");
505+ ASSERT_HIGHLIGHT ("john", "john johnson", "<b>john</b> <b>john</b>son");
506+
507+ // empty search
508+ ASSERT_HIGHLIGHT ("", "john q. public", "john q. public");
509+
510+ // empty search with entities
511+ ASSERT_HIGHLIGHT ("", "john & public", "john &amp; public");
512+
513+ // empty search term
514+ ASSERT_HIGHLIGHT (" ", "john q. public", "john q. public");
515+
516+ // empty result
517+ ASSERT_HIGHLIGHT ("john", "", "");
518+
519+ // case insensitive match
520+ ASSERT_HIGHLIGHT ("john", "John Smith", "<b>John</b> Smith");
521+ ASSERT_HIGHLIGHT ("john", "Brian JOHNSON", "Brian <b>JOHN</b>SON");
522+ ASSERT_HIGHLIGHT ("br jo", "Brian JOHNSON", "<b>Br</b>ian <b>JO</b>HNSON");
523+
524+ // nested match
525+ ASSERT_HIGHLIGHT ("edward wa", "edward wall", "<b>ed<b>wa</b>rd</b> <b>wa</b>ll");
526+
527+ // utf-8 strings
528+ ASSERT_HIGHLIGHT ("ñandÚ", "Ñandú", "<b>Ñandú</b>");
529+
530+ // xml entities in the search
531+ ASSERT_HIGHLIGHT ("john &", "john sons", "<b>john</b> sons");
532+
533+ // xml entities in the result
534+ ASSERT_HIGHLIGHT ("john sons", "john & sons", "<b>john</b> &amp; <b>sons</b>");
535+
536+ // xml entities everywhere
537+ ASSERT_HIGHLIGHT ("john & sons", "john & sons", "<b>john</b> <b>&amp;</b> <b>sons</b>");
538+
539+ // make sure the name of the entities is not highlighted
540+ ASSERT_HIGHLIGHT ("john amp sons", "john & sons", "<b>john</b> &amp; <b>sons</b>");
541+}
542+
543+int
544+main (int argc, gchar *argv[])
545+{
546+ GMainLoop *main_loop;
547+
548+ g_type_init ();
549+ g_test_init (&argc, &argv, NULL);
550+
551+ g_test_add_func ("/testcontacts/TestHighlightMatches", test_highlight_matches);
552+
553+ return g_test_run ();
554+}

Subscribers

People subscribed via source and target branches