evolution-3.6.4/widgets/misc/e-buffer-tagger.c

No issues found

  1 /*
  2  * e-buffer-tagger.c
  3  *
  4  * This program is free software; you can redistribute it and/or
  5  * modify it under the terms of the GNU Lesser General Public
  6  * License as published by the Free Software Foundation; either
  7  * version 2 of the License, or (at your option) version 3.
  8  *
  9  * This program is distributed in the hope that it will be useful,
 10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 12  * Lesser General Public License for more details.
 13  *
 14  * You should have received a copy of the GNU Lesser General Public
 15  * License along with the program; if not, see <http://www.gnu.org/licenses/>
 16  *
 17  *
 18  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 19  *
 20  */
 21 
 22 #ifdef HAVE_CONFIG_H
 23 #include <config.h>
 24 #endif
 25 
 26 #include <gtk/gtk.h>
 27 #include <gdk/gdkkeysyms.h>
 28 #include <glib/gi18n.h>
 29 #include <regex.h>
 30 #include <string.h>
 31 #include <ctype.h>
 32 
 33 #include "e-util/e-util.h"
 34 #include "e-buffer-tagger.h"
 35 
 36 enum EBufferTaggerState
 37 {
 38 	E_BUFFER_TAGGER_STATE_NONE                = 0,
 39 	E_BUFFER_TAGGER_STATE_INSDEL              = 1 << 0, /* set when was called insert or delete of a text */
 40 	E_BUFFER_TAGGER_STATE_CHANGED             = 1 << 1, /* remark of the buffer is scheduled */
 41 	E_BUFFER_TAGGER_STATE_IS_HOVERING         = 1 << 2, /* mouse is over the link */
 42 	E_BUFFER_TAGGER_STATE_IS_HOVERING_TOOLTIP = 1 << 3, /* mouse is over the link and the tooltip can be shown */
 43 	E_BUFFER_TAGGER_STATE_CTRL_DOWN           = 1 << 4  /* Ctrl key is down */
 44 };
 45 
 46 #define E_BUFFER_TAGGER_DATA_STATE "EBufferTagger::state"
 47 #define E_BUFFER_TAGGER_LINK_TAG   "EBufferTagger::link"
 48 
 49 struct _MagicInsertMatch
 50 {
 51 	const gchar *regex;
 52 	regex_t *preg;
 53 	const gchar *prefix;
 54 };
 55 
 56 typedef struct _MagicInsertMatch MagicInsertMatch;
 57 
 58 static MagicInsertMatch mim[] = {
 59 	/* prefixed expressions */
 60 	{ "(news|telnet|nntp|file|http|ftp|sftp|https|webcal)://([-a-z0-9]+(:[-a-z0-9]+)?@)?[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(([.])?/[-a-z0-9_$.+!*(),;:@%&=?/~#']*[^]'.}>\\) \n\r\t,?!;:\"]?)?", NULL, NULL },
 61 	{ "(sip|h323|callto):([-_a-z0-9.'\\+]+(:[0-9]{1,5})?(/[-_a-z0-9.']+)?)(@([-_a-z0-9.%=?]+|([0-9]{1,3}.){3}[0-9]{1,3})?)?(:[0-9]{1,5})?", NULL, NULL },
 62 	{ "mailto:[-_a-z0-9.'\\+]+@[-_a-z0-9.%=?]+", NULL, NULL },
 63 	/* not prefixed expression */
 64 	{ "www\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(([.])?/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]'.}>\\) \n\r\t,?!;:\"]?)?", NULL, "http://" },
 65 	{ "ftp\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(([.])?/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]'.}>\\) \n\r\t,?!;:\"]?)?", NULL, "ftp://" },
 66 	{ "[-_a-z0-9.'\\+]+@[-_a-z0-9.%=?]+", NULL, "mailto:" }
 67 };
 68 
 69 static void
 70 init_magic_links (void)
 71 {
 72 	static gboolean done = FALSE;
 73 	gint i;
 74 
 75 	if (done)
 76 		return;
 77 
 78 	done = TRUE;
 79 
 80 	for (i = 0; i < G_N_ELEMENTS (mim); i++) {
 81 		mim[i].preg = g_new0 (regex_t, 1);
 82 		if (regcomp (mim[i].preg, mim[i].regex, REG_EXTENDED | REG_ICASE)) {
 83 			/* error */
 84 			g_free (mim[i].preg);
 85 			mim[i].preg = 0;
 86 		}
 87 	}
 88 }
 89 
 90 static void
 91 markup_text (GtkTextBuffer *buffer)
 92 {
 93 	GtkTextIter start, end;
 94 	gchar *text;
 95 	gint i;
 96 	regmatch_t pmatch[2];
 97 	gboolean any;
 98 	const gchar *str;
 99 	gint offset = 0;
100 
101 	g_return_if_fail (buffer != NULL);
102 
103 	gtk_text_buffer_get_start_iter (buffer, &start);
104 	gtk_text_buffer_get_end_iter (buffer, &end);
105 	gtk_text_buffer_remove_tag_by_name (buffer, E_BUFFER_TAGGER_LINK_TAG, &start, &end);
106 	text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
107 
108 	str = text;
109 	any = TRUE;
110 	while (any) {
111 		any = FALSE;
112 		for (i = 0; i < G_N_ELEMENTS (mim); i++) {
113 			if (mim[i].preg && !regexec (mim[i].preg, str, 2, pmatch, 0)) {
114 				gtk_text_buffer_get_iter_at_offset (buffer, &start, offset + pmatch[0].rm_so);
115 				gtk_text_buffer_get_iter_at_offset (buffer, &end, offset + pmatch[0].rm_eo);
116 				gtk_text_buffer_apply_tag_by_name (buffer, E_BUFFER_TAGGER_LINK_TAG, &start, &end);
117 
118 				any = TRUE;
119 				str += pmatch[0].rm_eo;
120 				offset += pmatch[0].rm_eo;
121 				break;
122 			}
123 		}
124 	}
125 
126 	g_free (text);
127 }
128 
129 static guint32
130 get_state (GtkTextBuffer *buffer)
131 {
132 	g_return_val_if_fail (buffer != NULL, E_BUFFER_TAGGER_STATE_NONE);
133 	g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), E_BUFFER_TAGGER_STATE_NONE);
134 
135 	return GPOINTER_TO_INT (g_object_get_data (G_OBJECT (buffer), E_BUFFER_TAGGER_DATA_STATE));
136 }
137 
138 static void
139 set_state (GtkTextBuffer *buffer,
140            guint32 state)
141 {
142 	g_object_set_data (G_OBJECT (buffer), E_BUFFER_TAGGER_DATA_STATE, GINT_TO_POINTER (state));
143 }
144 
145 static void
146 update_state (GtkTextBuffer *buffer,
147               guint32 value,
148               gboolean do_set)
149 {
150 	guint32 state;
151 
152 	g_return_if_fail (buffer != NULL);
153 	g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
154 
155 	state = get_state (buffer);
156 
157 	if (do_set)
158 		state = state | value;
159 	else
160 		state = state & (~value);
161 
162 	set_state (buffer, state);
163 }
164 
165 static gboolean
166 get_tag_bounds (GtkTextIter *iter,
167                 GtkTextTag *tag,
168                 GtkTextIter *start,
169                 GtkTextIter *end)
170 {
171 	gboolean res = FALSE;
172 
173 	g_return_val_if_fail (iter != NULL, FALSE);
174 	g_return_val_if_fail (tag != NULL, FALSE);
175 	g_return_val_if_fail (start != NULL, FALSE);
176 	g_return_val_if_fail (end != NULL, FALSE);
177 
178 	if (gtk_text_iter_has_tag (iter, tag)) {
179 		*start = *iter;
180 		*end = *iter;
181 
182 		if (!gtk_text_iter_begins_tag (start, tag))
183 			gtk_text_iter_backward_to_tag_toggle (start, tag);
184 
185 		if (!gtk_text_iter_ends_tag (end, tag))
186 			gtk_text_iter_forward_to_tag_toggle (end, tag);
187 
188 		res = TRUE;
189 	}
190 
191 	return res;
192 }
193 
194 static gchar *
195 get_url_at_iter (GtkTextBuffer *buffer,
196                  GtkTextIter *iter)
197 {
198 	GtkTextTagTable *tag_table;
199 	GtkTextTag *tag;
200 	GtkTextIter start, end;
201 	gchar *url = NULL;
202 
203 	g_return_val_if_fail (buffer != NULL, NULL);
204 
205 	tag_table = gtk_text_buffer_get_tag_table (buffer);
206 	tag = gtk_text_tag_table_lookup (tag_table, E_BUFFER_TAGGER_LINK_TAG);
207 	g_return_val_if_fail (tag != NULL, FALSE);
208 
209 	if (get_tag_bounds (iter, tag, &start, &end))
210 		url = gtk_text_iter_get_text (&start, &end);
211 
212 	return url;
213 }
214 
215 static gboolean
216 invoke_link_if_present (GtkTextBuffer *buffer,
217                         GtkTextIter *iter)
218 {
219 	gboolean res;
220 	gchar *url;
221 
222 	g_return_val_if_fail (buffer != NULL, FALSE);
223 
224 	url = get_url_at_iter (buffer, iter);
225 
226 	res = url && *url;
227 	if (res)
228 		e_show_uri (NULL, url);
229 
230 	g_free (url);
231 
232 	return res;
233 }
234 
235 static void
236 remove_tag_if_present (GtkTextBuffer *buffer,
237                        GtkTextIter *where)
238 {
239 	GtkTextTagTable *tag_table;
240 	GtkTextTag *tag;
241 	GtkTextIter start, end;
242 
243 	g_return_if_fail (buffer != NULL);
244 	g_return_if_fail (where != NULL);
245 
246 	tag_table = gtk_text_buffer_get_tag_table (buffer);
247 	tag = gtk_text_tag_table_lookup (tag_table, E_BUFFER_TAGGER_LINK_TAG);
248 	g_return_if_fail (tag != NULL);
249 
250 	if (get_tag_bounds (where, tag, &start, &end))
251 		gtk_text_buffer_remove_tag (buffer, tag, &start, &end);
252 }
253 
254 static void
255 buffer_insert_text (GtkTextBuffer *buffer,
256                     GtkTextIter *location,
257                     gchar *text,
258                     gint len,
259                     gpointer user_data)
260 {
261 	update_state (buffer, E_BUFFER_TAGGER_STATE_INSDEL, TRUE);
262 	remove_tag_if_present (buffer, location);
263 }
264 
265 static void
266 buffer_delete_range (GtkTextBuffer *buffer,
267                      GtkTextIter *start,
268                      GtkTextIter *end,
269                      gpointer user_data)
270 {
271 	update_state (buffer, E_BUFFER_TAGGER_STATE_INSDEL, TRUE);
272 	remove_tag_if_present (buffer, start);
273 	remove_tag_if_present (buffer, end);
274 }
275 
276 static void
277 buffer_cursor_position (GtkTextBuffer *buffer,
278                         gpointer user_data)
279 {
280 	guint32 state;
281 
282 	state = get_state (buffer);
283 	if (state & E_BUFFER_TAGGER_STATE_INSDEL) {
284 		state = (state & (~E_BUFFER_TAGGER_STATE_INSDEL)) | E_BUFFER_TAGGER_STATE_CHANGED;
285 	} else {
286 		if (state & E_BUFFER_TAGGER_STATE_CHANGED) {
287 			markup_text (buffer);
288 		}
289 
290 		state = state & (~ (E_BUFFER_TAGGER_STATE_CHANGED | E_BUFFER_TAGGER_STATE_INSDEL));
291 	}
292 
293 	set_state (buffer, state);
294 }
295 
296 static void
297 update_mouse_cursor (GtkTextView *text_view,
298                      gint x,
299                      gint y)
300 {
301 	static GdkCursor *hand_cursor = NULL;
302 	static GdkCursor *regular_cursor = NULL;
303 	gboolean hovering = FALSE, hovering_over_link = FALSE, hovering_real;
304 	guint32 state;
305 	GtkTextBuffer *buffer = gtk_text_view_get_buffer (text_view);
306 	GtkTextTagTable *tag_table;
307 	GtkTextTag *tag;
308 	GtkTextIter iter;
309 
310 	if (!hand_cursor) {
311 		hand_cursor = gdk_cursor_new (GDK_HAND2);
312 		regular_cursor = gdk_cursor_new (GDK_XTERM);
313 	}
314 
315 	g_return_if_fail (buffer != NULL);
316 
317 	tag_table = gtk_text_buffer_get_tag_table (buffer);
318 	tag = gtk_text_tag_table_lookup (tag_table, E_BUFFER_TAGGER_LINK_TAG);
319 	g_return_if_fail (tag != NULL);
320 
321 	state = get_state (buffer);
322 
323 	gtk_text_view_get_iter_at_location (text_view, &iter, x, y);
324 	hovering_real = gtk_text_iter_has_tag (&iter, tag);
325 
326 	hovering_over_link = (state & E_BUFFER_TAGGER_STATE_IS_HOVERING) != 0;
327 	if ((state & E_BUFFER_TAGGER_STATE_CTRL_DOWN) == 0) {
328 		hovering = FALSE;
329 	} else {
330 		hovering = hovering_real;
331 	}
332 
333 	if (hovering != hovering_over_link) {
334 		update_state (buffer, E_BUFFER_TAGGER_STATE_IS_HOVERING, hovering);
335 
336 		if (hovering && gtk_widget_has_focus (GTK_WIDGET (text_view)))
337 			gdk_window_set_cursor (gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT), hand_cursor);
338 		else
339 			gdk_window_set_cursor (gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT), regular_cursor);
340 
341 		gdk_window_get_pointer (gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_WIDGET), NULL, NULL, NULL);
342 	}
343 
344 	hovering_over_link = (state & E_BUFFER_TAGGER_STATE_IS_HOVERING_TOOLTIP) != 0;
345 
346 	if (hovering_real != hovering_over_link) {
347 		update_state (buffer, E_BUFFER_TAGGER_STATE_IS_HOVERING_TOOLTIP, hovering_real);
348 
349 		gtk_widget_trigger_tooltip_query (GTK_WIDGET (text_view));
350 	}
351 }
352 
353 static gboolean
354 textview_query_tooltip (GtkTextView *text_view,
355                         gint x,
356                         gint y,
357                         gboolean keyboard_mode,
358                         GtkTooltip *tooltip,
359                         gpointer user_data)
360 {
361 	GtkTextBuffer *buffer;
362 	guint32 state;
363 	gboolean res = FALSE;
364 
365 	if (keyboard_mode)
366 		return FALSE;
367 
368 	buffer = gtk_text_view_get_buffer (text_view);
369 	g_return_val_if_fail (buffer != NULL, FALSE);
370 
371 	state = get_state (buffer);
372 
373 	if ((state & E_BUFFER_TAGGER_STATE_IS_HOVERING_TOOLTIP) != 0) {
374 		gchar *url;
375 		GtkTextIter iter;
376 
377 		gtk_text_view_window_to_buffer_coords (
378 			text_view,
379 			GTK_TEXT_WINDOW_WIDGET,
380 			x, y, &x, &y);
381 		gtk_text_view_get_iter_at_location (text_view, &iter, x, y);
382 
383 		url = get_url_at_iter (buffer, &iter);
384 		res = url && *url;
385 
386 		if (res) {
387 			gchar *str;
388 
389 			/* To Translators: The text is concatenated to a form: "Ctrl-click to open a link http://www.example.com" */
390 			str = g_strconcat (_("Ctrl-click to open a link"), " ", url, NULL);
391 			gtk_tooltip_set_text (tooltip, str);
392 			g_free (str);
393 		}
394 
395 		g_free (url);
396 	}
397 
398 	return res;
399 }
400 
401 /* Links can be activated by pressing Enter. */
402 static gboolean
403 textview_key_press_event (GtkWidget *text_view,
404                           GdkEventKey *event)
405 {
406 	GtkTextIter iter;
407 	GtkTextBuffer *buffer;
408 
409 	if ((event->state & GDK_CONTROL_MASK) == 0)
410 		return FALSE;
411 
412 	switch (event->keyval) {
413 	case GDK_KEY_Return:
414 	case GDK_KEY_KP_Enter:
415 		buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
416 		gtk_text_buffer_get_iter_at_mark (buffer, &iter, gtk_text_buffer_get_insert (buffer));
417 		if (invoke_link_if_present (buffer, &iter))
418 			return TRUE;
419 		break;
420 
421 	default:
422 		break;
423 	}
424 
425 	return FALSE;
426 }
427 
428 static void
429 update_ctrl_state (GtkTextView *textview,
430                    gboolean ctrl_is_down)
431 {
432 	GtkTextBuffer *buffer;
433 	gint x, y;
434 
435 	buffer = gtk_text_view_get_buffer (textview);
436 	if (buffer) {
437 		if (((get_state (buffer) & E_BUFFER_TAGGER_STATE_CTRL_DOWN) != 0) != (ctrl_is_down != FALSE)) {
438 			update_state (buffer, E_BUFFER_TAGGER_STATE_CTRL_DOWN, ctrl_is_down != FALSE);
439 		}
440 
441 		gdk_window_get_pointer (gtk_text_view_get_window (textview, GTK_TEXT_WINDOW_WIDGET), &x, &y, NULL);
442 		gtk_text_view_window_to_buffer_coords (textview, GTK_TEXT_WINDOW_WIDGET, x, y, &x, &y);
443 		update_mouse_cursor (textview, x, y);
444 	}
445 }
446 
447 /* Links can also be activated by clicking. */
448 static gboolean
449 textview_event_after (GtkTextView *textview,
450                       GdkEvent *ev)
451 {
452 	GtkTextIter start, end, iter;
453 	GtkTextBuffer *buffer;
454 	GdkEventButton *event;
455 	gint x, y;
456 	GdkModifierType mt = 0;
457 
458 	g_return_val_if_fail (GTK_IS_TEXT_VIEW (textview), FALSE);
459 
460 	if (ev->type == GDK_KEY_PRESS || ev->type == GDK_KEY_RELEASE) {
461 		GdkEventKey *event_key = (GdkEventKey *) ev;
462 
463 		switch (event_key->keyval) {
464 		case GDK_KEY_Control_L:
465 		case GDK_KEY_Control_R:
466 			update_ctrl_state (textview, ev->type == GDK_KEY_PRESS);
467 			break;
468 		}
469 
470 		return FALSE;
471 	}
472 
473 	if (!gdk_event_get_state (ev, &mt)) {
474 		GdkWindow *w = gtk_widget_get_parent_window (GTK_WIDGET (textview));
475 
476 		if (w)
477 			gdk_window_get_pointer (w, NULL, NULL, &mt);
478 	}
479 
480 	update_ctrl_state (textview, (mt & GDK_CONTROL_MASK) != 0);
481 
482 	if (ev->type != GDK_BUTTON_RELEASE)
483 		return FALSE;
484 
485 	event = (GdkEventButton *) ev;
486 
487 	if (event->button != 1 || (event->state & GDK_CONTROL_MASK) == 0)
488 		return FALSE;
489 
490 	buffer = gtk_text_view_get_buffer (textview);
491 
492 	/* we shouldn't follow a link if the user has selected something */
493 	gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
494 	if (gtk_text_iter_get_offset (&start) != gtk_text_iter_get_offset (&end))
495 		return FALSE;
496 
497 	gtk_text_view_window_to_buffer_coords (
498 		textview,
499 		GTK_TEXT_WINDOW_WIDGET,
500 		event->x, event->y, &x, &y);
501 
502 	gtk_text_view_get_iter_at_location (textview, &iter, x, y);
503 
504 	invoke_link_if_present (buffer, &iter);
505 	update_mouse_cursor (textview, x, y);
506 
507 	return FALSE;
508 }
509 
510 static gboolean
511 textview_motion_notify_event (GtkTextView *textview,
512                               GdkEventMotion *event)
513 {
514 	gint x, y;
515 
516 	g_return_val_if_fail (GTK_IS_TEXT_VIEW (textview), FALSE);
517 
518 	gtk_text_view_window_to_buffer_coords (
519 		textview,
520 		GTK_TEXT_WINDOW_WIDGET,
521 		event->x, event->y, &x, &y);
522 
523 	update_mouse_cursor (textview, x, y);
524 
525 	return FALSE;
526 }
527 
528 static gboolean
529 textview_visibility_notify_event (GtkTextView *textview,
530                                   GdkEventVisibility *event)
531 {
532 	gint wx, wy, bx, by;
533 
534 	g_return_val_if_fail (GTK_IS_TEXT_VIEW (textview), FALSE);
535 
536 	gdk_window_get_pointer (gtk_text_view_get_window (textview, GTK_TEXT_WINDOW_WIDGET), &wx, &wy, NULL);
537 
538 	gtk_text_view_window_to_buffer_coords (
539 		textview,
540 		GTK_TEXT_WINDOW_WIDGET,
541 		wx, wy, &bx, &by);
542 
543 	update_mouse_cursor (textview, bx, by);
544 
545 	return FALSE;
546 }
547 
548 void
549 e_buffer_tagger_connect (GtkTextView *textview)
550 {
551 	GtkTextBuffer *buffer;
552 	GtkTextTagTable *tag_table;
553 	GtkTextTag *tag;
554 
555 	init_magic_links ();
556 
557 	g_return_if_fail (textview != NULL);
558 	g_return_if_fail (GTK_IS_TEXT_VIEW (textview));
559 
560 	buffer = gtk_text_view_get_buffer (textview);
561 	tag_table = gtk_text_buffer_get_tag_table (buffer);
562 	tag = gtk_text_tag_table_lookup (tag_table, E_BUFFER_TAGGER_LINK_TAG);
563 
564 	/* if tag is there already, then it is connected, thus claim */
565 	g_return_if_fail (tag == NULL);
566 
567 	gtk_text_buffer_create_tag (
568 		buffer, E_BUFFER_TAGGER_LINK_TAG,
569 		"foreground", "blue",
570 		"underline", PANGO_UNDERLINE_SINGLE,
571 		NULL);
572 
573 	set_state (buffer, E_BUFFER_TAGGER_STATE_NONE);
574 
575 	g_signal_connect (
576 		buffer, "insert-text",
577 		G_CALLBACK (buffer_insert_text), NULL);
578 	g_signal_connect (
579 		buffer, "delete-range",
580 		G_CALLBACK (buffer_delete_range), NULL);
581 	g_signal_connect (
582 		buffer, "notify::cursor-position",
583 		G_CALLBACK (buffer_cursor_position), NULL);
584 
585 	gtk_widget_set_has_tooltip (GTK_WIDGET (textview), TRUE);
586 
587 	g_signal_connect (
588 		textview, "query-tooltip",
589 		G_CALLBACK (textview_query_tooltip), NULL);
590 	g_signal_connect (
591 		textview, "key-press-event",
592 		G_CALLBACK (textview_key_press_event), NULL);
593 	g_signal_connect (
594 		textview, "event-after",
595 		G_CALLBACK (textview_event_after), NULL);
596 	g_signal_connect (
597 		textview, "motion-notify-event",
598 		G_CALLBACK (textview_motion_notify_event), NULL);
599 	g_signal_connect (
600 		textview, "visibility-notify-event",
601 		G_CALLBACK (textview_visibility_notify_event), NULL);
602 }
603 
604 void
605 e_buffer_tagger_disconnect (GtkTextView *textview)
606 {
607 	GtkTextBuffer *buffer;
608 	GtkTextTagTable *tag_table;
609 	GtkTextTag *tag;
610 
611 	g_return_if_fail (textview != NULL);
612 	g_return_if_fail (GTK_IS_TEXT_VIEW (textview));
613 
614 	buffer = gtk_text_view_get_buffer (textview);
615 	tag_table = gtk_text_buffer_get_tag_table (buffer);
616 	tag = gtk_text_tag_table_lookup (tag_table, E_BUFFER_TAGGER_LINK_TAG);
617 
618 	/* if tag is not there, then it is not connected, thus claim */
619 	g_return_if_fail (tag != NULL);
620 
621 	gtk_text_tag_table_remove (tag_table, tag);
622 
623 	set_state (buffer, E_BUFFER_TAGGER_STATE_NONE);
624 
625 	g_signal_handlers_disconnect_by_func (buffer, G_CALLBACK (buffer_insert_text), NULL);
626 	g_signal_handlers_disconnect_by_func (buffer, G_CALLBACK (buffer_delete_range), NULL);
627 	g_signal_handlers_disconnect_by_func (buffer, G_CALLBACK (buffer_cursor_position), NULL);
628 
629 	gtk_widget_set_has_tooltip (GTK_WIDGET (textview), FALSE);
630 
631 	g_signal_handlers_disconnect_by_func (textview, G_CALLBACK (textview_query_tooltip), NULL);
632 	g_signal_handlers_disconnect_by_func (textview, G_CALLBACK (textview_key_press_event), NULL);
633 	g_signal_handlers_disconnect_by_func (textview, G_CALLBACK (textview_event_after), NULL);
634 	g_signal_handlers_disconnect_by_func (textview, G_CALLBACK (textview_motion_notify_event), NULL);
635 	g_signal_handlers_disconnect_by_func (textview, G_CALLBACK (textview_visibility_notify_event), NULL);
636 }
637 
638 void
639 e_buffer_tagger_update_tags (GtkTextView *textview)
640 {
641 	GtkTextBuffer *buffer;
642 	GtkTextTagTable *tag_table;
643 	GtkTextTag *tag;
644 
645 	g_return_if_fail (textview != NULL);
646 	g_return_if_fail (GTK_IS_TEXT_VIEW (textview));
647 
648 	buffer = gtk_text_view_get_buffer (textview);
649 	tag_table = gtk_text_buffer_get_tag_table (buffer);
650 	tag = gtk_text_tag_table_lookup (tag_table, E_BUFFER_TAGGER_LINK_TAG);
651 
652 	/* if tag is not there, then it is not connected, thus claim */
653 	g_return_if_fail (tag != NULL);
654 
655 	update_state (buffer, E_BUFFER_TAGGER_STATE_INSDEL | E_BUFFER_TAGGER_STATE_CHANGED, FALSE);
656 
657 	markup_text (buffer);
658 }