1 /*
2  * Copyright © 2004 Noah Levitt
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU General Public License as published by the
6  * Free Software Foundation; either version 3 of the License, or (at your
7  * option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
17  */
18 
19 #include <config.h>
20 #include <glib/gi18n-lib.h>
21 #include <gtk/gtk.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include "gucharmap-search-dialog.h"
25 #include "gucharmap-window.h"
26 
27 #define GUCHARMAP_SEARCH_DIALOG_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), gucharmap_search_dialog_get_type (), GucharmapSearchDialogPrivate))
28 
29 #define I_(string) g_intern_static_string (string)
30 
31 enum
32 {
33   SEARCH_START,
34   SEARCH_FINISH,
35   NUM_SIGNALS
36 };
37 
38 static guint gucharmap_search_dialog_signals[NUM_SIGNALS];
39 
40 enum
41 {
42   GUCHARMAP_RESPONSE_PREVIOUS,
43   GUCHARMAP_RESPONSE_NEXT
44 };
45 
46 typedef struct _GucharmapSearchDialogPrivate GucharmapSearchDialogPrivate;
47 typedef struct _GucharmapSearchState GucharmapSearchState;
48 
49 struct _GucharmapSearchState
50 {
51   GucharmapCodepointList *list;
52   gchar                  *search_string;
53   gchar                  *search_string_nfd_temp;
54   gchar                  *search_string_nfd;  /* points into search_string_nfd_temp */
55   gint                    search_string_nfd_len;
56   gint                    search_index_nfd;
57   gchar                  *search_string_nfc;
58   gint                    search_string_nfc_len;
59   gint                    search_index_nfc;
60   gint                    search_string_value;
61   gint                    start_index;
62   gint                    curr_index;
63   GucharmapDirection      increment;
64   gboolean                whole_word;
65   gboolean                annotations;
66   gint                    found_index;       /* index of the found character */
67   /* true if there are known to be no matches, or there is known to be
68    * exactly one match and it has been found */
69   gboolean                dont_search;
70   gboolean                did_before_checks;
71   gpointer                saved_data;        /* holds some data to pass back to the caller */
72   gint                    list_num_chars;    /* last_index + 1 */
73   gboolean                searching;
74   gint                    strings_checked;
75 };
76 
77 struct _GucharmapSearchDialogPrivate
78 {
79   GucharmapWindow       *guw;
80   GtkWidget             *entry;
81   GtkWidget             *whole_word_option;
82   GtkWidget             *annotations_option;
83   GucharmapSearchState  *search_state;
84   GtkWidget             *prev_button;
85   GtkWidget             *next_button;
86 };
87 
88 static void gucharmap_search_dialog_class_init (GucharmapSearchDialogClass *klass);
89 static void gucharmap_search_dialog_init       (GucharmapSearchDialog *dialog);
90 
G_DEFINE_TYPE(GucharmapSearchDialog,gucharmap_search_dialog,GTK_TYPE_DIALOG)91 G_DEFINE_TYPE (GucharmapSearchDialog, gucharmap_search_dialog, GTK_TYPE_DIALOG)
92 
93 static const gchar *
94 utf8_strcasestr (const gchar *haystack,
95                  const gchar *needle,
96 		 const gboolean whole_word)
97 {
98   gint needle_len = strlen (needle);
99   gint haystack_len = strlen (haystack);
100   const gchar *p, *q, *r;
101 
102   for (p = haystack;  p + needle_len <= haystack + haystack_len;  p = g_utf8_next_char (p))
103     {
104       if (whole_word && !(p == haystack || g_unichar_isspace(p[-1])))
105 	goto next;
106 
107       for (q = needle, r = p;  *q && *r;  q = g_utf8_next_char (q), r = g_utf8_next_char (r))
108         {
109           gunichar lc0 = g_unichar_tolower (g_utf8_get_char (r));
110           gunichar lc1 = g_unichar_tolower (g_utf8_get_char (q));
111           if (lc0 != lc1)
112             goto next;
113         }
114 
115       if (whole_word && !(r[0] == '\0' || g_unichar_isspace(r[0])))
116 	goto next;
117 
118       return p;
119 
120       next:
121         ;
122     }
123 
124   return NULL;
125 }
126 
127 static gboolean
matches(GucharmapSearchDialog * search_dialog,gunichar wc,const gchar * search_string_nfd,const gboolean annotations)128 matches (GucharmapSearchDialog *search_dialog,
129          gunichar               wc,
130          const gchar           *search_string_nfd,
131          const gboolean         annotations)
132 {
133   GucharmapSearchDialogPrivate *priv = GUCHARMAP_SEARCH_DIALOG_GET_PRIVATE (search_dialog);
134   const gchar *haystack;
135   const gchar **haystack_arr;
136   gchar *haystack_nfd;
137   gboolean matched = FALSE;
138   gint i;
139 
140   haystack = gucharmap_get_unicode_data_name (wc);
141   if (haystack)
142     {
143       priv->search_state->strings_checked++;
144 
145       /* character names are ascii, so are nfd */
146       haystack_nfd = (gchar *) haystack;
147       matched = utf8_strcasestr (haystack_nfd, search_string_nfd, priv->search_state->whole_word) != NULL;
148     }
149 
150   if (annotations)
151     {
152       haystack = gucharmap_get_unicode_kDefinition (wc);
153       if (haystack)
154 	{
155 	  priv->search_state->strings_checked++;
156 
157 	  haystack_nfd = g_utf8_normalize (haystack, -1, G_NORMALIZE_NFD);
158 	  matched = utf8_strcasestr (haystack_nfd, search_string_nfd, priv->search_state->whole_word) != NULL;
159 	  g_free (haystack_nfd);
160 	}
161 
162       if (matched)
163 	return TRUE;
164 
165       haystack_arr = gucharmap_get_nameslist_equals (wc);
166       if (haystack_arr)
167 	{
168 	  for (i = 0; haystack_arr[i] != NULL; i++)
169 	    {
170 	      priv->search_state->strings_checked++;
171 
172 	      haystack_nfd = g_utf8_normalize (haystack_arr[i], -1, G_NORMALIZE_NFD);
173 	      matched = utf8_strcasestr (haystack_nfd, search_string_nfd, priv->search_state->whole_word) != NULL;
174 	      g_free (haystack_nfd);
175 	      if (matched)
176 		break;
177 	    }
178 	  g_free (haystack_arr);
179 	}
180 
181       if (matched)
182 	return TRUE;
183 
184       haystack_arr = gucharmap_get_nameslist_stars (wc);
185       if (haystack_arr)
186 	{
187 	  for (i = 0; haystack_arr[i] != NULL; i++)
188 	    {
189 	      priv->search_state->strings_checked++;
190 
191 	      haystack_nfd = g_utf8_normalize (haystack_arr[i], -1, G_NORMALIZE_NFD);
192 	      matched = utf8_strcasestr (haystack_nfd, search_string_nfd, priv->search_state->whole_word) != NULL;
193 	      g_free (haystack_nfd);
194 	      if (matched)
195 		break;
196 	    }
197 	  g_free (haystack_arr);
198 	}
199 
200       if (matched)
201 	return TRUE;
202 
203       haystack_arr = gucharmap_get_nameslist_colons (wc);
204       if (haystack_arr)
205 	{
206 	  for (i = 0; haystack_arr[i] != NULL; i++)
207 	    {
208 	      priv->search_state->strings_checked++;
209 
210 	      haystack_nfd = g_utf8_normalize (haystack_arr[i], -1, G_NORMALIZE_NFD);
211 	      matched = utf8_strcasestr (haystack_nfd, search_string_nfd, priv->search_state->whole_word) != NULL;
212 	      g_free (haystack_nfd);
213 	      if (matched)
214 		break;
215 	    }
216 	  g_free (haystack_arr);
217 	}
218 
219       if (matched)
220 	return TRUE;
221 
222       haystack_arr = gucharmap_get_nameslist_pounds (wc);
223       if (haystack_arr)
224 	{
225 	  for (i = 0; haystack_arr[i] != NULL; i++)
226 	    {
227 	      priv->search_state->strings_checked++;
228 
229 	      haystack_nfd = g_utf8_normalize (haystack_arr[i], -1, G_NORMALIZE_NFD);
230 	      matched = utf8_strcasestr (haystack_nfd, search_string_nfd, priv->search_state->whole_word) != NULL;
231 	      g_free (haystack_nfd);
232 	      if (matched)
233 		break;
234 	    }
235 	  g_free (haystack_arr);
236 	}
237 
238       if (matched)
239 	return TRUE;
240     }
241 
242   /* XXX: other strings */
243 
244   return matched;
245 }
246 
247 /* string should have no leading spaces */
248 static gint
check_for_explicit_codepoint(const GucharmapCodepointList * list,const gchar * string)249 check_for_explicit_codepoint (const GucharmapCodepointList *list,
250                               const gchar                  *string)
251 {
252   const gchar *nptr;
253   gchar *endptr;
254   gunichar wc;
255 
256   /* check for explicit decimal codepoint */
257   nptr = string;
258   if (g_ascii_strncasecmp (string, "&#", 2) == 0)
259     nptr = string + 2;
260   else if (*string == '#')
261     nptr = string + 1;
262 
263   if (nptr != string)
264     {
265       wc = strtoul (nptr, &endptr, 10);
266       if (endptr != nptr)
267         {
268           gint index = gucharmap_codepoint_list_get_index ((GucharmapCodepointList *) list, wc);
269           if (index != -1)
270             return index;
271         }
272     }
273 
274   /* check for explicit hex code point */
275   nptr = string;
276   if (g_ascii_strncasecmp (string, "&#x", 3) == 0)
277     nptr = string + 3;
278   else if (g_ascii_strncasecmp (string, "U+", 2) == 0 || g_ascii_strncasecmp (string, "0x", 2) == 0)
279     nptr = string + 2;
280 
281   if (nptr != string)
282     {
283       wc = strtoul (nptr, &endptr, 16);
284       if (endptr != nptr)
285         {
286           gint index = gucharmap_codepoint_list_get_index ((GucharmapCodepointList *) list, wc);
287           if (index != -1)
288             return index;
289         }
290     }
291 
292   /* check for hex codepoint without any prefix */
293   /* as unicode standard assigns numerical codes to characters, its very usual
294    * to search with character code without any prefix. so moved it to here.
295    */
296   wc = strtoul (string, &endptr, 16);
297   if (endptr-3 >= string)
298     {
299       gint index = gucharmap_codepoint_list_get_index ((GucharmapCodepointList *) list, wc);
300       if (index != -1)
301 	return index;
302     }
303 
304   return -1;
305 }
306 
307 static gboolean
quick_checks_before(GucharmapSearchState * search_state)308 quick_checks_before (GucharmapSearchState *search_state)
309 {
310   if (search_state->dont_search)
311     return TRUE;
312 
313   if (search_state->did_before_checks)
314     return FALSE;
315   search_state->did_before_checks = TRUE;
316 
317   g_return_val_if_fail (search_state->search_string_nfd != NULL, FALSE);
318   g_return_val_if_fail (search_state->search_string_nfc != NULL, FALSE);
319 
320   /* caller should check for empty string */
321   if (search_state->search_string_nfd[0] == '\0')
322     {
323       search_state->dont_search = TRUE;
324       return TRUE;
325     }
326 
327   if (!search_state->whole_word)
328     {
329 
330       /* if NFD of the search string is a single character, jump to that */
331       if (search_state->search_string_nfd_len == 1)
332 	{
333 	  if (search_state->search_index_nfd != -1)
334 	    {
335 	      search_state->found_index = search_state->curr_index = search_state->search_index_nfd;
336 	      search_state->dont_search = TRUE;
337 	      return TRUE;
338 	    }
339 	}
340 
341       /* if NFC of the search string is a single character, jump to that */
342       if (search_state->search_string_nfc_len == 1)
343 	{
344 	  if (search_state->search_index_nfc != -1)
345 	    {
346 	      search_state->found_index = search_state->curr_index = search_state->search_index_nfc;
347 	      search_state->dont_search = TRUE;
348 	      return TRUE;
349 	    }
350 	}
351 
352     }
353 
354   return FALSE;
355 }
356 
357 static gboolean
quick_checks_after(GucharmapSearchState * search_state)358 quick_checks_after (GucharmapSearchState *search_state)
359 {
360   /* jump to the first nonspace character unless it’s plain ascii */
361   if (!search_state->whole_word)
362     if (search_state->search_string_nfd[0] < 0x20 || search_state->search_string_nfd[0] > 0x7e)
363       {
364 	gint index = gucharmap_codepoint_list_get_index (search_state->list, g_utf8_get_char (search_state->search_string_nfd));
365 	if (index != -1)
366 	  {
367 	    search_state->found_index = index;
368 	    search_state->dont_search = TRUE;
369 	    return TRUE;
370 	  }
371       }
372 
373   return FALSE;
374 }
375 
376 static gboolean
idle_search(GucharmapSearchDialog * search_dialog)377 idle_search (GucharmapSearchDialog *search_dialog)
378 {
379   GucharmapSearchDialogPrivate *priv = GUCHARMAP_SEARCH_DIALOG_GET_PRIVATE (search_dialog);
380   gunichar wc;
381   GTimer *timer;
382 
383   /* search without leading and tailing spaces */
384   /* with "match whole word" option, there's no need for leading and tailing spaces */
385 
386   if (quick_checks_before (priv->search_state))
387     return FALSE;
388 
389   timer = g_timer_new ();
390 
391   do
392     {
393       priv->search_state->curr_index = (priv->search_state->curr_index + priv->search_state->increment + priv->search_state->list_num_chars) % priv->search_state->list_num_chars;
394       wc = gucharmap_codepoint_list_get_char (priv->search_state->list, priv->search_state->curr_index);
395 
396       if (!gucharmap_unichar_validate (wc) || !gucharmap_unichar_isdefined (wc))
397         continue;
398 
399 
400       /* check for explicit codepoint */
401       if (priv->search_state->search_string_value != -1 && priv->search_state->curr_index == priv->search_state->search_string_value)
402 	{
403 	  priv->search_state->found_index = priv->search_state->curr_index;
404           g_timer_destroy (timer);
405 	  return FALSE;
406 	}
407 
408       /* check for other matches */
409       if (matches (search_dialog, wc, priv->search_state->search_string_nfd, priv->search_state->annotations))
410         {
411           priv->search_state->found_index = priv->search_state->curr_index;
412           g_timer_destroy (timer);
413           return FALSE;
414         }
415 
416       if (g_timer_elapsed (timer, NULL) > 0.050)
417         {
418           g_timer_destroy (timer);
419           return TRUE;
420         }
421     }
422   while (priv->search_state->curr_index != priv->search_state->start_index);
423 
424   g_timer_destroy (timer);
425 
426   if (quick_checks_after (priv->search_state))
427     return FALSE;
428 
429   priv->search_state->dont_search = TRUE;
430 
431   return FALSE;
432 }
433 
434 /**
435  * gucharmap_search_state_get_found_char:
436  * @search_state:
437  * Return value:
438  **/
439 static gunichar
gucharmap_search_state_get_found_char(GucharmapSearchState * search_state)440 gucharmap_search_state_get_found_char (GucharmapSearchState *search_state)
441 {
442   if (search_state->found_index > 0)
443     return gucharmap_codepoint_list_get_char (search_state->list, search_state->found_index);
444   else
445     return (gunichar)(-1);
446 }
447 
448 /**
449  * gucharmap_search_state_free:
450  * @search_state:
451  **/
452 static void
gucharmap_search_state_free(GucharmapSearchState * search_state)453 gucharmap_search_state_free (GucharmapSearchState *search_state)
454 {
455   g_object_unref (search_state->list);
456   g_free (search_state->search_string_nfd_temp);
457   g_free (search_state->search_string_nfc);
458   g_slice_free (GucharmapSearchState, search_state);
459 }
460 
461 /**
462  * gucharmap_search_state_new:
463  * @list: a #GucharmapCodepointList to be searched
464  * @search_string: the text to search for
465  * @start_index: the starting point within @list
466  * @direction: forward or backward
467  * @whole_word: %TRUE if it should match whole words
468  * @annotations: %TRUE if it should search in character's annotations
469  *
470  * Initializes a #GucharmapSearchState to search for the next character in
471  * the codepoint list that matches @search_string. Assumes input is valid.
472  *
473  * Return value: the new #GucharmapSearchState.
474  **/
475 static GucharmapSearchState *
gucharmap_search_state_new(GucharmapCodepointList * list,const gchar * search_string,gint start_index,GucharmapDirection direction,gboolean whole_word,gboolean annotations)476 gucharmap_search_state_new (GucharmapCodepointList       *list,
477                             const gchar                  *search_string,
478                             gint                          start_index,
479                             GucharmapDirection            direction,
480                             gboolean                      whole_word,
481                             gboolean                      annotations)
482 {
483   GucharmapSearchState *search_state;
484   gchar *p, *q, *r;
485 
486   g_assert (direction == GUCHARMAP_DIRECTION_BACKWARD || direction == GUCHARMAP_DIRECTION_FORWARD);
487 
488   search_state = g_slice_new (GucharmapSearchState);
489 
490   search_state->list = g_object_ref (list);
491   search_state->list_num_chars = gucharmap_codepoint_list_get_last_index (search_state->list) + 1;
492 
493   search_state->search_string = g_strdup (search_string);
494   search_state->search_string_nfd_temp = g_utf8_normalize (search_string, -1, G_NORMALIZE_NFD);
495 
496   search_state->increment = direction;
497   search_state->whole_word = whole_word;
498   search_state->annotations = annotations;
499   search_state->did_before_checks = FALSE;
500 
501   search_state->found_index = -1;
502   search_state->dont_search = FALSE;
503 
504   search_state->start_index = start_index;
505   search_state->curr_index = start_index;
506 
507   /* set end of search string to last non-space character */
508   for (p = q = r = search_state->search_string_nfd_temp;
509        p[0] != '\0';
510        q = p, p = g_utf8_next_char (p))
511     if (g_unichar_isspace (g_utf8_get_char (p)) && !g_unichar_isspace (g_utf8_get_char (q)))
512 	r = p;
513   if (!g_unichar_isspace (g_utf8_get_char (q)))
514       r = p;
515   r[0] = '\0';
516 
517   /* caller should check not to search for empty string */
518   g_return_val_if_fail (r != search_state->search_string_nfd_temp, FALSE);
519 
520   /* NFD */
521   /* set pointer to first non-space character in the search string */
522   for (search_state->search_string_nfd = search_state->search_string_nfd_temp;
523        *search_state->search_string_nfd != '\0'
524        && g_unichar_isspace (g_utf8_get_char (search_state->search_string_nfd));
525        search_state->search_string_nfd = g_utf8_next_char (search_state->search_string_nfd))
526     ;
527   search_state->search_string_nfd_len = g_utf8_strlen (search_state->search_string_nfd, -1);
528   if (search_state->search_string_nfd_len == 1)
529     search_state->search_index_nfd = gucharmap_codepoint_list_get_index (search_state->list, g_utf8_get_char (search_state->search_string_nfd));
530   else
531     search_state->search_index_nfd = -1;
532 
533   /* NFC */
534   search_state->search_string_nfc = g_utf8_normalize (search_state->search_string_nfd, -1, G_NORMALIZE_NFC);
535   search_state->search_string_nfc_len = g_utf8_strlen (search_state->search_string_nfc, -1);
536   if (search_state->search_string_nfc_len == 1)
537     search_state->search_index_nfc = gucharmap_codepoint_list_get_index (search_state->list, g_utf8_get_char (search_state->search_string_nfc));
538   else
539     search_state->search_index_nfc = -1;
540 
541   /* INDEX */
542   search_state->search_string_value = check_for_explicit_codepoint (search_state->list, search_state->search_string_nfd);
543 
544   search_state->searching = FALSE;
545   return search_state;
546 }
547 
548 static void
information_dialog(GucharmapSearchDialog * search_dialog,const gchar * message)549 information_dialog (GucharmapSearchDialog *search_dialog,
550                     const gchar           *message)
551 {
552   GucharmapSearchDialogPrivate *priv = GUCHARMAP_SEARCH_DIALOG_GET_PRIVATE (search_dialog);
553   GtkWidget *dialog;
554 
555   dialog = gtk_message_dialog_new (gtk_widget_get_visible (GTK_WIDGET (search_dialog)) ?
556                                      GTK_WINDOW (search_dialog) :
557                                      GTK_WINDOW (priv->guw),
558                                    GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
559                                    GTK_MESSAGE_INFO,
560                                    GTK_BUTTONS_OK,
561                                    "%s", message);
562   gtk_window_set_title (GTK_WINDOW (dialog), _("Information"));
563   gtk_window_set_icon_name (GTK_WINDOW (dialog), GUCHARMAP_ICON_NAME);
564   gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
565   g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL);
566 
567   gtk_window_present (GTK_WINDOW (dialog));
568 }
569 
570 static void
search_completed(GucharmapSearchDialog * search_dialog)571 search_completed (GucharmapSearchDialog *search_dialog)
572 {
573   GucharmapSearchDialogPrivate *priv = GUCHARMAP_SEARCH_DIALOG_GET_PRIVATE (search_dialog);
574   gunichar found_char = gucharmap_search_state_get_found_char (priv->search_state);
575 
576   priv->search_state->searching = FALSE;
577 
578   g_signal_emit (search_dialog, gucharmap_search_dialog_signals[SEARCH_FINISH], 0, found_char);
579 
580   if (found_char == (gunichar)(-1))
581     {
582       information_dialog (search_dialog, _("Not found."));
583       gtk_widget_set_sensitive (priv->prev_button, FALSE);
584       gtk_widget_set_sensitive (priv->next_button, FALSE);
585     }
586 
587   if (gtk_widget_get_realized (GTK_WIDGET (search_dialog)))
588       gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (search_dialog)), NULL);
589 }
590 
591 static gboolean
_entry_is_empty(GtkEntry * entry)592 _entry_is_empty (GtkEntry *entry)
593 {
594   const gchar *text = gtk_entry_get_text (entry);
595   const gchar *p;	/* points into text */
596 
597   for (p = text;
598        p[0] != '\0' && g_unichar_isspace (g_utf8_get_char (p));
599        p = g_utf8_next_char (p))
600     ;
601   return p[0] == '\0';
602 }
603 
604 static void
_gucharmap_search_dialog_fire_search(GucharmapSearchDialog * search_dialog,GucharmapDirection direction)605 _gucharmap_search_dialog_fire_search (GucharmapSearchDialog *search_dialog,
606                                       GucharmapDirection     direction)
607 {
608   GucharmapSearchDialogPrivate *priv = GUCHARMAP_SEARCH_DIALOG_GET_PRIVATE (search_dialog);
609   GucharmapCodepointList *list;
610   gunichar start_char;
611   gint start_index;
612   GdkCursor *cursor;
613 
614   if (priv->search_state && priv->search_state->searching) /* Already searching */
615     return;
616 
617   if (gtk_widget_get_realized (GTK_WIDGET (search_dialog))) {
618     cursor = gdk_cursor_new_for_display (gtk_widget_get_display (GTK_WIDGET (search_dialog)), GDK_WATCH);
619     gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (search_dialog)), cursor);
620     g_object_unref (cursor);
621   }
622 
623   list = gucharmap_charmap_get_book_codepoint_list (priv->guw->charmap);
624   if (!list)
625     return;
626 
627   if (priv->search_state == NULL
628       || list != priv->search_state->list
629       || strcmp (priv->search_state->search_string, gtk_entry_get_text (GTK_ENTRY (priv->entry))) != 0
630       || priv->search_state->whole_word != gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->whole_word_option))
631       || priv->search_state->annotations != gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->annotations_option)) )
632     {
633       if (priv->search_state)
634         gucharmap_search_state_free (priv->search_state);
635 
636       start_char = gucharmap_charmap_get_active_character (priv->guw->charmap);
637       start_index = gucharmap_codepoint_list_get_index (list, start_char);
638       priv->search_state = gucharmap_search_state_new (list, gtk_entry_get_text (GTK_ENTRY (priv->entry)),
639 			      start_index, direction,
640 			      gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->whole_word_option)),
641 			      gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->annotations_option)) );
642     }
643   else
644     {
645       start_char = gucharmap_charmap_get_active_character (priv->guw->charmap);
646       priv->search_state->start_index = gucharmap_codepoint_list_get_index (list, start_char);
647       priv->search_state->curr_index = priv->search_state->start_index;
648       priv->search_state->increment = direction;
649     }
650 
651   priv->search_state->searching = TRUE;
652   priv->search_state->strings_checked = 0;
653 
654   g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, (GSourceFunc) idle_search, search_dialog, (GDestroyNotify) search_completed);
655   g_signal_emit (search_dialog, gucharmap_search_dialog_signals[SEARCH_START], 0);
656 
657   g_object_unref (list);
658 }
659 
660 void
gucharmap_search_dialog_start_search(GucharmapSearchDialog * search_dialog,GucharmapDirection direction)661 gucharmap_search_dialog_start_search (GucharmapSearchDialog *search_dialog,
662                                       GucharmapDirection     direction)
663 {
664   GucharmapSearchDialogPrivate *priv = GUCHARMAP_SEARCH_DIALOG_GET_PRIVATE (search_dialog);
665 
666   if (!_entry_is_empty (GTK_ENTRY (priv->entry)))
667     _gucharmap_search_dialog_fire_search (search_dialog, direction);
668   else
669     gtk_window_present (GTK_WINDOW (search_dialog));
670 }
671 
672 static void
search_find_response(GtkDialog * dialog,gint response)673 search_find_response (GtkDialog *dialog,
674                       gint       response)
675 {
676   GucharmapSearchDialog *search_dialog = GUCHARMAP_SEARCH_DIALOG (dialog);
677   GucharmapSearchDialogPrivate *priv = GUCHARMAP_SEARCH_DIALOG_GET_PRIVATE (search_dialog);
678 
679   switch (response)
680     {
681       case GUCHARMAP_RESPONSE_PREVIOUS:
682         _gucharmap_search_dialog_fire_search (search_dialog, GUCHARMAP_DIRECTION_BACKWARD);
683         break;
684 
685       case GUCHARMAP_RESPONSE_NEXT:
686         _gucharmap_search_dialog_fire_search (search_dialog, GUCHARMAP_DIRECTION_FORWARD);
687         break;
688 
689       default:
690         gtk_widget_hide (GTK_WIDGET (search_dialog));
691         break;
692     }
693 
694   gtk_editable_select_region (GTK_EDITABLE (priv->entry), 0, -1);
695 }
696 
697 static void
entry_changed(GObject * object,GucharmapSearchDialog * search_dialog)698 entry_changed (GObject               *object,
699                GucharmapSearchDialog *search_dialog)
700 {
701   GucharmapSearchDialogPrivate *priv = GUCHARMAP_SEARCH_DIALOG_GET_PRIVATE (search_dialog);
702   gboolean is_empty;
703 
704   is_empty = _entry_is_empty (GTK_ENTRY (priv->entry));
705 
706   gtk_widget_set_sensitive (priv->prev_button, !is_empty);
707   gtk_widget_set_sensitive (priv->next_button, !is_empty);
708 }
709 
710 static void
set_button_stock_image_and_label(GtkButton * button,gchar * stock_id,gchar * mnemonic)711 set_button_stock_image_and_label (GtkButton *button,
712                                   gchar     *stock_id,
713                                   gchar     *mnemonic)
714 {
715   GtkWidget *image;
716 
717   image = gtk_image_new_from_stock (stock_id, GTK_ICON_SIZE_BUTTON);
718   gtk_button_set_image (button, image);
719   gtk_button_set_label (button, mnemonic);
720   gtk_button_set_use_underline (button, TRUE);
721 }
722 
723 static void
gucharmap_search_dialog_init(GucharmapSearchDialog * search_dialog)724 gucharmap_search_dialog_init (GucharmapSearchDialog *search_dialog)
725 {
726   GucharmapSearchDialogPrivate *priv = GUCHARMAP_SEARCH_DIALOG_GET_PRIVATE (search_dialog);
727   GtkWidget *grid, *label, *content_area;
728 
729   content_area = gtk_dialog_get_content_area (GTK_DIALOG (search_dialog));
730 
731   /* follow hig guidelines */
732   gtk_window_set_title (GTK_WINDOW (search_dialog), _("Find"));
733   gtk_container_set_border_width (GTK_CONTAINER (search_dialog), 6);
734   gtk_window_set_destroy_with_parent (GTK_WINDOW (search_dialog), TRUE);
735   gtk_box_set_spacing (GTK_BOX (content_area), 12);
736   gtk_window_set_resizable (GTK_WINDOW (search_dialog), FALSE);
737 
738   g_signal_connect (search_dialog, "delete-event", G_CALLBACK (gtk_widget_hide_on_delete), NULL);
739 
740   /* add buttons */
741   gtk_dialog_add_button (GTK_DIALOG (search_dialog), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
742 
743   priv->prev_button = gtk_button_new ();
744   gtk_widget_set_can_default (priv->prev_button, TRUE);
745   set_button_stock_image_and_label (GTK_BUTTON (priv->prev_button), GTK_STOCK_GO_BACK, _("_Previous"));
746   gtk_dialog_add_action_widget (GTK_DIALOG (search_dialog), priv->prev_button, GUCHARMAP_RESPONSE_PREVIOUS);
747   gtk_widget_show (priv->prev_button);
748 
749   priv->next_button = gtk_button_new ();
750   gtk_widget_set_can_default (priv->next_button, TRUE);
751   gtk_widget_show (priv->next_button);
752   set_button_stock_image_and_label (GTK_BUTTON (priv->next_button), GTK_STOCK_GO_FORWARD, _("_Next"));
753   gtk_dialog_add_action_widget (GTK_DIALOG (search_dialog), priv->next_button, GUCHARMAP_RESPONSE_NEXT);
754 
755   gtk_dialog_set_default_response (GTK_DIALOG (search_dialog), GUCHARMAP_RESPONSE_NEXT);
756   gtk_dialog_set_alternative_button_order (GTK_DIALOG (search_dialog),
757                                            GUCHARMAP_RESPONSE_PREVIOUS,
758                                            GUCHARMAP_RESPONSE_NEXT,
759                                            GTK_RESPONSE_CLOSE,
760                                            -1);
761 
762   grid = gtk_grid_new ();
763   gtk_grid_set_column_spacing (GTK_GRID (grid), 12);
764   gtk_widget_show (grid);
765   gtk_container_set_border_width (GTK_CONTAINER (grid), 6);
766   gtk_box_pack_start (GTK_BOX (content_area), grid, FALSE, FALSE, 0);
767 
768   label = gtk_label_new_with_mnemonic (_("_Search:"));
769   gtk_widget_show (label);
770   gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 1, 1);
771 
772   priv->entry = gtk_entry_new ();
773   gtk_widget_show (priv->entry);
774   gtk_widget_set_hexpand (priv->entry, TRUE);
775   gtk_entry_set_activates_default (GTK_ENTRY (priv->entry), TRUE);
776   gtk_grid_attach (GTK_GRID (grid), priv->entry, 1, 0, 1, 1);
777   g_signal_connect (priv->entry, "changed", G_CALLBACK (entry_changed), search_dialog);
778 
779   priv->whole_word_option = gtk_check_button_new_with_mnemonic (_("Match _whole word"));
780   gtk_widget_show (priv->whole_word_option);
781   gtk_box_pack_start (GTK_BOX (content_area), priv->whole_word_option, FALSE, FALSE, 0);
782   g_signal_connect (priv->whole_word_option, "toggled", G_CALLBACK (entry_changed), search_dialog);
783 
784   priv->annotations_option = gtk_check_button_new_with_mnemonic (_("Search in character _details"));
785   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(priv->annotations_option), TRUE);
786   gtk_widget_show (priv->annotations_option);
787   gtk_box_pack_start (GTK_BOX (content_area), priv->annotations_option, FALSE, FALSE, 0);
788   g_signal_connect (priv->annotations_option, "toggled", G_CALLBACK (entry_changed), search_dialog);
789 
790   gtk_label_set_mnemonic_widget (GTK_LABEL (label), priv->entry);
791 
792   /* since the entry is empty */
793   gtk_widget_set_sensitive (priv->prev_button, FALSE);
794   gtk_widget_set_sensitive (priv->next_button, FALSE);
795 
796   priv->search_state = NULL;
797   priv->guw = NULL;
798 
799   g_signal_connect (GTK_DIALOG (search_dialog), "response", G_CALLBACK (search_find_response), NULL);
800 }
801 
802 static void
gucharmap_search_dialog_finalize(GObject * object)803 gucharmap_search_dialog_finalize (GObject *object)
804 {
805   GucharmapSearchDialog *search_dialog = GUCHARMAP_SEARCH_DIALOG (object);
806   GucharmapSearchDialogPrivate *priv = GUCHARMAP_SEARCH_DIALOG_GET_PRIVATE (search_dialog);
807 
808   if (priv->search_state)
809     gucharmap_search_state_free (priv->search_state);
810 
811   G_OBJECT_CLASS (gucharmap_search_dialog_parent_class)->finalize (object);
812 }
813 
814 static void
gucharmap_search_dialog_class_init(GucharmapSearchDialogClass * clazz)815 gucharmap_search_dialog_class_init (GucharmapSearchDialogClass *clazz)
816 {
817   g_type_class_add_private (clazz, sizeof (GucharmapSearchDialogPrivate));
818 
819   G_OBJECT_CLASS (clazz)->finalize = gucharmap_search_dialog_finalize;
820 
821   clazz->search_start = NULL;
822   clazz->search_finish = NULL;
823 
824   gucharmap_search_dialog_signals[SEARCH_START] =
825       g_signal_new (I_("search-start"), gucharmap_search_dialog_get_type (), G_SIGNAL_RUN_FIRST,
826                     G_STRUCT_OFFSET (GucharmapSearchDialogClass, search_start), NULL, NULL,
827                     g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
828   gucharmap_search_dialog_signals[SEARCH_FINISH] =
829       g_signal_new (I_("search-finish"), gucharmap_search_dialog_get_type (), G_SIGNAL_RUN_FIRST,
830                     G_STRUCT_OFFSET (GucharmapSearchDialogClass, search_finish), NULL, NULL,
831                     g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT);
832 }
833 
834 GtkWidget *
gucharmap_search_dialog_new(GucharmapWindow * guw)835 gucharmap_search_dialog_new (GucharmapWindow *guw)
836 {
837   GucharmapSearchDialog *search_dialog = g_object_new (gucharmap_search_dialog_get_type (), NULL);
838   GucharmapSearchDialogPrivate *priv = GUCHARMAP_SEARCH_DIALOG_GET_PRIVATE (search_dialog);
839 
840   priv->guw = guw;
841 
842   gtk_window_set_transient_for (GTK_WINDOW (search_dialog), GTK_WINDOW (guw));
843 
844   if (guw)
845     gtk_window_set_icon (GTK_WINDOW (search_dialog), gtk_window_get_icon (GTK_WINDOW (guw)));
846 
847   return GTK_WIDGET (search_dialog);
848 }
849 
850 void
gucharmap_search_dialog_present(GucharmapSearchDialog * search_dialog)851 gucharmap_search_dialog_present (GucharmapSearchDialog *search_dialog)
852 {
853   gtk_widget_grab_focus (GUCHARMAP_SEARCH_DIALOG_GET_PRIVATE (search_dialog)->entry);
854   gtk_window_present (GTK_WINDOW (search_dialog));
855 }
856 
857 void
gucharmap_search_dialog_set_search(GucharmapSearchDialog * search_dialog,const char * search_string)858 gucharmap_search_dialog_set_search (GucharmapSearchDialog *search_dialog,
859                                     const char            *search_string)
860 {
861   GucharmapSearchDialogPrivate *priv;
862   g_return_if_fail (GUCHARMAP_IS_SEARCH_DIALOG (search_dialog));
863   g_return_if_fail (search_string != NULL);
864 
865   priv = GUCHARMAP_SEARCH_DIALOG_GET_PRIVATE (search_dialog);
866   gtk_entry_set_text (GTK_ENTRY (priv->entry), search_string);
867 }
868 
869 gdouble
gucharmap_search_dialog_get_completed(GucharmapSearchDialog * search_dialog)870 gucharmap_search_dialog_get_completed (GucharmapSearchDialog *search_dialog)
871 {
872   GucharmapSearchDialogPrivate *priv = GUCHARMAP_SEARCH_DIALOG_GET_PRIVATE (search_dialog);
873 
874   if (priv->search_state == NULL || !priv->search_state->searching)
875     return -1.0;
876   else
877     {
878       gdouble total = gucharmap_get_unicode_data_name_count () + gucharmap_get_unihan_count ();
879       return (gdouble) priv->search_state->strings_checked / total;
880     }
881 }
882