From 971a53bec2bf2ced267f94d1799fa288e08e8c28 Mon Sep 17 00:00:00 2001 From: Milan Crha Date: Thu, 15 Oct 2009 18:34:57 +0200 Subject: Bug #562512 - Make hyperlinks clickable in Memos, Tasks and Calendar --- calendar/gui/alarm-notify/alarm-notify-dialog.c | 4 + calendar/gui/dialogs/event-page.c | 8 + calendar/gui/dialogs/memo-page.c | 4 + calendar/gui/dialogs/task-page.c | 5 + widgets/misc/Makefile.am | 2 + widgets/misc/e-buffer-tagger.c | 534 ++++++++++++++++++++++++ widgets/misc/e-buffer-tagger.h | 35 ++ 7 files changed, 592 insertions(+) create mode 100644 widgets/misc/e-buffer-tagger.c create mode 100644 widgets/misc/e-buffer-tagger.h diff --git a/calendar/gui/alarm-notify/alarm-notify-dialog.c b/calendar/gui/alarm-notify/alarm-notify-dialog.c index e499e4332f..4efc863b1a 100644 --- a/calendar/gui/alarm-notify/alarm-notify-dialog.c +++ b/calendar/gui/alarm-notify/alarm-notify-dialog.c @@ -33,6 +33,7 @@ #include "config-data.h" #include "util.h" #include "e-util/e-util-private.h" +#include "misc/e-buffer-tagger.h" enum { ALARM_DISPLAY_COLUMN, @@ -286,6 +287,8 @@ notified_alarms_dialog_new (void) return NULL; } + e_buffer_tagger_connect (GTK_TEXT_VIEW (an->description)); + gtk_tree_view_set_model (GTK_TREE_VIEW(an->treeview), model); gtk_window_set_keep_above (GTK_WINDOW (an->dialog), TRUE); @@ -443,6 +446,7 @@ fill_in_labels (AlarmNotify *an, const gchar *summary, const gchar *description, gtk_text_buffer_set_text (buffer, description, -1); gtk_text_view_set_buffer (GTK_TEXT_VIEW (an->description), buffer); gtk_label_set_text (GTK_LABEL (an->location), location); + e_buffer_tagger_update_tags (GTK_TEXT_VIEW (an->description)); g_object_unref (table); g_object_unref (buffer); } diff --git a/calendar/gui/dialogs/event-page.c b/calendar/gui/dialogs/event-page.c index 5a9b8ec47a..83f783a365 100644 --- a/calendar/gui/dialogs/event-page.c +++ b/calendar/gui/dialogs/event-page.c @@ -41,6 +41,7 @@ #include "e-util/e-dialog-widgets.h" #include "misc/e-dateedit.h" #include "misc/e-send-options.h" +#include "misc/e-buffer-tagger.h" #include #include "../calendar-config.h" #include "../e-timezone-entry.h" @@ -484,6 +485,7 @@ clear_widgets (EventPage *epage) e_dialog_editable_set (priv->summary, NULL); e_dialog_editable_set (priv->location, NULL); gtk_text_buffer_set_text (gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->description)), "", 0); + e_buffer_tagger_update_tags (GTK_TEXT_VIEW (priv->description)); /* Start and end times */ g_signal_handlers_block_matched (priv->start_time, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, epage); @@ -972,8 +974,12 @@ event_page_fill_widgets (CompEditorPage *page, ECalComponent *comp) dtext = l->data; gtk_text_buffer_set_text (gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->description)), dtext->value ? dtext->value : "", -1); + } else { + gtk_text_buffer_set_text (gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->description)), + "", 0); } e_cal_component_free_text_list (l); + e_buffer_tagger_update_tags (GTK_TEXT_VIEW (priv->description)); e_cal_get_cal_address (client, &backend_addr, NULL); set_subscriber_info_string (epage, backend_addr); @@ -2762,6 +2768,8 @@ init_widgets (EventPage *epage) gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (priv->description), GTK_WRAP_WORD); + e_buffer_tagger_connect (GTK_TEXT_VIEW (priv->description)); + /* Start and end times */ g_signal_connect((priv->start_time), "changed", G_CALLBACK (start_date_changed_cb), epage); diff --git a/calendar/gui/dialogs/memo-page.c b/calendar/gui/dialogs/memo-page.c index 595a0e409e..6e51196a09 100644 --- a/calendar/gui/dialogs/memo-page.c +++ b/calendar/gui/dialogs/memo-page.c @@ -40,6 +40,7 @@ #include #include #include +#include "misc/e-buffer-tagger.h" #include "common/authentication.h" #include "e-util/e-dialog-widgets.h" @@ -124,6 +125,7 @@ clear_widgets (MemoPage *mpage) view = GTK_TEXT_VIEW (mpage->priv->memo_content); buffer = gtk_text_view_get_buffer (view); gtk_text_buffer_set_text (buffer, "", 0); + e_buffer_tagger_update_tags (view); /* Classification */ editor = comp_editor_page_get_editor (COMP_EDITOR_PAGE (mpage)); @@ -228,6 +230,7 @@ memo_page_fill_widgets (CompEditorPage *page, "", 0); } e_cal_component_free_text_list (l); + e_buffer_tagger_update_tags (GTK_TEXT_VIEW (priv->memo_content)); /* Start Date. */ e_cal_component_get_dtstart (comp, &d); @@ -969,6 +972,7 @@ init_widgets (MemoPage *mpage) view = GTK_TEXT_VIEW (priv->memo_content); buffer = gtk_text_view_get_buffer (view); gtk_text_view_set_wrap_mode (view, GTK_WRAP_WORD); + e_buffer_tagger_connect (view); /* Categories button */ g_signal_connect( diff --git a/calendar/gui/dialogs/task-page.c b/calendar/gui/dialogs/task-page.c index 2f0c258f3a..2b49192036 100644 --- a/calendar/gui/dialogs/task-page.c +++ b/calendar/gui/dialogs/task-page.c @@ -37,6 +37,7 @@ #include #include #include +#include "misc/e-buffer-tagger.h" #include #include "common/authentication.h" #include "e-util/e-dialog-widgets.h" @@ -263,6 +264,7 @@ clear_widgets (TaskPage *tpage) /* Summary, description */ e_dialog_editable_set (priv->summary, NULL); gtk_text_buffer_set_text (gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->description)), "", 0); + e_buffer_tagger_update_tags (GTK_TEXT_VIEW (priv->description)); /* Start, due times */ e_date_edit_set_time (E_DATE_EDIT (priv->start_date), 0); @@ -514,6 +516,7 @@ task_page_fill_widgets (CompEditorPage *page, ECalComponent *comp) "", 0); } e_cal_component_free_text_list (l); + e_buffer_tagger_update_tags (GTK_TEXT_VIEW (priv->description)); default_zone = calendar_config_get_icaltimezone (); @@ -1789,6 +1792,8 @@ init_widgets (TaskPage *tpage) gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (priv->description), GTK_WRAP_WORD); + e_buffer_tagger_connect (GTK_TEXT_VIEW (priv->description)); + /* Dates */ g_signal_connect((priv->start_date), "changed", G_CALLBACK (date_changed_cb), tpage); diff --git a/widgets/misc/Makefile.am b/widgets/misc/Makefile.am index da75d752bb..bf6291a0b2 100644 --- a/widgets/misc/Makefile.am +++ b/widgets/misc/Makefile.am @@ -36,6 +36,7 @@ widgetsinclude_HEADERS = \ e-attachment-store.h \ e-attachment-tree-view.h \ e-attachment-view.h \ + e-buffer-tagger.h \ e-calendar.h \ e-calendar-item.h \ e-canvas.h \ @@ -113,6 +114,7 @@ libemiscwidgets_la_SOURCES = \ e-attachment-store.c \ e-attachment-tree-view.c \ e-attachment-view.c \ + e-buffer-tagger.c \ e-calendar.c \ e-calendar-item.c \ e-canvas.c \ diff --git a/widgets/misc/e-buffer-tagger.c b/widgets/misc/e-buffer-tagger.c new file mode 100644 index 0000000000..ec5f3a1c45 --- /dev/null +++ b/widgets/misc/e-buffer-tagger.c @@ -0,0 +1,534 @@ +/* + * e-buffer-tagger.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#include +#include +#include +#include +#include +#include "e-util/e-util.h" +#include "e-buffer-tagger.h" + +enum EBufferTaggerState +{ + E_BUFFER_TAGGER_STATE_NONE = 0, + E_BUFFER_TAGGER_STATE_INSDEL = 1 << 0, /* set when was called insert or delete of a text */ + E_BUFFER_TAGGER_STATE_CHANGED = 1 << 1, /* remark of the buffer is scheduled */ + E_BUFFER_TAGGER_STATE_IS_HOVERING = 1 << 2, /* mouse is over the link */ + E_BUFFER_TAGGER_STATE_CTRL_DOWN = 1 << 3 /* Ctrl key is down */ +}; + +#define E_BUFFER_TAGGER_DATA_STATE "EBufferTagger::state" +#define E_BUFFER_TAGGER_LINK_TAG "EBufferTagger::link" + +struct _MagicInsertMatch +{ + const gchar *regex; + regex_t *preg; + const gchar *prefix; +}; + +typedef struct _MagicInsertMatch MagicInsertMatch; + +static MagicInsertMatch mim [] = { + /* prefixed expressions */ + { "(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_$.+!*(),;:@%&=?/~#']*[^]'.}>\\) ,?!;:\"]?)?", NULL, NULL }, + { "(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 }, + { "mailto:[-_a-z0-9.'\\+]+@[-_a-z0-9.%=?]+", NULL, NULL }, + /* not prefixed expression */ + { "www\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(([.])?/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]'.}>\\) ,?!;:\"]?)?", NULL, "http://" }, + { "ftp\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(([.])?/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]'.}>\\) ,?!;:\"]?)?", NULL, "ftp://" }, + { "[-_a-z0-9.'\\+]+@[-_a-z0-9.%=?]+", NULL, "mailto:" } +}; + +static void +init_magic_links (void) +{ + static gboolean done = FALSE; + gint i; + + if (done) + return; + + for (i = 0; i < G_N_ELEMENTS (mim); i++) { + mim [i].preg = g_new0 (regex_t, 1); + if (regcomp (mim [i].preg, mim [i].regex, REG_EXTENDED | REG_ICASE)) { + /* error */ + g_free (mim [i].preg); + mim [i].preg = 0; + } + } +} + +static void +markup_text (GtkTextBuffer *buffer) +{ + GtkTextIter start, end; + gchar *text; + int i; + regmatch_t pmatch [2]; + gboolean any; + const char *str; + gint offset = 0; + + g_return_if_fail (buffer != NULL); + + gtk_text_buffer_get_start_iter (buffer, &start); + gtk_text_buffer_get_end_iter (buffer, &end); + gtk_text_buffer_remove_tag_by_name (buffer, E_BUFFER_TAGGER_LINK_TAG, &start, &end); + text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE); + + str = text; + any = TRUE; + while (any) { + any = FALSE; + for (i = 0; i < G_N_ELEMENTS (mim); i++) { + if (mim [i].preg && !regexec (mim [i].preg, str, 2, pmatch, 0)) { + gtk_text_buffer_get_iter_at_offset (buffer, &start, offset + pmatch [0].rm_so); + gtk_text_buffer_get_iter_at_offset (buffer, &end, offset + pmatch [0].rm_eo - 1); + gtk_text_buffer_apply_tag_by_name (buffer, E_BUFFER_TAGGER_LINK_TAG, &start, &end); + + any = TRUE; + str += pmatch [0].rm_eo; + offset += pmatch [0].rm_eo; + break; + } + } + } + + g_free (text); +} + +static guint32 +get_state (GtkTextBuffer *buffer) +{ + g_return_val_if_fail (buffer != NULL, E_BUFFER_TAGGER_STATE_NONE); + g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), E_BUFFER_TAGGER_STATE_NONE); + + return GPOINTER_TO_INT (g_object_get_data (G_OBJECT (buffer), E_BUFFER_TAGGER_DATA_STATE)); +} + +static void +set_state (GtkTextBuffer *buffer, guint32 state) +{ + g_object_set_data (G_OBJECT (buffer), E_BUFFER_TAGGER_DATA_STATE, GINT_TO_POINTER (state)); +} + +static void +update_state (GtkTextBuffer *buffer, guint32 value, gboolean do_set) +{ + guint32 state; + + g_return_if_fail (buffer != NULL); + g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer)); + + state = get_state (buffer); + + if (do_set) + state = state | value; + else + state = state & (~value); + + set_state (buffer, state); +} + +static gboolean +get_tag_bounds (GtkTextIter *iter, GtkTextTag *tag, GtkTextIter *start, GtkTextIter *end) +{ + gboolean res = FALSE; + + g_return_val_if_fail (iter != NULL, FALSE); + g_return_val_if_fail (tag != NULL, FALSE); + g_return_val_if_fail (start != NULL, FALSE); + g_return_val_if_fail (end != NULL, FALSE); + + if (gtk_text_iter_has_tag (iter, tag)) { + *start = *iter; + *end = *iter; + + if (!gtk_text_iter_begins_tag (start, tag)) + gtk_text_iter_backward_to_tag_toggle (start, tag); + + if (!gtk_text_iter_ends_tag (end, tag)) + gtk_text_iter_forward_to_tag_toggle (end, tag); + + res = TRUE; + } + + return res; +} + +static gboolean +invoke_link_if_present (GtkTextBuffer *buffer, GtkTextIter *iter) +{ + GtkTextTagTable *tag_table; + GtkTextTag *tag; + GtkTextIter start, end; + gboolean res = FALSE; + + g_return_val_if_fail (buffer != NULL, FALSE); + + tag_table = gtk_text_buffer_get_tag_table (buffer); + tag = gtk_text_tag_table_lookup (tag_table, E_BUFFER_TAGGER_LINK_TAG); + g_return_val_if_fail (tag != NULL, FALSE); + + if (get_tag_bounds (iter, tag, &start, &end)) { + gchar *text; + + text = gtk_text_iter_get_text (&start, &end); + + res = text && *text; + if (res) + e_show_uri (NULL, text); + + g_free (text); + } + + return res; +} + +static void +remove_tag_if_present (GtkTextBuffer *buffer, GtkTextIter *where) +{ + GtkTextTagTable *tag_table; + GtkTextTag *tag; + GtkTextIter start, end; + + g_return_if_fail (buffer != NULL); + g_return_if_fail (where != NULL); + + tag_table = gtk_text_buffer_get_tag_table (buffer); + tag = gtk_text_tag_table_lookup (tag_table, E_BUFFER_TAGGER_LINK_TAG); + g_return_if_fail (tag != NULL); + + if (get_tag_bounds (where, tag, &start, &end)) + gtk_text_buffer_remove_tag (buffer, tag, &start, &end); +} + +static void +buffer_insert_text (GtkTextBuffer *buffer, GtkTextIter *location, gchar *text, gint len, gpointer user_data) +{ + update_state (buffer, E_BUFFER_TAGGER_STATE_INSDEL, TRUE); + remove_tag_if_present (buffer, location); +} + +static void +buffer_delete_range (GtkTextBuffer *buffer, GtkTextIter *start, GtkTextIter *end, gpointer user_data) +{ + update_state (buffer, E_BUFFER_TAGGER_STATE_INSDEL, TRUE); + remove_tag_if_present (buffer, start); + remove_tag_if_present (buffer, end); +} + +static void +buffer_cursor_position (GtkTextBuffer *buffer, gpointer user_data) +{ + guint32 state; + + state = get_state (buffer); + if (state & E_BUFFER_TAGGER_STATE_INSDEL) { + state = (state & (~E_BUFFER_TAGGER_STATE_INSDEL)) | E_BUFFER_TAGGER_STATE_CHANGED; + } else { + if (state & E_BUFFER_TAGGER_STATE_CHANGED) { + markup_text (buffer); + } + + state = state & (~ (E_BUFFER_TAGGER_STATE_CHANGED | E_BUFFER_TAGGER_STATE_INSDEL)); + } + + set_state (buffer, state); +} + +static void +update_mouse_cursor (GtkTextView *text_view, gint x, gint y) +{ + static GdkCursor *hand_cursor = NULL; + static GdkCursor *regular_cursor = NULL; + gboolean hovering = FALSE, hovering_over_link = FALSE; + guint32 state; + GtkTextBuffer *buffer = gtk_text_view_get_buffer (text_view); + GtkTextTagTable *tag_table; + GtkTextTag *tag; + + if (!hand_cursor) { + hand_cursor = gdk_cursor_new (GDK_HAND2); + regular_cursor = gdk_cursor_new (GDK_XTERM); + } + + g_return_if_fail (buffer != NULL); + + tag_table = gtk_text_buffer_get_tag_table (buffer); + tag = gtk_text_tag_table_lookup (tag_table, E_BUFFER_TAGGER_LINK_TAG); + g_return_if_fail (tag != NULL); + + state = get_state (buffer); + + hovering_over_link = (state & E_BUFFER_TAGGER_STATE_IS_HOVERING) != 0; + + if ((state & E_BUFFER_TAGGER_STATE_CTRL_DOWN) == 0) { + hovering = FALSE; + } else { + GtkTextIter iter; + + gtk_text_view_get_iter_at_location (text_view, &iter, x, y); + + hovering = gtk_text_iter_has_tag (&iter, tag); + } + + if (hovering != hovering_over_link) { + update_state (buffer, E_BUFFER_TAGGER_STATE_IS_HOVERING, hovering); + + if (hovering) + gdk_window_set_cursor (gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT), hand_cursor); + else + gdk_window_set_cursor (gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT), regular_cursor); + + gdk_window_get_pointer (gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_WIDGET), NULL, NULL, NULL); + } +} + +/* Links can be activated by pressing Enter. */ +static gboolean +textview_key_press_event (GtkWidget *text_view, GdkEventKey *event) +{ + GtkTextIter iter; + GtkTextBuffer *buffer; + + if ((event->state & GDK_CONTROL_MASK) == 0) + return FALSE; + + switch (event->keyval) { + case GDK_Return: + case GDK_KP_Enter: + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view)); + gtk_text_buffer_get_iter_at_mark (buffer, &iter, gtk_text_buffer_get_insert (buffer)); + if (invoke_link_if_present (buffer, &iter)) + return TRUE; + break; + + default: + break; + } + + return FALSE; +} + +static void +update_ctrl_state (GtkTextView *textview, gboolean ctrl_is_down) +{ + GtkTextBuffer *buffer; + gint x, y; + + buffer = gtk_text_view_get_buffer (textview); + if (buffer) { + if (((get_state (buffer) & E_BUFFER_TAGGER_STATE_CTRL_DOWN) != 0) != (ctrl_is_down != FALSE)) { + update_state (buffer, E_BUFFER_TAGGER_STATE_CTRL_DOWN, ctrl_is_down != FALSE); + } + + gdk_window_get_pointer (gtk_text_view_get_window (textview, GTK_TEXT_WINDOW_WIDGET), &x, &y, NULL); + gtk_text_view_window_to_buffer_coords (textview, GTK_TEXT_WINDOW_WIDGET, x, y, &x, &y); + update_mouse_cursor (textview, x, y); + } +} + +/* Links can also be activated by clicking. */ +static gboolean +textview_event_after (GtkTextView *textview, GdkEvent *ev) +{ + GtkTextIter start, end, iter; + GtkTextBuffer *buffer; + GdkEventButton *event; + gint x, y; + GdkModifierType mt = 0; + + g_return_val_if_fail (GTK_IS_TEXT_VIEW (textview), FALSE); + + if (ev->type == GDK_KEY_PRESS || ev->type == GDK_KEY_RELEASE) { + GdkEventKey *event_key = (GdkEventKey *)ev; + + switch (event_key->keyval) { + case GDK_Control_L: + case GDK_Control_R: + update_ctrl_state (textview, ev->type == GDK_KEY_PRESS); + break; + } + + return FALSE; + } + + if (!gdk_event_get_state (ev, &mt)) { + GdkWindow *w = gtk_widget_get_parent_window (GTK_WIDGET (textview)); + + if (w) + gdk_window_get_pointer (w, NULL, NULL, &mt); + } + + update_ctrl_state (textview, (mt & GDK_CONTROL_MASK) != 0); + + if (ev->type != GDK_BUTTON_RELEASE) + return FALSE; + + event = (GdkEventButton *)ev; + + if (event->button != 1 || (event->state & GDK_CONTROL_MASK) == 0) + return FALSE; + + buffer = gtk_text_view_get_buffer (textview); + + /* we shouldn't follow a link if the user has selected something */ + gtk_text_buffer_get_selection_bounds (buffer, &start, &end); + if (gtk_text_iter_get_offset (&start) != gtk_text_iter_get_offset (&end)) + return FALSE; + + gtk_text_view_window_to_buffer_coords (textview, + GTK_TEXT_WINDOW_WIDGET, + event->x, event->y, &x, &y); + + gtk_text_view_get_iter_at_location (textview, &iter, x, y); + + invoke_link_if_present (buffer, &iter); + update_mouse_cursor (textview, x, y); + + return FALSE; +} + +static gboolean +textview_motion_notify_event (GtkTextView *textview, GdkEventMotion *event) +{ + gint x, y; + + g_return_val_if_fail (GTK_IS_TEXT_VIEW (textview), FALSE); + + gtk_text_view_window_to_buffer_coords (textview, + GTK_TEXT_WINDOW_WIDGET, + event->x, event->y, &x, &y); + + update_mouse_cursor (textview, x, y); + + return FALSE; +} + +static gboolean +textview_visibility_notify_event (GtkTextView *textview, GdkEventVisibility *event) +{ + gint wx, wy, bx, by; + + g_return_val_if_fail (GTK_IS_TEXT_VIEW (textview), FALSE); + + gdk_window_get_pointer (gtk_text_view_get_window (textview, GTK_TEXT_WINDOW_WIDGET), &wx, &wy, NULL); + + gtk_text_view_window_to_buffer_coords (textview, + GTK_TEXT_WINDOW_WIDGET, + wx, wy, &bx, &by); + + update_mouse_cursor (textview, bx, by); + + return FALSE; +} + +void +e_buffer_tagger_connect (GtkTextView *textview) +{ + GtkTextBuffer *buffer; + GtkTextTagTable *tag_table; + GtkTextTag *tag; + + init_magic_links (); + + g_return_if_fail (textview != NULL); + g_return_if_fail (GTK_IS_TEXT_VIEW (textview)); + + buffer = gtk_text_view_get_buffer (textview); + tag_table = gtk_text_buffer_get_tag_table (buffer); + tag = gtk_text_tag_table_lookup (tag_table, E_BUFFER_TAGGER_LINK_TAG); + + /* if tag is there already, then it is connected, thus claim */ + g_return_if_fail (tag == NULL); + + gtk_text_buffer_create_tag (buffer, E_BUFFER_TAGGER_LINK_TAG, + "foreground", "blue", + "underline", PANGO_UNDERLINE_SINGLE, + NULL); + + set_state (buffer, E_BUFFER_TAGGER_STATE_NONE); + + g_signal_connect (buffer, "insert-text", G_CALLBACK (buffer_insert_text), NULL); + g_signal_connect (buffer, "delete-range", G_CALLBACK (buffer_delete_range), NULL); + g_signal_connect (buffer, "notify::cursor-position", G_CALLBACK (buffer_cursor_position), NULL); + + g_signal_connect (textview, "key-press-event", G_CALLBACK (textview_key_press_event), NULL); + g_signal_connect (textview, "event-after", G_CALLBACK (textview_event_after), NULL); + g_signal_connect (textview, "motion-notify-event", G_CALLBACK (textview_motion_notify_event), NULL); + g_signal_connect (textview, "visibility-notify-event", G_CALLBACK (textview_visibility_notify_event), NULL); +} + +void +e_buffer_tagger_disconnect (GtkTextView *textview) +{ + GtkTextBuffer *buffer; + GtkTextTagTable *tag_table; + GtkTextTag *tag; + + g_return_if_fail (textview != NULL); + g_return_if_fail (GTK_IS_TEXT_VIEW (textview)); + + buffer = gtk_text_view_get_buffer (textview); + tag_table = gtk_text_buffer_get_tag_table (buffer); + tag = gtk_text_tag_table_lookup (tag_table, E_BUFFER_TAGGER_LINK_TAG); + + /* if tag is not there, then it is not connected, thus claim */ + g_return_if_fail (tag != NULL); + + gtk_text_tag_table_remove (tag_table, tag); + + set_state (buffer, E_BUFFER_TAGGER_STATE_NONE); + + g_signal_handlers_disconnect_by_func (buffer, G_CALLBACK (buffer_insert_text), NULL); + g_signal_handlers_disconnect_by_func (buffer, G_CALLBACK (buffer_delete_range), NULL); + g_signal_handlers_disconnect_by_func (buffer, G_CALLBACK (buffer_cursor_position), NULL); + + g_signal_handlers_disconnect_by_func (textview, G_CALLBACK (textview_key_press_event), NULL); + g_signal_handlers_disconnect_by_func (textview, G_CALLBACK (textview_event_after), NULL); + g_signal_handlers_disconnect_by_func (textview, G_CALLBACK (textview_motion_notify_event), NULL); + g_signal_handlers_disconnect_by_func (textview, G_CALLBACK (textview_visibility_notify_event), NULL); +} + +void +e_buffer_tagger_update_tags (GtkTextView *textview) +{ + GtkTextBuffer *buffer; + GtkTextTagTable *tag_table; + GtkTextTag *tag; + + g_return_if_fail (textview != NULL); + g_return_if_fail (GTK_IS_TEXT_VIEW (textview)); + + buffer = gtk_text_view_get_buffer (textview); + tag_table = gtk_text_buffer_get_tag_table (buffer); + tag = gtk_text_tag_table_lookup (tag_table, E_BUFFER_TAGGER_LINK_TAG); + + /* if tag is not there, then it is not connected, thus claim */ + g_return_if_fail (tag != NULL); + + update_state (buffer, E_BUFFER_TAGGER_STATE_INSDEL | E_BUFFER_TAGGER_STATE_CHANGED, FALSE); + + markup_text (buffer); +} diff --git a/widgets/misc/e-buffer-tagger.h b/widgets/misc/e-buffer-tagger.h new file mode 100644 index 0000000000..86e6710d01 --- /dev/null +++ b/widgets/misc/e-buffer-tagger.h @@ -0,0 +1,35 @@ +/* + * e-buffer-tagger.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_BUFFER_TAGGER_H +#define E_BUFFER_TAGGER_H + +#include + +G_BEGIN_DECLS + +void e_buffer_tagger_connect (GtkTextView *textview); +void e_buffer_tagger_disconnect (GtkTextView *textview); +void e_buffer_tagger_update_tags (GtkTextView *textview); + +G_END_DECLS + +#endif /* E_BUFFER_TAGGER_H */ -- cgit v1.2.3