1 /* gdict-defbox.c - display widget for dictionary definitions
2 *
3 * Copyright (C) 2005-2006 Emmanuele Bassi <ebassi@gmail.com>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this library. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 /**
20 * SECTION:gdict-defbox
21 * @short_description: Display the list of definitions for a word
22 *
23 * The #GdictDefbox widget is a composite widget showing the list of
24 * definitions for a word. It queries the passed #GdictContext and displays
25 * the list of #GdictDefinition<!-- -->s obtained.
26 *
27 * It provides syntax highlighting, clickable links and an embedded find
28 * bar.
29 */
30
31 #include "config.h"
32
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <stdarg.h>
37
38 #include <gdk/gdkkeysyms.h>
39 #include <gtk/gtk.h>
40 #include <glib/gi18n-lib.h>
41
42 #include "gdict-defbox.h"
43 #include "gdict-utils.h"
44 #include "gdict-debug.h"
45 #include "gdict-private.h"
46 #include "gdict-enum-types.h"
47 #include "gdict-marshal.h"
48
49 #define QUERY_MARGIN 48
50 #define ERROR_MARGIN 24
51
52 typedef struct
53 {
54 GdictDefinition *definition;
55
56 gint begin;
57 } Definition;
58
59 struct _GdictDefboxPrivate
60 {
61 GtkWidget *text_view;
62
63 /* the "find" pane */
64 GtkWidget *find_pane;
65 GtkWidget *find_entry;
66 GtkWidget *find_next;
67 GtkWidget *find_prev;
68
69 GtkWidget *progress_dialog;
70
71 GtkTextBuffer *buffer;
72
73 GdictContext *context;
74 GSList *definitions;
75
76 gchar *word;
77 gchar *database;
78 gchar *font_name;
79
80 guint show_find : 1;
81 guint is_searching : 1;
82 guint is_hovering : 1;
83
84 GdkCursor *hand_cursor;
85 GdkCursor *regular_cursor;
86
87 guint start_id;
88 guint end_id;
89 guint define_id;
90 guint error_id;
91 guint hide_timeout;
92
93 GtkTextTag *link_tag;
94 GtkTextTag *visited_link_tag;
95 };
96
97 enum
98 {
99 PROP_0,
100
101 PROP_CONTEXT,
102 PROP_WORD,
103 PROP_DATABASE,
104 PROP_FONT_NAME,
105 PROP_COUNT
106 };
107
108 enum
109 {
110 SHOW_FIND,
111 HIDE_FIND,
112 FIND_NEXT,
113 FIND_PREVIOUS,
114 LINK_CLICKED,
115 SELECTION_CHANGED,
116
117 LAST_SIGNAL
118 };
119
120 static guint gdict_defbox_signals[LAST_SIGNAL] = { 0 };
121
G_DEFINE_TYPE_WITH_PRIVATE(GdictDefbox,gdict_defbox,GTK_TYPE_BOX)122 G_DEFINE_TYPE_WITH_PRIVATE (GdictDefbox, gdict_defbox, GTK_TYPE_BOX)
123
124 static Definition *
125 definition_new (void)
126 {
127 Definition *def;
128
129 def = g_slice_new (Definition);
130 def->definition = NULL;
131 def->begin = -1;
132
133 return def;
134 }
135
136 static void
definition_free(Definition * def)137 definition_free (Definition *def)
138 {
139 if (!def)
140 return;
141
142 gdict_definition_unref (def->definition);
143 g_slice_free (Definition, def);
144 }
145
146 static void
gdict_defbox_dispose(GObject * gobject)147 gdict_defbox_dispose (GObject *gobject)
148 {
149 GdictDefbox *defbox = GDICT_DEFBOX (gobject);
150 GdictDefboxPrivate *priv = defbox->priv;
151
152 if (priv->start_id)
153 {
154 g_signal_handler_disconnect (priv->context, priv->start_id);
155 g_signal_handler_disconnect (priv->context, priv->end_id);
156 g_signal_handler_disconnect (priv->context, priv->define_id);
157
158 priv->start_id = 0;
159 priv->end_id = 0;
160 priv->define_id = 0;
161 }
162
163 if (priv->error_id)
164 {
165 g_signal_handler_disconnect (priv->context, priv->error_id);
166 priv->error_id = 0;
167 }
168
169 g_clear_object (&priv->context);
170 g_clear_object (&priv->buffer);
171 g_clear_object (&priv->hand_cursor);
172 g_clear_object (&priv->regular_cursor);
173
174 G_OBJECT_CLASS (gdict_defbox_parent_class)->dispose (gobject);
175 }
176
177 static void
gdict_defbox_finalize(GObject * object)178 gdict_defbox_finalize (GObject *object)
179 {
180 GdictDefbox *defbox = GDICT_DEFBOX (object);
181 GdictDefboxPrivate *priv = defbox->priv;
182
183 g_free (priv->database);
184 g_free (priv->word);
185 g_free (priv->font_name);
186
187 if (priv->definitions)
188 {
189 g_slist_foreach (priv->definitions, (GFunc) definition_free, NULL);
190 g_slist_free (priv->definitions);
191
192 priv->definitions = NULL;
193 }
194
195 G_OBJECT_CLASS (gdict_defbox_parent_class)->finalize (object);
196 }
197
198 static void
set_gdict_context(GdictDefbox * defbox,GdictContext * context)199 set_gdict_context (GdictDefbox *defbox,
200 GdictContext *context)
201 {
202 GdictDefboxPrivate *priv;
203
204 g_assert (GDICT_IS_DEFBOX (defbox));
205
206 priv = defbox->priv;
207 if (priv->context)
208 {
209 if (priv->start_id)
210 {
211 GDICT_NOTE (DEFBOX, "Removing old context handlers");
212
213 g_signal_handler_disconnect (priv->context, priv->start_id);
214 g_signal_handler_disconnect (priv->context, priv->define_id);
215 g_signal_handler_disconnect (priv->context, priv->end_id);
216
217 priv->start_id = 0;
218 priv->end_id = 0;
219 priv->define_id = 0;
220 }
221
222 if (priv->error_id)
223 {
224 g_signal_handler_disconnect (priv->context, priv->error_id);
225
226 priv->error_id = 0;
227 }
228
229 GDICT_NOTE (DEFBOX, "Removing old context");
230
231 g_object_unref (G_OBJECT (priv->context));
232 }
233
234 if (!context)
235 return;
236
237 if (!GDICT_IS_CONTEXT (context))
238 {
239 g_warning ("Object of type '%s' instead of a GdictContext\n",
240 g_type_name (G_OBJECT_TYPE (context)));
241 return;
242 }
243
244 GDICT_NOTE (DEFBOX, "Setting new context");
245
246 priv->context = context;
247 g_object_ref (G_OBJECT (priv->context));
248 }
249
250 static void
gdict_defbox_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)251 gdict_defbox_set_property (GObject *object,
252 guint prop_id,
253 const GValue *value,
254 GParamSpec *pspec)
255 {
256 GdictDefbox *defbox = GDICT_DEFBOX (object);
257 GdictDefboxPrivate *priv = defbox->priv;
258
259 switch (prop_id)
260 {
261 case PROP_WORD:
262 gdict_defbox_lookup (defbox, g_value_get_string (value));
263 break;
264 case PROP_CONTEXT:
265 set_gdict_context (defbox, g_value_get_object (value));
266 break;
267 case PROP_DATABASE:
268 g_free (priv->database);
269 priv->database = g_strdup (g_value_get_string (value));
270 break;
271 case PROP_FONT_NAME:
272 gdict_defbox_set_font_name (defbox, g_value_get_string (value));
273 break;
274 default:
275 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
276 break;
277 }
278 }
279
280 static void
gdict_defbox_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)281 gdict_defbox_get_property (GObject *object,
282 guint prop_id,
283 GValue *value,
284 GParamSpec *pspec)
285 {
286 GdictDefbox *defbox = GDICT_DEFBOX (object);
287 GdictDefboxPrivate *priv = defbox->priv;
288
289 switch (prop_id)
290 {
291 case PROP_WORD:
292 g_value_set_string (value, priv->word);
293 break;
294 case PROP_CONTEXT:
295 g_value_set_object (value, priv->context);
296 break;
297 case PROP_DATABASE:
298 g_value_set_string (value, priv->database);
299 break;
300 case PROP_FONT_NAME:
301 g_value_set_string (value, priv->font_name);
302 break;
303 default:
304 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
305 break;
306 }
307 }
308
309 /*
310 * this code has been copied from gtksourceview; it's the implementation
311 * for case-insensitive search in a GtkTextBuffer. this is non-trivial, as
312 * searches on a utf-8 text stream involve a norm(casefold(norm(utf8)))
313 * operation which can be costly on large buffers. luckily for us, it's
314 * not the case on a set of definitions.
315 */
316
317 #define GTK_TEXT_UNKNOWN_CHAR 0xFFFC
318
319 /* this function acts like g_utf8_offset_to_pointer() except that if it finds a
320 * decomposable character it consumes the decomposition length from the given
321 * offset. So it's useful when the offset was calculated for the normalized
322 * version of str, but we need a pointer to str itself. */
323 static const gchar *
pointer_from_offset_skipping_decomp(const gchar * str,gint offset)324 pointer_from_offset_skipping_decomp (const gchar *str, gint offset)
325 {
326 gchar *casefold, *normal;
327 const gchar *p, *q;
328
329 p = str;
330 while (offset > 0)
331 {
332 q = g_utf8_next_char (p);
333 casefold = g_utf8_casefold (p, q - p);
334 normal = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
335 offset -= g_utf8_strlen (normal, -1);
336 g_free (casefold);
337 g_free (normal);
338 p = q;
339 }
340 return p;
341 }
342
343 static gboolean
exact_prefix_cmp(const gchar * string,const gchar * prefix,guint prefix_len)344 exact_prefix_cmp (const gchar *string,
345 const gchar *prefix,
346 guint prefix_len)
347 {
348 GUnicodeType type;
349
350 if (strncmp (string, prefix, prefix_len) != 0)
351 return FALSE;
352 if (string[prefix_len] == '\0')
353 return TRUE;
354
355 type = g_unichar_type (g_utf8_get_char (string + prefix_len));
356
357 /* If string contains prefix, check that prefix is not followed
358 * by a unicode mark symbol, e.g. that trailing 'a' in prefix
359 * is not part of two-char a-with-hat symbol in string. */
360
361 #if GLIB_CHECK_VERSION(2, 29, 14)
362 return type != G_UNICODE_SPACING_MARK &&
363 type != G_UNICODE_ENCLOSING_MARK &&
364 type != G_UNICODE_NON_SPACING_MARK;
365 #else
366 return type != G_UNICODE_COMBINING_MARK &&
367 type != G_UNICODE_ENCLOSING_MARK &&
368 type != G_UNICODE_NON_SPACING_MARK;
369 #endif
370 }
371
372 static const gchar *
utf8_strcasestr(const gchar * haystack,const gchar * needle)373 utf8_strcasestr (const gchar *haystack, const gchar *needle)
374 {
375 gsize needle_len;
376 gsize haystack_len;
377 const gchar *ret = NULL;
378 gchar *p;
379 gchar *casefold;
380 gchar *caseless_haystack;
381 gint i;
382
383 g_return_val_if_fail (haystack != NULL, NULL);
384 g_return_val_if_fail (needle != NULL, NULL);
385
386 casefold = g_utf8_casefold (haystack, -1);
387 caseless_haystack = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
388 g_free (casefold);
389
390 needle_len = g_utf8_strlen (needle, -1);
391 haystack_len = g_utf8_strlen (caseless_haystack, -1);
392
393 if (needle_len == 0)
394 {
395 ret = (gchar *)haystack;
396 goto finally_1;
397 }
398
399 if (haystack_len < needle_len)
400 {
401 ret = NULL;
402 goto finally_1;
403 }
404
405 p = (gchar*)caseless_haystack;
406 needle_len = strlen (needle);
407 i = 0;
408
409 while (*p)
410 {
411 if (exact_prefix_cmp (p, needle, needle_len))
412 {
413 ret = pointer_from_offset_skipping_decomp (haystack, i);
414 goto finally_1;
415 }
416
417 p = g_utf8_next_char (p);
418 i++;
419 }
420
421 finally_1:
422 g_free (caseless_haystack);
423
424 return ret;
425 }
426
427 static const gchar *
utf8_strrcasestr(const gchar * haystack,const gchar * needle)428 utf8_strrcasestr (const gchar *haystack, const gchar *needle)
429 {
430 gsize needle_len;
431 gsize haystack_len;
432 const gchar *ret = NULL;
433 gchar *p;
434 gchar *casefold;
435 gchar *caseless_haystack;
436 gint i;
437
438 g_return_val_if_fail (haystack != NULL, NULL);
439 g_return_val_if_fail (needle != NULL, NULL);
440
441 casefold = g_utf8_casefold (haystack, -1);
442 caseless_haystack = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
443 g_free (casefold);
444
445 needle_len = g_utf8_strlen (needle, -1);
446 haystack_len = g_utf8_strlen (caseless_haystack, -1);
447
448 if (needle_len == 0)
449 {
450 ret = (gchar *)haystack;
451 goto finally_1;
452 }
453
454 if (haystack_len < needle_len)
455 {
456 ret = NULL;
457 goto finally_1;
458 }
459
460 i = haystack_len - needle_len;
461 p = g_utf8_offset_to_pointer (caseless_haystack, i);
462 needle_len = strlen (needle);
463
464 while (p >= caseless_haystack)
465 {
466 if (exact_prefix_cmp (p, needle, needle_len))
467 {
468 ret = pointer_from_offset_skipping_decomp (haystack, i);
469 goto finally_1;
470 }
471
472 p = g_utf8_prev_char (p);
473 i--;
474 }
475
476 finally_1:
477 g_free (caseless_haystack);
478
479 return ret;
480 }
481
482 static gboolean
utf8_caselessnmatch(const char * s1,const char * s2,gssize n1,gssize n2)483 utf8_caselessnmatch (const char *s1, const char *s2,
484 gssize n1, gssize n2)
485 {
486 gchar *casefold;
487 gchar *normalized_s1;
488 gchar *normalized_s2;
489 gint len_s1;
490 gint len_s2;
491 gboolean ret = FALSE;
492
493 g_return_val_if_fail (s1 != NULL, FALSE);
494 g_return_val_if_fail (s2 != NULL, FALSE);
495 g_return_val_if_fail (n1 > 0, FALSE);
496 g_return_val_if_fail (n2 > 0, FALSE);
497
498 casefold = g_utf8_casefold (s1, n1);
499 normalized_s1 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
500 g_free (casefold);
501
502 casefold = g_utf8_casefold (s2, n2);
503 normalized_s2 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
504 g_free (casefold);
505
506 len_s1 = strlen (normalized_s1);
507 len_s2 = strlen (normalized_s2);
508
509 if (len_s1 < len_s2)
510 goto finally_2;
511
512 ret = (strncmp (normalized_s1, normalized_s2, len_s2) == 0);
513
514 finally_2:
515 g_free (normalized_s1);
516 g_free (normalized_s2);
517
518 return ret;
519 }
520
521 /* FIXME: total horror */
522 static gboolean
char_is_invisible(const GtkTextIter * iter)523 char_is_invisible (const GtkTextIter *iter)
524 {
525 GSList *tags;
526 gboolean invisible = FALSE;
527 tags = gtk_text_iter_get_tags (iter);
528 while (tags)
529 {
530 gboolean this_invisible, invisible_set;
531 g_object_get (tags->data, "invisible", &this_invisible,
532 "invisible-set", &invisible_set, NULL);
533 if (invisible_set)
534 invisible = this_invisible;
535 tags = g_slist_delete_link (tags, tags);
536 }
537 return invisible;
538 }
539
540 static void
forward_chars_with_skipping(GtkTextIter * iter,gint count,gboolean skip_invisible,gboolean skip_nontext,gboolean skip_decomp)541 forward_chars_with_skipping (GtkTextIter *iter,
542 gint count,
543 gboolean skip_invisible,
544 gboolean skip_nontext,
545 gboolean skip_decomp)
546 {
547 gint i;
548
549 g_return_if_fail (count >= 0);
550
551 i = count;
552
553 while (i > 0)
554 {
555 gboolean ignored = FALSE;
556
557 /* minimal workaround to avoid the infinite loop of bug #168247.
558 * It doesn't fix the problemjust the symptom...
559 */
560 if (gtk_text_iter_is_end (iter))
561 return;
562
563 if (skip_nontext && gtk_text_iter_get_char (iter) == GTK_TEXT_UNKNOWN_CHAR)
564 ignored = TRUE;
565
566 /* FIXME: char_is_invisible() gets list of tags for each char there,
567 and checks every tag. It doesn't sound like a good idea. */
568 if (!ignored && skip_invisible && char_is_invisible (iter))
569 ignored = TRUE;
570
571 if (!ignored && skip_decomp)
572 {
573 /* being UTF8 correct sucks; this accounts for extra
574 offsets coming from canonical decompositions of
575 UTF8 characters (e.g. accented characters) which
576 g_utf8_normalize() performs */
577 gchar *normal;
578 gchar buffer[6];
579 gint buffer_len;
580
581 buffer_len = g_unichar_to_utf8 (gtk_text_iter_get_char (iter), buffer);
582 normal = g_utf8_normalize (buffer, buffer_len, G_NORMALIZE_NFD);
583 i -= (g_utf8_strlen (normal, -1) - 1);
584 g_free (normal);
585 }
586
587 gtk_text_iter_forward_char (iter);
588
589 if (!ignored)
590 --i;
591 }
592 }
593
594 static gboolean
lines_match(const GtkTextIter * start,const gchar ** lines,gboolean visible_only,gboolean slice,GtkTextIter * match_start,GtkTextIter * match_end)595 lines_match (const GtkTextIter *start,
596 const gchar **lines,
597 gboolean visible_only,
598 gboolean slice,
599 GtkTextIter *match_start,
600 GtkTextIter *match_end)
601 {
602 GtkTextIter next;
603 gchar *line_text;
604 const gchar *found;
605 gint offset;
606
607 if (*lines == NULL || **lines == '\0')
608 {
609 if (match_start)
610 *match_start = *start;
611 if (match_end)
612 *match_end = *start;
613 return TRUE;
614 }
615
616 next = *start;
617 gtk_text_iter_forward_line (&next);
618
619 /* No more text in buffer, but *lines is nonempty */
620 if (gtk_text_iter_equal (start, &next))
621 return FALSE;
622
623 if (slice)
624 {
625 if (visible_only)
626 line_text = gtk_text_iter_get_visible_slice (start, &next);
627 else
628 line_text = gtk_text_iter_get_slice (start, &next);
629 }
630 else
631 {
632 if (visible_only)
633 line_text = gtk_text_iter_get_visible_text (start, &next);
634 else
635 line_text = gtk_text_iter_get_text (start, &next);
636 }
637
638 if (match_start) /* if this is the first line we're matching */
639 {
640 found = utf8_strcasestr (line_text, *lines);
641 }
642 else
643 {
644 /* If it's not the first line, we have to match from the
645 * start of the line.
646 */
647 if (utf8_caselessnmatch (line_text, *lines, strlen (line_text),
648 strlen (*lines)))
649 found = line_text;
650 else
651 found = NULL;
652 }
653
654 if (found == NULL)
655 {
656 g_free (line_text);
657 return FALSE;
658 }
659
660 /* Get offset to start of search string */
661 offset = g_utf8_strlen (line_text, found - line_text);
662
663 next = *start;
664
665 /* If match start needs to be returned, set it to the
666 * start of the search string.
667 */
668 forward_chars_with_skipping (&next, offset, visible_only, !slice, FALSE);
669 if (match_start)
670 {
671 *match_start = next;
672 }
673
674 /* Go to end of search string */
675 forward_chars_with_skipping (&next, g_utf8_strlen (*lines, -1), visible_only, !slice, TRUE);
676
677 g_free (line_text);
678
679 ++lines;
680
681 if (match_end)
682 *match_end = next;
683
684 /* pass NULL for match_start, since we don't need to find the
685 * start again.
686 */
687 return lines_match (&next, lines, visible_only, slice, NULL, match_end);
688 }
689
690 static gboolean
backward_lines_match(const GtkTextIter * start,const gchar ** lines,gboolean visible_only,gboolean slice,GtkTextIter * match_start,GtkTextIter * match_end)691 backward_lines_match (const GtkTextIter *start,
692 const gchar **lines,
693 gboolean visible_only,
694 gboolean slice,
695 GtkTextIter *match_start,
696 GtkTextIter *match_end)
697 {
698 GtkTextIter line, next;
699 gchar *line_text;
700 const gchar *found;
701 gint offset;
702
703 if (*lines == NULL || **lines == '\0')
704 {
705 if (match_start)
706 *match_start = *start;
707 if (match_end)
708 *match_end = *start;
709 return TRUE;
710 }
711
712 line = next = *start;
713 if (gtk_text_iter_get_line_offset (&next) == 0)
714 {
715 if (!gtk_text_iter_backward_line (&next))
716 return FALSE;
717 }
718 else
719 gtk_text_iter_set_line_offset (&next, 0);
720
721 if (slice)
722 {
723 if (visible_only)
724 line_text = gtk_text_iter_get_visible_slice (&next, &line);
725 else
726 line_text = gtk_text_iter_get_slice (&next, &line);
727 }
728 else
729 {
730 if (visible_only)
731 line_text = gtk_text_iter_get_visible_text (&next, &line);
732 else
733 line_text = gtk_text_iter_get_text (&next, &line);
734 }
735
736 if (match_start) /* if this is the first line we're matching */
737 {
738 found = utf8_strrcasestr (line_text, *lines);
739 }
740 else
741 {
742 /* If it's not the first line, we have to match from the
743 * start of the line.
744 */
745 if (utf8_caselessnmatch (line_text, *lines, strlen (line_text),
746 strlen (*lines)))
747 found = line_text;
748 else
749 found = NULL;
750 }
751
752 if (found == NULL)
753 {
754 g_free (line_text);
755 return FALSE;
756 }
757
758 /* Get offset to start of search string */
759 offset = g_utf8_strlen (line_text, found - line_text);
760
761 forward_chars_with_skipping (&next, offset, visible_only, !slice, FALSE);
762
763 /* If match start needs to be returned, set it to the
764 * start of the search string.
765 */
766 if (match_start)
767 {
768 *match_start = next;
769 }
770
771 /* Go to end of search string */
772 forward_chars_with_skipping (&next, g_utf8_strlen (*lines, -1), visible_only, !slice, TRUE);
773
774 g_free (line_text);
775
776 ++lines;
777
778 if (match_end)
779 *match_end = next;
780
781 /* try to match the rest of the lines forward, passing NULL
782 * for match_start so lines_match will try to match the entire
783 * line */
784 return lines_match (&next, lines, visible_only,
785 slice, NULL, match_end);
786 }
787
788 /* strsplit () that retains the delimiter as part of the string. */
789 static gchar **
breakup_string(const char * string,const char * delimiter,gint max_tokens)790 breakup_string (const char *string,
791 const char *delimiter,
792 gint max_tokens)
793 {
794 GSList *string_list = NULL, *slist;
795 gchar **str_array, *s, *casefold, *new_string;
796 guint i, n = 1;
797
798 g_return_val_if_fail (string != NULL, NULL);
799 g_return_val_if_fail (delimiter != NULL, NULL);
800
801 if (max_tokens < 1)
802 max_tokens = G_MAXINT;
803
804 s = strstr (string, delimiter);
805 if (s)
806 {
807 guint delimiter_len = strlen (delimiter);
808
809 do
810 {
811 guint len;
812
813 len = s - string + delimiter_len;
814 new_string = g_new (gchar, len + 1);
815 strncpy (new_string, string, len);
816 new_string[len] = 0;
817 casefold = g_utf8_casefold (new_string, -1);
818 g_free (new_string);
819 new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
820 g_free (casefold);
821 string_list = g_slist_prepend (string_list, new_string);
822 n++;
823 string = s + delimiter_len;
824 s = strstr (string, delimiter);
825 } while (--max_tokens && s);
826 }
827
828 if (*string)
829 {
830 n++;
831 casefold = g_utf8_casefold (string, -1);
832 new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
833 g_free (casefold);
834 string_list = g_slist_prepend (string_list, new_string);
835 }
836
837 str_array = g_new (gchar*, n);
838
839 i = n - 1;
840
841 str_array[i--] = NULL;
842 for (slist = string_list; slist; slist = slist->next)
843 str_array[i--] = slist->data;
844
845 g_slist_free (string_list);
846
847 return str_array;
848 }
849
850 static gboolean
gdict_defbox_iter_forward_search(const GtkTextIter * iter,const gchar * str,GtkTextIter * match_start,GtkTextIter * match_end,const GtkTextIter * limit)851 gdict_defbox_iter_forward_search (const GtkTextIter *iter,
852 const gchar *str,
853 GtkTextIter *match_start,
854 GtkTextIter *match_end,
855 const GtkTextIter *limit)
856 {
857 gchar **lines = NULL;
858 GtkTextIter match;
859 gboolean retval = FALSE;
860 GtkTextIter search;
861
862 g_return_val_if_fail (iter != NULL, FALSE);
863 g_return_val_if_fail (str != NULL, FALSE);
864
865 if (limit && gtk_text_iter_compare (iter, limit) >= 0)
866 return FALSE;
867
868 if (*str == '\0')
869 {
870 /* If we can move one char, return the empty string there */
871 match = *iter;
872
873 if (gtk_text_iter_forward_char (&match))
874 {
875 if (limit && gtk_text_iter_equal (&match, limit))
876 return FALSE;
877
878 if (match_start)
879 *match_start = match;
880
881 if (match_end)
882 *match_end = match;
883
884 return TRUE;
885 }
886 else
887 return FALSE;
888 }
889
890 /* locate all lines */
891 lines = breakup_string (str, "\n", -1);
892
893 search = *iter;
894
895 /* This loop has an inefficient worst-case, where
896 * gtk_text_iter_get_text () is called repeatedly on
897 * a single line.
898 */
899 do
900 {
901 GtkTextIter end;
902 gboolean res;
903
904 if (limit && gtk_text_iter_compare (&search, limit) >= 0)
905 break;
906
907 res = lines_match (&search, (const gchar**)lines,
908 TRUE, FALSE,
909 &match, &end);
910 if (res)
911 {
912 if (limit == NULL ||
913 (limit && gtk_text_iter_compare (&end, limit) <= 0))
914 {
915 retval = TRUE;
916
917 if (match_start)
918 *match_start = match;
919
920 if (match_end)
921 *match_end = end;
922 }
923
924 break;
925 }
926 } while (gtk_text_iter_forward_line (&search));
927
928 g_strfreev ((gchar**) lines);
929
930 return retval;
931 }
932
933 static gboolean
gdict_defbox_iter_backward_search(const GtkTextIter * iter,const gchar * str,GtkTextIter * match_start,GtkTextIter * match_end,const GtkTextIter * limit)934 gdict_defbox_iter_backward_search (const GtkTextIter *iter,
935 const gchar *str,
936 GtkTextIter *match_start,
937 GtkTextIter *match_end,
938 const GtkTextIter *limit)
939 {
940 gchar **lines = NULL;
941 GtkTextIter match;
942 gboolean retval = FALSE;
943 GtkTextIter search;
944
945 g_return_val_if_fail (iter != NULL, FALSE);
946 g_return_val_if_fail (str != NULL, FALSE);
947
948 if (limit && gtk_text_iter_compare (iter, limit) <= 0)
949 return FALSE;
950
951 if (*str == '\0')
952 {
953 /* If we can move one char, return the empty string there */
954 match = *iter;
955
956 if (gtk_text_iter_backward_char (&match))
957 {
958 if (limit && gtk_text_iter_equal (&match, limit))
959 return FALSE;
960
961 if (match_start)
962 *match_start = match;
963
964 if (match_end)
965 *match_end = match;
966
967 return TRUE;
968 }
969 else
970 return FALSE;
971 }
972
973 /* locate all lines */
974 lines = breakup_string (str, "\n", -1);
975
976 search = *iter;
977
978 /* This loop has an inefficient worst-case, where
979 * gtk_text_iter_get_text () is called repeatedly on
980 * a single line.
981 */
982 while (TRUE)
983 {
984 GtkTextIter end;
985 gboolean res;
986
987 if (limit && gtk_text_iter_compare (&search, limit) <= 0)
988 break;
989
990 res = backward_lines_match (&search, (const gchar**)lines,
991 TRUE, FALSE,
992 &match, &end);
993 if (res)
994 {
995 if (limit == NULL ||
996 (limit && gtk_text_iter_compare (&end, limit) > 0))
997 {
998 retval = TRUE;
999
1000 if (match_start)
1001 *match_start = match;
1002
1003 if (match_end)
1004 *match_end = end;
1005
1006 }
1007
1008 break;
1009 }
1010
1011 if (gtk_text_iter_get_line_offset (&search) == 0)
1012 {
1013 if (!gtk_text_iter_backward_line (&search))
1014 break;
1015 }
1016 else
1017 gtk_text_iter_set_line_offset (&search, 0);
1018 }
1019
1020 g_strfreev ((gchar**) lines);
1021
1022 return retval;
1023 }
1024
1025 static gboolean
gdict_defbox_find_backward(GdictDefbox * defbox,const gchar * text)1026 gdict_defbox_find_backward (GdictDefbox *defbox,
1027 const gchar *text)
1028 {
1029 GdictDefboxPrivate *priv = defbox->priv;
1030 GtkTextIter start_iter, end_iter;
1031 GtkTextIter match_start, match_end;
1032 GtkTextIter iter;
1033 GtkTextMark *last_search;
1034 gboolean res;
1035
1036 g_assert (GTK_IS_TEXT_BUFFER (priv->buffer));
1037
1038 gtk_text_buffer_get_bounds (priv->buffer, &start_iter, &end_iter);
1039
1040 /* if there already has been another result, begin from there */
1041 last_search = gtk_text_buffer_get_mark (priv->buffer, "last-search-prev");
1042 if (last_search)
1043 gtk_text_buffer_get_iter_at_mark (priv->buffer, &iter, last_search);
1044 else
1045 iter = end_iter;
1046
1047 res = gdict_defbox_iter_backward_search (&iter, text,
1048 &match_start, &match_end,
1049 NULL);
1050 if (res)
1051 {
1052 gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (priv->text_view),
1053 &match_start,
1054 0.0,
1055 TRUE,
1056 0.0, 0.0);
1057 gtk_text_buffer_place_cursor (priv->buffer, &match_end);
1058 gtk_text_buffer_move_mark (priv->buffer,
1059 gtk_text_buffer_get_mark (priv->buffer, "selection_bound"),
1060 &match_start);
1061 gtk_text_buffer_create_mark (priv->buffer, "last-search-prev", &match_start, FALSE);
1062 gtk_text_buffer_create_mark (priv->buffer, "last-search-next", &match_end, FALSE);
1063
1064 return TRUE;
1065 }
1066
1067 return FALSE;
1068 }
1069
1070 static gboolean
hide_find_pane(gpointer user_data)1071 hide_find_pane (gpointer user_data)
1072 {
1073 GdictDefbox *defbox = user_data;
1074
1075 gtk_search_bar_set_search_mode (GTK_SEARCH_BAR (defbox->priv->find_pane), FALSE);
1076 defbox->priv->show_find = FALSE;
1077
1078 gtk_widget_grab_focus (defbox->priv->text_view);
1079
1080 defbox->priv->hide_timeout = 0;
1081
1082 return FALSE;
1083 }
1084
1085 static void
find_prev_clicked_cb(GtkWidget * widget,gpointer user_data)1086 find_prev_clicked_cb (GtkWidget *widget,
1087 gpointer user_data)
1088 {
1089 GdictDefbox *defbox = GDICT_DEFBOX (user_data);
1090 GdictDefboxPrivate *priv = defbox->priv;
1091 const gchar *text;
1092 gboolean found G_GNUC_UNUSED;
1093
1094 text = gtk_entry_get_text (GTK_ENTRY (priv->find_entry));
1095 if (!text)
1096 return;
1097
1098 found = gdict_defbox_find_backward (defbox, text);
1099
1100 if (priv->hide_timeout)
1101 {
1102 g_source_remove (priv->hide_timeout);
1103 priv->hide_timeout = g_timeout_add_seconds (5, hide_find_pane, defbox);
1104 }
1105 }
1106
1107 static gboolean
gdict_defbox_find_forward(GdictDefbox * defbox,const gchar * text,gboolean is_typing)1108 gdict_defbox_find_forward (GdictDefbox *defbox,
1109 const gchar *text,
1110 gboolean is_typing)
1111 {
1112 GdictDefboxPrivate *priv = defbox->priv;
1113 GtkTextIter start_iter, end_iter;
1114 GtkTextIter match_start, match_end;
1115 GtkTextIter iter;
1116 GtkTextMark *last_search;
1117 gboolean res;
1118
1119 g_assert (GTK_IS_TEXT_BUFFER (priv->buffer));
1120
1121 gtk_text_buffer_get_bounds (priv->buffer, &start_iter, &end_iter);
1122
1123 if (!is_typing)
1124 {
1125 /* if there already has been another result, begin from there */
1126 last_search = gtk_text_buffer_get_mark (priv->buffer, "last-search-next");
1127
1128 if (last_search)
1129 gtk_text_buffer_get_iter_at_mark (priv->buffer, &iter, last_search);
1130 else
1131 iter = start_iter;
1132 }
1133 else
1134 {
1135 last_search = gtk_text_buffer_get_mark (priv->buffer, "last-search-prev");
1136
1137 if (last_search)
1138 gtk_text_buffer_get_iter_at_mark (priv->buffer, &iter, last_search);
1139 else
1140 iter = start_iter;
1141 }
1142
1143 res = gdict_defbox_iter_forward_search (&iter, text,
1144 &match_start,
1145 &match_end,
1146 NULL);
1147 if (res)
1148 {
1149 gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (priv->text_view),
1150 &match_start,
1151 0.0,
1152 TRUE,
1153 0.0, 0.0);
1154 gtk_text_buffer_place_cursor (priv->buffer, &match_end);
1155 gtk_text_buffer_move_mark (priv->buffer,
1156 gtk_text_buffer_get_mark (priv->buffer, "selection_bound"),
1157 &match_start);
1158 gtk_text_buffer_create_mark (priv->buffer, "last-search-prev", &match_start, FALSE);
1159 gtk_text_buffer_create_mark (priv->buffer, "last-search-next", &match_end, FALSE);
1160
1161 return TRUE;
1162 }
1163
1164 return FALSE;
1165 }
1166
1167 static void
find_next_clicked_cb(GtkWidget * widget,gpointer user_data)1168 find_next_clicked_cb (GtkWidget *widget,
1169 gpointer user_data)
1170 {
1171 GdictDefbox *defbox = GDICT_DEFBOX (user_data);
1172 GdictDefboxPrivate *priv = defbox->priv;
1173 const gchar *text;
1174 gboolean found G_GNUC_UNUSED;
1175
1176 text = gtk_entry_get_text (GTK_ENTRY (priv->find_entry));
1177 if (!text)
1178 return;
1179
1180 found = gdict_defbox_find_forward (defbox, text, FALSE);
1181
1182 if (priv->hide_timeout)
1183 {
1184 g_source_remove (priv->hide_timeout);
1185 priv->hide_timeout = g_timeout_add_seconds (5, hide_find_pane, defbox);
1186 }
1187 }
1188
1189 static void
find_entry_changed_cb(GtkWidget * widget,gpointer user_data)1190 find_entry_changed_cb (GtkWidget *widget,
1191 gpointer user_data)
1192 {
1193 GdictDefbox *defbox = GDICT_DEFBOX (user_data);
1194 GdictDefboxPrivate *priv = defbox->priv;
1195 gchar *text;
1196 gboolean found G_GNUC_UNUSED;
1197
1198 text = gtk_editable_get_chars (GTK_EDITABLE (widget), 0, -1);
1199 if (!text)
1200 return;
1201
1202 found = gdict_defbox_find_forward (defbox, text, TRUE);
1203
1204 g_free (text);
1205
1206 if (priv->hide_timeout)
1207 {
1208 g_source_remove (priv->hide_timeout);
1209 priv->hide_timeout = g_timeout_add_seconds (5, hide_find_pane, defbox);
1210 }
1211 }
1212
1213 static GtkWidget *
create_find_pane(GdictDefbox * defbox)1214 create_find_pane (GdictDefbox *defbox)
1215 {
1216 GdictDefboxPrivate *priv;
1217 GtkWidget *find_pane;
1218 GtkWidget *hbox;
1219
1220 priv = defbox->priv;
1221
1222 find_pane = gtk_search_bar_new ();
1223 gtk_widget_show (find_pane);
1224
1225 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1226 gtk_container_add (GTK_CONTAINER (find_pane), hbox);
1227 gtk_widget_show (hbox);
1228
1229 priv->find_entry = gtk_search_entry_new ();
1230 g_signal_connect (priv->find_entry, "changed",
1231 G_CALLBACK (find_entry_changed_cb), defbox);
1232 gtk_box_pack_start (GTK_BOX (hbox), priv->find_entry, TRUE, TRUE, 0);
1233 gtk_widget_show (priv->find_entry);
1234
1235 gtk_search_bar_connect_entry (GTK_SEARCH_BAR (find_pane),
1236 GTK_ENTRY (priv->find_entry));
1237
1238 priv->find_prev = gtk_button_new_from_icon_name ("go-up-symbolic",
1239 GTK_ICON_SIZE_MENU);
1240 g_signal_connect (priv->find_prev, "clicked",
1241 G_CALLBACK (find_prev_clicked_cb), defbox);
1242 gtk_box_pack_start (GTK_BOX (hbox), priv->find_prev, FALSE, FALSE, 0);
1243 gtk_widget_show (priv->find_prev);
1244
1245 priv->find_next = gtk_button_new_from_icon_name ("go-down-symbolic",
1246 GTK_ICON_SIZE_MENU);
1247 g_signal_connect (priv->find_next, "clicked",
1248 G_CALLBACK (find_next_clicked_cb), defbox);
1249 gtk_box_pack_start (GTK_BOX (hbox), priv->find_next, FALSE, FALSE, 0);
1250 gtk_widget_show (priv->find_next);
1251
1252 gtk_style_context_add_class (gtk_widget_get_style_context (hbox),
1253 GTK_STYLE_CLASS_RAISED);
1254 gtk_style_context_add_class (gtk_widget_get_style_context (hbox),
1255 GTK_STYLE_CLASS_LINKED);
1256
1257 return find_pane;
1258 }
1259
1260 static void
gdict_defbox_init_tags(GdictDefbox * defbox)1261 gdict_defbox_init_tags (GdictDefbox *defbox)
1262 {
1263 GdictDefboxPrivate *priv = defbox->priv;
1264
1265 g_assert (GTK_IS_TEXT_BUFFER (priv->buffer));
1266
1267 gtk_text_buffer_create_tag (priv->buffer, "italic",
1268 "style", PANGO_STYLE_ITALIC,
1269 NULL);
1270 gtk_text_buffer_create_tag (priv->buffer, "bold",
1271 "weight", PANGO_WEIGHT_BOLD,
1272 NULL);
1273 gtk_text_buffer_create_tag (priv->buffer, "underline",
1274 "underline", PANGO_UNDERLINE_SINGLE,
1275 NULL);
1276
1277 gtk_text_buffer_create_tag (priv->buffer, "big",
1278 "scale", 1.6,
1279 NULL);
1280 gtk_text_buffer_create_tag (priv->buffer, "small",
1281 "scale", PANGO_SCALE_SMALL,
1282 NULL);
1283
1284 {
1285 GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (defbox));
1286 gboolean prefer_dark = FALSE;
1287 GdkRGBA rgba;
1288
1289 /* HACK: we're hardcoding the Adwaita values because GtkTextTag
1290 * cannot be styled via CSS
1291 */
1292 g_object_get (settings, "gtk-application-prefer-dark-theme", &prefer_dark, NULL);
1293
1294 if (!prefer_dark)
1295 gdk_rgba_parse (&rgba, "#2a76c6");
1296 else
1297 gdk_rgba_parse (&rgba, "#4a90d9");
1298
1299 priv->link_tag =
1300 gtk_text_buffer_create_tag (priv->buffer, "link",
1301 "underline", PANGO_UNDERLINE_SINGLE,
1302 "foreground-rgba", &rgba,
1303 NULL);
1304
1305 if (!prefer_dark)
1306 gdk_rgba_parse (&rgba, "#215d9c");
1307 else
1308 gdk_rgba_parse (&rgba, "#2a76c6");
1309
1310 priv->visited_link_tag =
1311 gtk_text_buffer_create_tag (priv->buffer, "visited-link",
1312 "underline", PANGO_UNDERLINE_SINGLE,
1313 "foreground-rgba", &rgba,
1314 NULL);
1315 }
1316
1317 gtk_text_buffer_create_tag (priv->buffer, "phonetic",
1318 "foreground", "dark gray",
1319 NULL);
1320
1321 gtk_text_buffer_create_tag (priv->buffer, "query-title",
1322 "left-margin", QUERY_MARGIN,
1323 "pixels-above-lines", 5,
1324 "pixels-below-lines", 20,
1325 NULL);
1326 gtk_text_buffer_create_tag (priv->buffer, "query-from",
1327 "foreground", "dark gray",
1328 "scale", PANGO_SCALE_SMALL,
1329 "left-margin", QUERY_MARGIN,
1330 "pixels-above-lines", 5,
1331 "pixels-below-lines", 10,
1332 NULL);
1333
1334 gtk_text_buffer_create_tag (priv->buffer, "error-title",
1335 "foreground", "dark red",
1336 "left-margin", ERROR_MARGIN,
1337 NULL);
1338 gtk_text_buffer_create_tag (priv->buffer, "error-message",
1339 "left-margin", ERROR_MARGIN,
1340 NULL);
1341 }
1342
1343 static void
follow_if_is_link(GdictDefbox * defbox,GtkTextView * text_view,GtkTextIter * iter)1344 follow_if_is_link (GdictDefbox *defbox,
1345 GtkTextView *text_view,
1346 GtkTextIter *iter)
1347 {
1348 GSList *tags, *l;
1349
1350 tags = gtk_text_iter_get_tags (iter);
1351
1352 for (l = tags; l != NULL; l = l->next)
1353 {
1354 GtkTextTag *tag = l->data;
1355 gchar *name;
1356
1357 g_object_get (G_OBJECT (tag), "name", &name, NULL);
1358 if (name &&
1359 (strcmp (name, "link") == 0 ||
1360 strcmp (name, "visited-link") == 0))
1361 {
1362 GtkTextBuffer *buffer = gtk_text_view_get_buffer (text_view);
1363 GtkTextIter start, end;
1364 gchar *link_str;
1365
1366 start = *iter;
1367 end = *iter;
1368
1369 gtk_text_iter_backward_to_tag_toggle (&start, tag);
1370 gtk_text_iter_forward_to_tag_toggle (&end, tag);
1371
1372 link_str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
1373
1374 g_signal_emit (defbox, gdict_defbox_signals[LINK_CLICKED], 0, link_str);
1375
1376 g_free (link_str);
1377 g_free (name);
1378
1379 break;
1380 }
1381
1382 g_free (name);
1383 }
1384
1385 g_slist_free (tags);
1386 }
1387
1388 static gboolean
defbox_event_after_cb(GtkWidget * text_view,GdkEvent * event,GdictDefbox * defbox)1389 defbox_event_after_cb (GtkWidget *text_view,
1390 GdkEvent *event,
1391 GdictDefbox *defbox)
1392 {
1393 GtkTextIter iter;
1394 GtkTextBuffer *buffer;
1395 GdkEventButton *button_event;
1396 gint bx, by;
1397
1398 if (event->type != GDK_BUTTON_RELEASE)
1399 return FALSE;
1400
1401 button_event = (GdkEventButton *) event;
1402
1403 if (button_event->button != 1)
1404 return FALSE;
1405
1406 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
1407 if (gtk_text_buffer_get_has_selection (buffer))
1408 return FALSE;
1409
1410 gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view),
1411 GTK_TEXT_WINDOW_WIDGET,
1412 button_event->x, button_event->y,
1413 &bx, &by);
1414
1415 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (text_view),
1416 &iter,
1417 bx, by);
1418
1419 follow_if_is_link (defbox, GTK_TEXT_VIEW (text_view), &iter);
1420
1421 return FALSE;
1422 }
1423
1424 static void
defbox_notify_has_selection(GtkTextBuffer * buffer,GParamSpec * pspec,GdictDefbox * defbox)1425 defbox_notify_has_selection (GtkTextBuffer *buffer,
1426 GParamSpec *pspec,
1427 GdictDefbox *defbox)
1428 {
1429 GDICT_NOTE (DEFBOX, "text buffer has-selection notified");
1430 g_signal_emit (defbox, gdict_defbox_signals[SELECTION_CHANGED], 0);
1431 }
1432
1433 static void
set_cursor_if_appropriate(GdictDefbox * defbox,GtkTextView * text_view,gint x,gint y)1434 set_cursor_if_appropriate (GdictDefbox *defbox,
1435 GtkTextView *text_view,
1436 gint x,
1437 gint y)
1438 {
1439 GdictDefboxPrivate *priv;
1440 GSList *tags, *l;
1441 GtkTextIter iter;
1442 gboolean hovering = FALSE;
1443
1444 priv = defbox->priv;
1445
1446 if (!priv->hand_cursor)
1447 {
1448 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (defbox));
1449 priv->hand_cursor = gdk_cursor_new_for_display (display, GDK_HAND2);
1450 }
1451
1452 if (!priv->regular_cursor)
1453 {
1454 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (defbox));
1455 priv->regular_cursor = gdk_cursor_new_for_display (display, GDK_XTERM);
1456 }
1457
1458 gtk_text_view_get_iter_at_location (text_view, &iter, x, y);
1459
1460 tags = gtk_text_iter_get_tags (&iter);
1461 for (l = tags; l != NULL; l = l->next)
1462 {
1463 GtkTextTag *tag = l->data;
1464 gchar *name;
1465
1466 g_object_get (G_OBJECT (tag), "name", &name, NULL);
1467 if (name &&
1468 (strcmp (name, "link") == 0 ||
1469 strcmp (name, "visited-link") == 0))
1470 {
1471 hovering = TRUE;
1472 g_free (name);
1473
1474 break;
1475 }
1476
1477 g_free (name);
1478 }
1479
1480 if (hovering != defbox->priv->is_hovering)
1481 {
1482 defbox->priv->is_hovering = hovering;
1483
1484 if (defbox->priv->is_hovering)
1485 gdk_window_set_cursor (gtk_text_view_get_window (text_view,
1486 GTK_TEXT_WINDOW_TEXT),
1487 defbox->priv->hand_cursor);
1488 else
1489 gdk_window_set_cursor (gtk_text_view_get_window (text_view,
1490 GTK_TEXT_WINDOW_TEXT),
1491 defbox->priv->regular_cursor);
1492 }
1493
1494 if (tags)
1495 g_slist_free (tags);
1496 }
1497
1498 static gboolean
defbox_motion_notify_cb(GtkWidget * text_view,GdkEventMotion * event,GdictDefbox * defbox)1499 defbox_motion_notify_cb (GtkWidget *text_view,
1500 GdkEventMotion *event,
1501 GdictDefbox *defbox)
1502 {
1503 gint bx, by;
1504
1505 gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view),
1506 GTK_TEXT_WINDOW_WIDGET,
1507 event->x, event->y,
1508 &bx, &by);
1509
1510 set_cursor_if_appropriate (defbox, GTK_TEXT_VIEW (text_view), bx, by);
1511
1512 return FALSE;
1513 }
1514
1515 static gboolean
defbox_visibility_notify_cb(GtkWidget * text_view,GdkEventVisibility * event,GdictDefbox * defbox)1516 defbox_visibility_notify_cb (GtkWidget *text_view,
1517 GdkEventVisibility *event,
1518 GdictDefbox *defbox)
1519 {
1520 GdkDisplay *display;
1521 GdkSeat *seat;
1522 GdkDevice *pointer;
1523 gint wx, wy;
1524 gint bx, by;
1525
1526 display = gdk_window_get_display (event->window);
1527 seat = gdk_display_get_default_seat (display);
1528 pointer = gdk_seat_get_pointer (seat);
1529 gdk_window_get_device_position (gtk_widget_get_window (text_view), pointer, &wx, &wy, NULL);
1530
1531 gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view),
1532 GTK_TEXT_WINDOW_WIDGET,
1533 wx, wy,
1534 &bx, &by);
1535
1536 set_cursor_if_appropriate (defbox, GTK_TEXT_VIEW (text_view), bx, by);
1537
1538 return FALSE;
1539 }
1540
1541 static GObject *
gdict_defbox_constructor(GType type,guint n_construct_properties,GObjectConstructParam * construct_params)1542 gdict_defbox_constructor (GType type,
1543 guint n_construct_properties,
1544 GObjectConstructParam *construct_params)
1545 {
1546 GdictDefbox *defbox;
1547 GdictDefboxPrivate *priv;
1548 GObject *object;
1549 GtkWidget *sw;
1550
1551 object = G_OBJECT_CLASS (gdict_defbox_parent_class)->constructor (type,
1552 n_construct_properties,
1553 construct_params);
1554 defbox = GDICT_DEFBOX (object);
1555 priv = defbox->priv;
1556
1557 sw = gtk_scrolled_window_new (NULL, NULL);
1558 gtk_widget_set_vexpand (sw, TRUE);
1559 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
1560 GTK_SHADOW_IN);
1561 gtk_box_pack_end (GTK_BOX (defbox), sw, TRUE, TRUE, 0);
1562 gtk_widget_show (sw);
1563
1564 priv->buffer = gtk_text_buffer_new (NULL);
1565 gdict_defbox_init_tags (defbox);
1566 g_signal_connect (priv->buffer, "notify::has-selection",
1567 G_CALLBACK (defbox_notify_has_selection),
1568 defbox);
1569
1570 priv->text_view = gtk_text_view_new_with_buffer (priv->buffer);
1571 gtk_text_view_set_editable (GTK_TEXT_VIEW (priv->text_view), FALSE);
1572 gtk_text_view_set_left_margin (GTK_TEXT_VIEW (priv->text_view), 4);
1573 gtk_container_add (GTK_CONTAINER (sw), priv->text_view);
1574 gtk_widget_show (priv->text_view);
1575
1576 priv->find_pane = create_find_pane (defbox);
1577 gtk_box_pack_start (GTK_BOX (defbox), priv->find_pane, FALSE, FALSE, 0);
1578
1579 /* stuff to make the link machinery work */
1580 g_signal_connect (priv->text_view, "event-after",
1581 G_CALLBACK (defbox_event_after_cb),
1582 defbox);
1583 g_signal_connect (priv->text_view, "motion-notify-event",
1584 G_CALLBACK (defbox_motion_notify_cb),
1585 defbox);
1586 g_signal_connect (priv->text_view, "visibility-notify-event",
1587 G_CALLBACK (defbox_visibility_notify_cb),
1588 defbox);
1589
1590 return object;
1591 }
1592
1593 /* we override the GtkWidget::show_all method since we have widgets
1594 * we don't want to show, such as the find pane
1595 */
1596 static void
gdict_defbox_show_all(GtkWidget * widget)1597 gdict_defbox_show_all (GtkWidget *widget)
1598 {
1599 GdictDefbox *defbox = GDICT_DEFBOX (widget);
1600 GdictDefboxPrivate *priv = defbox->priv;
1601
1602 gtk_widget_show (widget);
1603
1604 if (priv->show_find)
1605 gtk_search_bar_set_search_mode (GTK_SEARCH_BAR (priv->find_pane), TRUE);
1606 }
1607
1608 static void
gdict_defbox_real_show_find(GdictDefbox * defbox)1609 gdict_defbox_real_show_find (GdictDefbox *defbox)
1610 {
1611 gtk_search_bar_set_search_mode (GTK_SEARCH_BAR (defbox->priv->find_pane), TRUE);
1612 defbox->priv->show_find = TRUE;
1613
1614 gtk_widget_grab_focus (defbox->priv->find_entry);
1615
1616 defbox->priv->hide_timeout = g_timeout_add_seconds (5, hide_find_pane, defbox);
1617 }
1618
1619 static void
gdict_defbox_real_find_next(GdictDefbox * defbox)1620 gdict_defbox_real_find_next (GdictDefbox *defbox)
1621 {
1622 /* synthetize a "clicked" signal to the "next" button */
1623 gtk_button_clicked (GTK_BUTTON (defbox->priv->find_next));
1624 }
1625
1626 static void
gdict_defbox_real_find_previous(GdictDefbox * defbox)1627 gdict_defbox_real_find_previous (GdictDefbox *defbox)
1628 {
1629 /* synthetize a "clicked" signal to the "prev" button */
1630 gtk_button_clicked (GTK_BUTTON (defbox->priv->find_prev));
1631 }
1632
1633 static void
gdict_defbox_real_hide_find(GdictDefbox * defbox)1634 gdict_defbox_real_hide_find (GdictDefbox *defbox)
1635 {
1636 gtk_search_bar_set_search_mode (GTK_SEARCH_BAR (defbox->priv->find_pane), FALSE);
1637 defbox->priv->show_find = FALSE;
1638
1639 gtk_widget_grab_focus (defbox->priv->text_view);
1640
1641 if (defbox->priv->hide_timeout)
1642 {
1643 g_source_remove (defbox->priv->hide_timeout);
1644 defbox->priv->hide_timeout = 0;
1645 }
1646 }
1647
1648 static void
gdict_defbox_class_init(GdictDefboxClass * klass)1649 gdict_defbox_class_init (GdictDefboxClass *klass)
1650 {
1651 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
1652 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1653 GtkBindingSet *binding_set;
1654
1655 gobject_class->constructor = gdict_defbox_constructor;
1656 gobject_class->set_property = gdict_defbox_set_property;
1657 gobject_class->get_property = gdict_defbox_get_property;
1658 gobject_class->dispose = gdict_defbox_dispose;
1659 gobject_class->finalize = gdict_defbox_finalize;
1660
1661 widget_class->show_all = gdict_defbox_show_all;
1662
1663 /**
1664 * GdictDefbox:word:
1665 *
1666 * The word to look up.
1667 *
1668 * Since: 0.10
1669 */
1670 g_object_class_install_property (gobject_class,
1671 PROP_WORD,
1672 g_param_spec_string ("word",
1673 "Word",
1674 "The word to look up",
1675 NULL,
1676 G_PARAM_READWRITE));
1677 /**
1678 * GdictDefbox:context:
1679 *
1680 * The #GdictContext object used to get the word definition.
1681 *
1682 * Since: 0.1
1683 */
1684 g_object_class_install_property (gobject_class,
1685 PROP_CONTEXT,
1686 g_param_spec_object ("context",
1687 "Context",
1688 "The GdictContext object used to get the word definition",
1689 GDICT_TYPE_CONTEXT,
1690 (G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)));
1691 /**
1692 * GdictDefbox:database
1693 *
1694 * The database used by the #GdictDefbox bound to this object to get the word
1695 * definition.
1696 *
1697 * Since: 0.1
1698 */
1699 g_object_class_install_property (gobject_class,
1700 PROP_DATABASE,
1701 g_param_spec_string ("database",
1702 "Database",
1703 "The database used to query the GdictContext",
1704 GDICT_DEFAULT_DATABASE,
1705 (G_PARAM_READABLE | G_PARAM_WRITABLE)));
1706 /**
1707 * GdictDefbox:font-name
1708 *
1709 * The name of the font used by the #GdictDefbox to display the definitions.
1710 * use the same string you use for pango_font_description_from_string().
1711 *
1712 * Since: 0.3
1713 */
1714 g_object_class_install_property (gobject_class,
1715 PROP_FONT_NAME,
1716 g_param_spec_string ("font-name",
1717 "Font Name",
1718 "The font to be used by the defbox",
1719 GDICT_DEFAULT_FONT_NAME,
1720 (G_PARAM_READABLE | G_PARAM_WRITABLE)));
1721
1722 gdict_defbox_signals[SHOW_FIND] =
1723 g_signal_new ("show-find",
1724 G_OBJECT_CLASS_TYPE (gobject_class),
1725 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1726 G_STRUCT_OFFSET (GdictDefboxClass, show_find),
1727 NULL, NULL,
1728 gdict_marshal_VOID__VOID,
1729 G_TYPE_NONE, 0);
1730 gdict_defbox_signals[FIND_PREVIOUS] =
1731 g_signal_new ("find-previous",
1732 G_OBJECT_CLASS_TYPE (gobject_class),
1733 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1734 G_STRUCT_OFFSET (GdictDefboxClass, find_previous),
1735 NULL, NULL,
1736 gdict_marshal_VOID__VOID,
1737 G_TYPE_NONE, 0);
1738 gdict_defbox_signals[FIND_NEXT] =
1739 g_signal_new ("find-next",
1740 G_OBJECT_CLASS_TYPE (gobject_class),
1741 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1742 G_STRUCT_OFFSET (GdictDefboxClass, find_next),
1743 NULL, NULL,
1744 gdict_marshal_VOID__VOID,
1745 G_TYPE_NONE, 0);
1746 gdict_defbox_signals[HIDE_FIND] =
1747 g_signal_new ("hide-find",
1748 G_OBJECT_CLASS_TYPE (gobject_class),
1749 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1750 G_STRUCT_OFFSET (GdictDefboxClass, hide_find),
1751 NULL, NULL,
1752 gdict_marshal_VOID__VOID,
1753 G_TYPE_NONE, 0);
1754 gdict_defbox_signals[LINK_CLICKED] =
1755 g_signal_new ("link-clicked",
1756 G_OBJECT_CLASS_TYPE (gobject_class),
1757 G_SIGNAL_RUN_LAST,
1758 G_STRUCT_OFFSET (GdictDefboxClass, link_clicked),
1759 NULL, NULL,
1760 gdict_marshal_VOID__STRING,
1761 G_TYPE_NONE, 1,
1762 G_TYPE_STRING);
1763 gdict_defbox_signals[SELECTION_CHANGED] =
1764 g_signal_new ("selection-changed",
1765 G_OBJECT_CLASS_TYPE (gobject_class),
1766 G_SIGNAL_RUN_LAST,
1767 G_STRUCT_OFFSET (GdictDefboxClass, selection_changed),
1768 NULL, NULL,
1769 gdict_marshal_VOID__VOID,
1770 G_TYPE_NONE, 0);
1771
1772 klass->show_find = gdict_defbox_real_show_find;
1773 klass->hide_find = gdict_defbox_real_hide_find;
1774 klass->find_next = gdict_defbox_real_find_next;
1775 klass->find_previous = gdict_defbox_real_find_previous;
1776
1777 binding_set = gtk_binding_set_by_class (klass);
1778 gtk_binding_entry_add_signal (binding_set,
1779 GDK_KEY_f, GDK_CONTROL_MASK,
1780 "show-find",
1781 0);
1782 gtk_binding_entry_add_signal (binding_set,
1783 GDK_KEY_g, GDK_CONTROL_MASK,
1784 "find-next",
1785 0);
1786 gtk_binding_entry_add_signal (binding_set,
1787 GDK_KEY_g, GDK_SHIFT_MASK | GDK_CONTROL_MASK,
1788 "find-previous",
1789 0);
1790 gtk_binding_entry_add_signal (binding_set,
1791 GDK_KEY_Escape, 0,
1792 "hide-find",
1793 0);
1794 }
1795
1796 static void
gdict_defbox_init(GdictDefbox * defbox)1797 gdict_defbox_init (GdictDefbox *defbox)
1798 {
1799 GdictDefboxPrivate *priv;
1800
1801 gtk_orientable_set_orientation (GTK_ORIENTABLE (defbox), GTK_ORIENTATION_VERTICAL);
1802
1803 priv = gdict_defbox_get_instance_private (defbox);
1804 defbox->priv = priv;
1805
1806 priv->context = NULL;
1807 priv->database = g_strdup (GDICT_DEFAULT_DATABASE);
1808 priv->font_name = g_strdup (GDICT_DEFAULT_FONT_NAME);
1809 priv->word = NULL;
1810
1811 priv->definitions = NULL;
1812
1813 priv->hand_cursor = NULL;
1814 priv->regular_cursor = NULL;
1815
1816 priv->show_find = FALSE;
1817 priv->is_searching = FALSE;
1818 priv->is_hovering = FALSE;
1819
1820 priv->hide_timeout = 0;
1821 }
1822
1823 /**
1824 * gdict_defbox_new:
1825 *
1826 * Creates a new #GdictDefbox widget. Use this widget to search for
1827 * a word using a #GdictContext, and to show the resulting definition(s).
1828 * You must set a #GdictContext for this widget using
1829 * gdict_defbox_set_context().
1830 *
1831 * Return value: a new #GdictDefbox widget.
1832 *
1833 * Since: 0.1
1834 */
1835 GtkWidget *
gdict_defbox_new(void)1836 gdict_defbox_new (void)
1837 {
1838 return g_object_new (GDICT_TYPE_DEFBOX, NULL);
1839 }
1840
1841 /**
1842 * gdict_defbox_new_with_context:
1843 * @context: a #GdictContext
1844 *
1845 * Creates a new #GdictDefbox widget. Use this widget to search for
1846 * a word using @context, and to show the resulting definition.
1847 *
1848 * Return value: a new #GdictDefbox widget.
1849 *
1850 * Since: 0.1
1851 */
1852 GtkWidget *
gdict_defbox_new_with_context(GdictContext * context)1853 gdict_defbox_new_with_context (GdictContext *context)
1854 {
1855 g_return_val_if_fail (GDICT_IS_CONTEXT (context), NULL);
1856
1857 return g_object_new (GDICT_TYPE_DEFBOX, "context", context, NULL);
1858 }
1859
1860 /**
1861 * gdict_defbox_set_context:
1862 * @defbox: a #GdictDefbox
1863 * @context: a #GdictContext
1864 *
1865 * Sets @context as the #GdictContext to be used by @defbox in order
1866 * to retrieve the definitions of a word.
1867 *
1868 * Since: 0.1
1869 */
1870 void
gdict_defbox_set_context(GdictDefbox * defbox,GdictContext * context)1871 gdict_defbox_set_context (GdictDefbox *defbox,
1872 GdictContext *context)
1873 {
1874 g_return_if_fail (GDICT_IS_DEFBOX (defbox));
1875 g_return_if_fail (context == NULL || GDICT_IS_CONTEXT (context));
1876
1877 g_object_set (defbox, "context", context, NULL);
1878 }
1879
1880 /**
1881 * gdict_defbox_get_context:
1882 * @defbox: a #GdictDefbox
1883 *
1884 * Gets the #GdictContext used by @defbox.
1885 *
1886 * Return value: (transfer none): a #GdictContext.
1887 *
1888 * Since: 0.1
1889 */
1890 GdictContext *
gdict_defbox_get_context(GdictDefbox * defbox)1891 gdict_defbox_get_context (GdictDefbox *defbox)
1892 {
1893 g_return_val_if_fail (GDICT_IS_DEFBOX (defbox), NULL);
1894
1895 return defbox->priv->context;
1896 }
1897
1898 /**
1899 * gdict_defbox_set_database:
1900 * @defbox: a #GdictDefbox
1901 * @database: a database
1902 *
1903 * Sets @database as the database used by the #GdictContext bound to @defbox
1904 * to query for word definitions.
1905 *
1906 * Since: 0.1
1907 */
1908 void
gdict_defbox_set_database(GdictDefbox * defbox,const gchar * database)1909 gdict_defbox_set_database (GdictDefbox *defbox,
1910 const gchar *database)
1911 {
1912 GdictDefboxPrivate *priv;
1913
1914 g_return_if_fail (GDICT_IS_DEFBOX (defbox));
1915
1916 priv = defbox->priv;
1917
1918 g_free (priv->database);
1919 priv->database = g_strdup (database);
1920
1921 g_object_notify (G_OBJECT (defbox), "database");
1922 }
1923
1924 /**
1925 * gdict_defbox_get_database:
1926 * @defbox: a #GdictDefbox
1927 *
1928 * Gets the database used by @defbox. See gdict_defbox_set_database().
1929 *
1930 * Return value: the name of a database. The return string is owned by
1931 * the #GdictDefbox widget and should not be modified or freed.
1932 *
1933 * Since: 0.1
1934 */
1935 const gchar *
gdict_defbox_get_database(GdictDefbox * defbox)1936 gdict_defbox_get_database (GdictDefbox *defbox)
1937 {
1938 g_return_val_if_fail (GDICT_IS_DEFBOX (defbox), NULL);
1939
1940 return defbox->priv->database;
1941 }
1942
1943 /**
1944 * gdict_defbox_get_word:
1945 * @defbox: a #GdictDefbox
1946 *
1947 * Retrieves the word being looked up.
1948 *
1949 * Return value: the word looked up, or %NULL. The returned string is
1950 * owned by the #GdictDefbox widget and should never be modified or
1951 * freed.
1952 *
1953 * Since: 0.12
1954 */
1955 const gchar *
gdict_defbox_get_word(GdictDefbox * defbox)1956 gdict_defbox_get_word (GdictDefbox *defbox)
1957 {
1958 g_return_val_if_fail (GDICT_IS_DEFBOX (defbox), NULL);
1959
1960 return defbox->priv->word;
1961 }
1962
1963 /**
1964 * gdict_defbox_set_show_find:
1965 * @defbox: a #GdictDefbox
1966 * @show_find: %TRUE to show the find pane
1967 *
1968 * Whether @defbox should show the find pane.
1969 *
1970 * Since: 0.1
1971 */
1972 void
gdict_defbox_set_show_find(GdictDefbox * defbox,gboolean show_find)1973 gdict_defbox_set_show_find (GdictDefbox *defbox,
1974 gboolean show_find)
1975 {
1976 GdictDefboxPrivate *priv;
1977
1978 g_return_if_fail (GDICT_IS_DEFBOX (defbox));
1979
1980 priv = defbox->priv;
1981
1982 if (priv->show_find == show_find)
1983 return;
1984
1985 priv->show_find = show_find;
1986 if (priv->show_find)
1987 {
1988 gtk_search_bar_set_search_mode (GTK_SEARCH_BAR (priv->find_pane), TRUE);
1989
1990 if (!priv->hide_timeout)
1991 priv->hide_timeout = g_timeout_add_seconds (5, hide_find_pane, defbox);
1992 }
1993 else
1994 {
1995 gtk_search_bar_set_search_mode (GTK_SEARCH_BAR (priv->find_pane), FALSE);
1996
1997 if (priv->hide_timeout)
1998 {
1999 g_source_remove (priv->hide_timeout);
2000 priv->hide_timeout = 0;
2001 }
2002 }
2003 }
2004
2005 /**
2006 * gdict_defbox_get_show_find:
2007 * @defbox: a #GdictDefbox
2008 *
2009 * Gets whether the find pane should be visible or not.
2010 *
2011 * Return value: %TRUE if the find pane is visible.
2012 *
2013 * Since: 0.1
2014 */
2015 gboolean
gdict_defbox_get_show_find(GdictDefbox * defbox)2016 gdict_defbox_get_show_find (GdictDefbox *defbox)
2017 {
2018 g_return_val_if_fail (GDICT_IS_DEFBOX (defbox), FALSE);
2019
2020 return (defbox->priv->show_find == TRUE);
2021 }
2022
2023 static void
lookup_start_cb(GdictContext * context,gpointer user_data)2024 lookup_start_cb (GdictContext *context,
2025 gpointer user_data)
2026 {
2027 GdictDefbox *defbox = GDICT_DEFBOX (user_data);
2028 GdictDefboxPrivate *priv = defbox->priv;
2029
2030 priv->is_searching = TRUE;
2031 }
2032
2033 static void
lookup_end_cb(GdictContext * context,gpointer user_data)2034 lookup_end_cb (GdictContext *context,
2035 gpointer user_data)
2036 {
2037 GdictDefbox *defbox = GDICT_DEFBOX (user_data);
2038 GdictDefboxPrivate *priv = defbox->priv;
2039 GtkTextBuffer *buffer;
2040 GtkTextIter start;
2041
2042 /* explicitely move the cursor to the beginning */
2043 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
2044 gtk_text_buffer_get_start_iter (buffer, &start);
2045 gtk_text_buffer_place_cursor (buffer, &start);
2046
2047 priv->is_searching = FALSE;
2048 }
2049
2050 static void
gdict_defbox_insert_word(GdictDefbox * defbox,GtkTextIter * iter,const gchar * word)2051 gdict_defbox_insert_word (GdictDefbox *defbox,
2052 GtkTextIter *iter,
2053 const gchar *word)
2054 {
2055 GdictDefboxPrivate *priv;
2056 gchar *text;
2057
2058 if (!word)
2059 return;
2060
2061 g_assert (GDICT_IS_DEFBOX (defbox));
2062 priv = defbox->priv;
2063
2064 g_assert (GTK_IS_TEXT_BUFFER (priv->buffer));
2065
2066 text = g_strdup_printf ("%s\n", word);
2067 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
2068 iter,
2069 text, strlen (text),
2070 "big", "bold", "query-title",
2071 NULL);
2072 g_free (text);
2073 }
2074
2075 /* escape a link string; links are expressed as "{...}".
2076 * the link with the '{}' removed is stored inside link_str, while
2077 * the returned value is a pointer to what follows the trailing '}'.
2078 * link_str is allocated and should be freed.
2079 */
2080 static const gchar *
escape_link(const gchar * str,gchar ** link_str)2081 escape_link (const gchar *str,
2082 gchar **link_str)
2083 {
2084 gsize str_len;
2085 GString *link_buf;
2086 const gchar *p;
2087
2088 str_len = strlen (str);
2089 link_buf = g_string_sized_new (str_len - 2);
2090
2091 for (p = str + 1; *p != '}'; p++)
2092 {
2093 link_buf = g_string_append_c (link_buf, *p);
2094 }
2095
2096 if (link_str)
2097 *link_str = g_string_free (link_buf, FALSE);
2098
2099 p++;
2100
2101 return p;
2102 }
2103
2104 static const gchar *
escape_phonethic(const gchar * str,gchar ** phon_str)2105 escape_phonethic (const gchar *str,
2106 gchar **phon_str)
2107 {
2108 gsize str_len;
2109 GString *phon_buf;
2110 const gchar *p;
2111
2112 str_len = strlen (str);
2113 phon_buf = g_string_sized_new (str_len - 2);
2114
2115 for (p = str + 1; *p != '\\'; p++)
2116 {
2117 phon_buf = g_string_append_c (phon_buf, *p);
2118 }
2119
2120 if (phon_str)
2121 *phon_str = g_string_free (phon_buf, FALSE);
2122
2123 p++;
2124
2125 return p;
2126 }
2127
2128 static void
gdict_defbox_insert_body(GdictDefbox * defbox,GtkTextIter * iter,const gchar * body)2129 gdict_defbox_insert_body (GdictDefbox *defbox,
2130 GtkTextIter *iter,
2131 const gchar *body)
2132 {
2133 GdictDefboxPrivate *priv;
2134 gchar **words;
2135 gint len, i;
2136 GtkTextIter end_iter;
2137
2138 if (!body)
2139 return;
2140
2141 g_assert (GDICT_IS_DEFBOX (defbox));
2142 priv = defbox->priv;
2143
2144 g_assert (GTK_IS_TEXT_BUFFER (priv->buffer));
2145
2146 words = g_strsplit (body, " ", -1);
2147 len = g_strv_length (words);
2148 end_iter = *iter;
2149
2150 for (i = 0; i < len; i++)
2151 {
2152 gchar *w = words[i];
2153 gint w_len = strlen (w);
2154 gchar *begin, *end;
2155
2156 if (w_len == 0)
2157 continue;
2158
2159 begin = g_utf8_offset_to_pointer (w, 0);
2160
2161 if (*begin == '{')
2162 {
2163 end = g_utf8_strrchr (w, -1, '}');
2164
2165 /* see this is a self contained link */
2166 if (end && *end == '}')
2167 {
2168 const gchar *rest;
2169 gchar *link_str;
2170
2171 rest = escape_link (w, &link_str);
2172
2173 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
2174 &end_iter,
2175 link_str, -1,
2176 "link",
2177 NULL);
2178
2179 gtk_text_buffer_insert (priv->buffer, &end_iter, rest, -1);
2180
2181 gtk_text_buffer_get_end_iter (priv->buffer, &end_iter);
2182 gtk_text_buffer_insert (priv->buffer, &end_iter, " ", 1);
2183
2184 g_free (link_str);
2185
2186 continue;
2187 }
2188 else
2189 {
2190 /* uh-oh: the link ends in another word */
2191 GString *buf;
2192 gchar *next;
2193 gint cur = i;
2194
2195 buf = g_string_new (NULL);
2196 next = words[cur++];
2197
2198 while (next && (end = g_utf8_strrchr (next, -1, '}')) == NULL)
2199 {
2200 buf = g_string_append (buf, next);
2201 buf = g_string_append_c (buf, ' ');
2202
2203 next = words[cur++];
2204 }
2205
2206 buf = g_string_append (buf, next);
2207
2208 next = g_string_free (buf, FALSE);
2209
2210 if (end && *end == '}')
2211 {
2212 const gchar *rest;
2213 gchar *link_str;
2214
2215 rest = escape_link (next, &link_str);
2216
2217 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
2218 &end_iter,
2219 link_str, -1,
2220 "link",
2221 NULL);
2222
2223 gtk_text_buffer_insert (priv->buffer, &end_iter, rest, -1);
2224 gtk_text_buffer_insert (priv->buffer, &end_iter, " ", 1);
2225
2226 g_free (link_str);
2227 }
2228
2229 g_free (next);
2230 i = cur;
2231
2232 continue;
2233 }
2234 }
2235 else if (*begin == '\\')
2236 {
2237 end = g_utf8_strrchr (w, -1, '\\');
2238
2239 if (end && *end == '\\')
2240 {
2241 const gchar *rest;
2242 gchar *phon;
2243
2244 rest = escape_phonethic (w, &phon);
2245
2246 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
2247 &end_iter,
2248 phon, -1,
2249 "italic", "phonetic",
2250 NULL);
2251
2252 gtk_text_buffer_insert (priv->buffer, &end_iter, rest, -1);
2253
2254 gtk_text_buffer_get_end_iter (priv->buffer, &end_iter);
2255 gtk_text_buffer_insert (priv->buffer, &end_iter, " ", -1);
2256
2257 g_free (phon);
2258
2259 continue;
2260 }
2261 }
2262
2263 gtk_text_buffer_insert (priv->buffer, &end_iter, w, w_len);
2264
2265 gtk_text_buffer_get_end_iter (priv->buffer, &end_iter);
2266 gtk_text_buffer_insert (priv->buffer, &end_iter, " ", 1);
2267 }
2268
2269 gtk_text_buffer_get_end_iter (priv->buffer, &end_iter);
2270 gtk_text_buffer_insert (priv->buffer, &end_iter, "\n", 1);
2271
2272 *iter = end_iter;
2273
2274 g_strfreev (words);
2275 }
2276
2277 static void
gdict_defbox_insert_from(GdictDefbox * defbox,GtkTextIter * iter,const gchar * database)2278 gdict_defbox_insert_from (GdictDefbox *defbox,
2279 GtkTextIter *iter,
2280 const gchar *database)
2281 {
2282 GdictDefboxPrivate *priv;
2283 gchar *text;
2284
2285 if (!database)
2286 return;
2287
2288 g_assert (GDICT_IS_DEFBOX (defbox));
2289 priv = defbox->priv;
2290
2291 g_assert (GTK_IS_TEXT_BUFFER (priv->buffer));
2292
2293 text = g_strdup_printf ("\t-- From %s\n\n", database);
2294 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
2295 iter,
2296 text, strlen (text),
2297 "small", "query-from",
2298 NULL);
2299 g_free (text);
2300 }
2301
2302 static void
gdict_defbox_insert_error(GdictDefbox * defbox,GtkTextIter * iter,const gchar * title,const gchar * message)2303 gdict_defbox_insert_error (GdictDefbox *defbox,
2304 GtkTextIter *iter,
2305 const gchar *title,
2306 const gchar *message)
2307 {
2308 GdictDefboxPrivate *priv;
2309 GtkTextMark *mark;
2310 GtkTextIter cur_iter;
2311
2312 if (!title)
2313 return;
2314
2315 g_assert (GDICT_IS_DEFBOX (defbox));
2316 priv = defbox->priv;
2317
2318 g_assert (GTK_IS_TEXT_BUFFER (priv->buffer));
2319
2320 mark = gtk_text_buffer_create_mark (priv->buffer, "block-cursor", iter, FALSE);
2321 gtk_text_buffer_get_iter_at_mark (priv->buffer, &cur_iter, mark);
2322
2323 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
2324 &cur_iter,
2325 title, strlen (title),
2326 "error-title", "big", "bold",
2327 NULL);
2328 gtk_text_buffer_get_iter_at_mark (priv->buffer, &cur_iter, mark);
2329
2330 gtk_text_buffer_insert (priv->buffer, &cur_iter, "\n\n", -1);
2331 gtk_text_buffer_get_iter_at_mark (priv->buffer, &cur_iter, mark);
2332
2333 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
2334 &cur_iter,
2335 message, strlen (message),
2336 "error-message",
2337 NULL);
2338 }
2339
2340 static void
definition_found_cb(GdictContext * context,GdictDefinition * definition,gpointer user_data)2341 definition_found_cb (GdictContext *context,
2342 GdictDefinition *definition,
2343 gpointer user_data)
2344 {
2345 GdictDefbox *defbox = GDICT_DEFBOX (user_data);
2346 GdictDefboxPrivate *priv = defbox->priv;
2347 GtkTextIter iter;
2348 Definition *def;
2349
2350 /* insert the word if this is the first definition */
2351 if (!priv->definitions)
2352 {
2353 gtk_text_buffer_get_start_iter (priv->buffer, &iter);
2354 gdict_defbox_insert_word (defbox, &iter,
2355 gdict_definition_get_word (definition));
2356 }
2357
2358 def = definition_new ();
2359
2360 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
2361 def->begin = gtk_text_iter_get_offset (&iter);
2362 gdict_defbox_insert_body (defbox, &iter, gdict_definition_get_text (definition));
2363
2364 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
2365 gdict_defbox_insert_from (defbox, &iter, gdict_definition_get_database (definition));
2366
2367 def->definition = gdict_definition_ref (definition);
2368
2369 priv->definitions = g_slist_append (priv->definitions, def);
2370 }
2371
2372 static void
error_cb(GdictContext * context,const GError * error,gpointer user_data)2373 error_cb (GdictContext *context,
2374 const GError *error,
2375 gpointer user_data)
2376 {
2377 GdictDefbox *defbox = GDICT_DEFBOX (user_data);
2378 GdictDefboxPrivate *priv = defbox->priv;
2379 GtkTextIter iter;
2380
2381 if (!error)
2382 return;
2383
2384 gdict_defbox_clear (defbox);
2385
2386 gtk_text_buffer_get_start_iter (priv->buffer, &iter);
2387 gdict_defbox_insert_error (defbox, &iter,
2388 _("Error while looking up definition"),
2389 error->message);
2390
2391 g_free (priv->word);
2392 priv->word = NULL;
2393
2394 defbox->priv->is_searching = FALSE;
2395 }
2396
2397 /**
2398 * gdict_defbox_lookup:
2399 * @defbox: a #GdictDefbox
2400 * @word: the word to look up
2401 *
2402 * Searches @word inside the dictionary sources using the #GdictContext
2403 * provided when creating @defbox or set using gdict_defbox_set_context().
2404 *
2405 * Since: 0.1
2406 */
2407 void
gdict_defbox_lookup(GdictDefbox * defbox,const gchar * word)2408 gdict_defbox_lookup (GdictDefbox *defbox,
2409 const gchar *word)
2410 {
2411 GdictDefboxPrivate *priv;
2412 GError *define_error;
2413
2414 g_return_if_fail (GDICT_IS_DEFBOX (defbox));
2415
2416 priv = defbox->priv;
2417
2418 if (!priv->context)
2419 {
2420 g_warning ("Attempting to look up `%s', but no GdictContext "
2421 "has been set. Use gdict_defbox_set_context() "
2422 "before invoking gdict_defbox_lookup().",
2423 word);
2424 return;
2425 }
2426
2427 if (priv->is_searching)
2428 {
2429 _gdict_show_error_dialog (GTK_WIDGET (defbox),
2430 _("Another search is in progress"),
2431 _("Please wait until the current search ends."));
2432
2433 return;
2434 }
2435
2436 gdict_defbox_clear (defbox);
2437
2438 if (!priv->start_id)
2439 {
2440 priv->start_id = g_signal_connect (priv->context, "definition-lookup-start",
2441 G_CALLBACK (lookup_start_cb),
2442 defbox);
2443 priv->define_id = g_signal_connect (priv->context, "definition-found",
2444 G_CALLBACK (definition_found_cb),
2445 defbox);
2446 priv->end_id = g_signal_connect (priv->context, "definition-lookup-end",
2447 G_CALLBACK (lookup_end_cb),
2448 defbox);
2449 }
2450
2451 if (!priv->error_id)
2452 priv->error_id = g_signal_connect (priv->context, "error",
2453 G_CALLBACK (error_cb),
2454 defbox);
2455
2456 priv->word = g_strdup (word);
2457 g_object_notify (G_OBJECT (defbox), "word");
2458
2459 define_error = NULL;
2460 gdict_context_define_word (priv->context,
2461 priv->database,
2462 word,
2463 &define_error);
2464 if (define_error)
2465 {
2466 GtkTextIter iter;
2467
2468 gtk_text_buffer_get_start_iter (priv->buffer, &iter);
2469 gdict_defbox_insert_error (defbox, &iter,
2470 _("Error while retrieving the definition"),
2471 define_error->message);
2472
2473 g_error_free (define_error);
2474 }
2475 }
2476
2477 /**
2478 * gdict_defbox_clear:
2479 * @defbox: a @GdictDefbox
2480 *
2481 * Clears the buffer of @defbox
2482 *
2483 * Since: 0.1
2484 */
2485 void
gdict_defbox_clear(GdictDefbox * defbox)2486 gdict_defbox_clear (GdictDefbox *defbox)
2487 {
2488 GdictDefboxPrivate *priv;
2489 GtkTextIter start, end;
2490
2491 g_return_if_fail (GDICT_IS_DEFBOX (defbox));
2492
2493 priv = defbox->priv;
2494
2495 /* destroy previously found definitions */
2496 if (priv->definitions)
2497 {
2498 g_slist_foreach (priv->definitions,
2499 (GFunc) definition_free,
2500 NULL);
2501 g_slist_free (priv->definitions);
2502
2503 priv->definitions = NULL;
2504 }
2505
2506 gtk_text_buffer_get_bounds (priv->buffer, &start, &end);
2507 gtk_text_buffer_delete (priv->buffer, &start, &end);
2508 }
2509
2510 /**
2511 * gdict_defbox_find_next:
2512 * @defbox: a #GdictDefbox
2513 *
2514 * Emits the "find-next" signal.
2515 *
2516 * Since: 0.1
2517 */
2518 void
gdict_defbox_find_next(GdictDefbox * defbox)2519 gdict_defbox_find_next (GdictDefbox *defbox)
2520 {
2521 g_return_if_fail (GDICT_IS_DEFBOX (defbox));
2522
2523 g_signal_emit (defbox, gdict_defbox_signals[FIND_NEXT], 0);
2524 }
2525
2526 /**
2527 * gdict_defbox_find_previous:
2528 * @defbox: a #GdictDefbox
2529 *
2530 * Emits the "find-previous" signal.
2531 *
2532 * Since: 0.1
2533 */
2534 void
gdict_defbox_find_previous(GdictDefbox * defbox)2535 gdict_defbox_find_previous (GdictDefbox *defbox)
2536 {
2537 g_return_if_fail (GDICT_IS_DEFBOX (defbox));
2538
2539 g_signal_emit (defbox, gdict_defbox_signals[FIND_PREVIOUS], 0);
2540 }
2541
2542 /**
2543 * gdict_defbox_select_all:
2544 * @defbox: a #GdictDefbox
2545 *
2546 * Selects all the text displayed by @defbox
2547 *
2548 * Since: 0.1
2549 */
2550 void
gdict_defbox_select_all(GdictDefbox * defbox)2551 gdict_defbox_select_all (GdictDefbox *defbox)
2552 {
2553 GdictDefboxPrivate *priv;
2554 GtkTextBuffer *buffer;
2555 GtkTextIter start, end;
2556
2557 g_return_if_fail (GDICT_IS_DEFBOX (defbox));
2558
2559 priv = defbox->priv;
2560 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
2561
2562 gtk_text_buffer_get_bounds (buffer, &start, &end);
2563 gtk_text_buffer_select_range (buffer, &start, &end);
2564 }
2565
2566 /**
2567 * gdict_defbox_copy_to_clipboard:
2568 * @defbox: a #GdictDefbox
2569 * @clipboard: a #GtkClipboard
2570 *
2571 * Copies the selected text inside @defbox into @clipboard.
2572 *
2573 * Since: 0.1
2574 */
2575 void
gdict_defbox_copy_to_clipboard(GdictDefbox * defbox,GtkClipboard * clipboard)2576 gdict_defbox_copy_to_clipboard (GdictDefbox *defbox,
2577 GtkClipboard *clipboard)
2578 {
2579 GdictDefboxPrivate *priv;
2580 GtkTextBuffer *buffer;
2581
2582 g_return_if_fail (GDICT_IS_DEFBOX (defbox));
2583 g_return_if_fail (GTK_IS_CLIPBOARD (clipboard));
2584
2585 priv = defbox->priv;
2586 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
2587
2588 gtk_text_buffer_copy_clipboard (buffer, clipboard);
2589 }
2590
2591 /**
2592 * gdict_defbox_count_definitions:
2593 * @defbox: a #GdictDefbox
2594 *
2595 * Gets the number of definitions displayed by @defbox
2596 *
2597 * Return value: the number of definitions.
2598 *
2599 * Since: 0.1
2600 */
2601 gint
gdict_defbox_count_definitions(GdictDefbox * defbox)2602 gdict_defbox_count_definitions (GdictDefbox *defbox)
2603 {
2604 GdictDefboxPrivate *priv;
2605
2606 g_return_val_if_fail (GDICT_IS_DEFBOX (defbox), -1);
2607
2608 priv = defbox->priv;
2609 if (!priv->definitions)
2610 return -1;
2611
2612 return g_slist_length (priv->definitions);
2613 }
2614
2615 /**
2616 * gdict_defbox_jump_to_definition:
2617 * @defbox: a #GdictDefbox
2618 * @number: the definition to jump to
2619 *
2620 * Scrolls to the definition identified by @number. If @number is -1,
2621 * jumps to the last definition.
2622 *
2623 * Since: 0.1
2624 */
2625 void
gdict_defbox_jump_to_definition(GdictDefbox * defbox,gint number)2626 gdict_defbox_jump_to_definition (GdictDefbox *defbox,
2627 gint number)
2628 {
2629 GdictDefboxPrivate *priv;
2630 gint count;
2631 Definition *def;
2632 GtkTextBuffer *buffer;
2633 GtkTextIter def_start;
2634
2635 g_return_if_fail (GDICT_IS_DEFBOX (defbox));
2636
2637 count = gdict_defbox_count_definitions (defbox) - 1;
2638 if (count == -1)
2639 return;
2640
2641 if ((number == -1) || (number > count))
2642 number = count;
2643
2644 priv = defbox->priv;
2645 def = (Definition *) g_slist_nth_data (priv->definitions, number);
2646 if (!def)
2647 return;
2648
2649 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
2650 gtk_text_buffer_get_iter_at_offset (buffer, &def_start, def->begin);
2651 gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (priv->text_view),
2652 &def_start,
2653 0.0,
2654 TRUE,
2655 0.0, 0.0);
2656 }
2657
2658 /**
2659 * gdict_defbox_get_text:
2660 * @defbox: a #GdictDefbox
2661 * @length: (out) (optional): return location for the text length or %NULL
2662 *
2663 * Gets the full contents of @defbox.
2664 *
2665 * Return value: a newly allocated string containing the text displayed by
2666 * @defbox.
2667 *
2668 * Since: 0.1
2669 */
2670 gchar *
gdict_defbox_get_text(GdictDefbox * defbox,gsize * length)2671 gdict_defbox_get_text (GdictDefbox *defbox,
2672 gsize *length)
2673 {
2674 GdictDefboxPrivate *priv;
2675 GtkTextBuffer *buffer;
2676 GtkTextIter start, end;
2677 gchar *retval;
2678
2679 g_return_val_if_fail (GDICT_IS_DEFBOX (defbox), NULL);
2680
2681 priv = defbox->priv;
2682 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
2683
2684 gtk_text_buffer_get_bounds (buffer, &start, &end);
2685
2686 retval = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
2687
2688 if (length)
2689 *length = strlen (retval);
2690
2691 return retval;
2692 }
2693
2694 /**
2695 * gdict_defbox_set_font_name:
2696 * @defbox: a #GdictDefbox
2697 * @font_name: (nullable): a font description, or %NULL
2698 *
2699 * Sets @font_name as the font for @defbox. It calls internally
2700 * pango_font_description_from_string() and gtk_widget_modify_font().
2701 *
2702 * Passing %NULL for @font_name will reset any previously set font.
2703 *
2704 * Since: 0.3.0
2705 */
2706 void
gdict_defbox_set_font_name(GdictDefbox * defbox,const gchar * font_name)2707 gdict_defbox_set_font_name (GdictDefbox *defbox,
2708 const gchar *font_name)
2709 {
2710 GdictDefboxPrivate *priv;
2711 PangoFontDescription *font_desc;
2712
2713 g_return_if_fail (GDICT_IS_DEFBOX (defbox));
2714
2715 priv = defbox->priv;
2716
2717 if (font_name)
2718 {
2719 font_desc = pango_font_description_from_string (font_name);
2720 g_return_if_fail (font_desc != NULL);
2721 }
2722 else
2723 font_desc = NULL;
2724
2725 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
2726 gtk_widget_override_font (priv->text_view, font_desc);
2727 G_GNUC_END_IGNORE_DEPRECATIONS
2728
2729 if (font_desc)
2730 pango_font_description_free (font_desc);
2731
2732 g_free (priv->font_name);
2733 priv->font_name = g_strdup (font_name);
2734 }
2735
2736 /**
2737 * gdict_defbox_get_font_name:
2738 * @defbox: a #GdictDefbox
2739 *
2740 * Retrieves the font currently used by @defbox.
2741 *
2742 * Return value: a font name. The returned string is owned by @defbox and
2743 * should not be modified or freed.
2744 *
2745 * Since: 0.3
2746 */
2747 const gchar *
gdict_defbox_get_font_name(GdictDefbox * defbox)2748 gdict_defbox_get_font_name (GdictDefbox *defbox)
2749 {
2750 g_return_val_if_fail (GDICT_IS_DEFBOX (defbox), NULL);
2751
2752 return defbox->priv->font_name;
2753 }
2754
2755 /**
2756 * gdict_defbox_get_selected_word:
2757 * @defbox: a #GdictDefbox
2758 *
2759 * Retrieves the selected word from the defbox widget
2760 *
2761 * Return value: a newly allocated string containing the selected
2762 * word. Use g_free() when done using it.
2763 *
2764 * Since: 0.12
2765 */
2766 gchar *
gdict_defbox_get_selected_word(GdictDefbox * defbox)2767 gdict_defbox_get_selected_word (GdictDefbox *defbox)
2768 {
2769 GdictDefboxPrivate *priv;
2770 GtkTextBuffer *buffer;
2771
2772 g_return_val_if_fail (GDICT_IS_DEFBOX (defbox), NULL);
2773
2774 priv = defbox->priv;
2775 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
2776
2777 if (!gtk_text_buffer_get_has_selection (buffer))
2778 return NULL;
2779 else
2780 {
2781 GtkTextIter start, end;
2782 gchar *retval;
2783
2784 gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
2785 retval = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
2786
2787 return retval;
2788 }
2789 }
2790
2791 /**
2792 * gdict_defbox_get_has_selection
2793 * @defbox: a #GdictDefbox
2794 *
2795 * Retrieves whether there is text selected in @defbox or not.
2796 *
2797 * Return value: whether text is selected or not.
2798 *
2799 * Since: 0.12
2800 */
2801 gboolean
gdict_defbox_get_has_selection(GdictDefbox * defbox)2802 gdict_defbox_get_has_selection (GdictDefbox *defbox)
2803 {
2804 GdictDefboxPrivate *priv;
2805 GtkTextBuffer *buffer;
2806
2807 g_return_val_if_fail (GDICT_IS_DEFBOX (defbox), FALSE);
2808
2809 priv = defbox->priv;
2810 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
2811
2812 return gtk_text_buffer_get_has_selection (buffer);
2813 }
2814