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