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