[evolution-data-server/wip/mcrha/gtk4] Initial setup - does not build



commit fa073f345e1bc92f1ec9387a47f61fd85ece9031
Author: Milan Crha <mcrha redhat com>
Date:   Fri Oct 8 12:48:59 2021 +0200

    Initial setup - does not build

 CMakeLists.txt                              |  35 +-
 src/CMakeLists.txt                          |   5 +-
 src/libedataserverui/CMakeLists.txt         | 168 ++++-
 src/libedataserverui/e-buffer-tagger4.c     | 946 ++++++++++++++++++++++++++++
 src/libedataserverui/libedataserverui.pc.in |  10 +-
 5 files changed, 1146 insertions(+), 18 deletions(-)
---
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6358035bf..0ea56b2e4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -64,6 +64,11 @@ set(LIBEDATASERVERUI_CURRENT 3)
 set(LIBEDATASERVERUI_REVISION 0)
 set(LIBEDATASERVERUI_AGE 0)
 
+set(LIBEDATASERVERUI4_API_VERSION 1.0)
+set(LIBEDATASERVERUI4_CURRENT 0)
+set(LIBEDATASERVERUI4_REVISION 0)
+set(LIBEDATASERVERUI4_AGE 0)
+
 set(LIBEBOOK_CURRENT 20)
 set(LIBEBOOK_REVISION 1)
 set(LIBEBOOK_AGE 3)
@@ -92,13 +97,16 @@ set(glib_encoded_version GLIB_VERSION_2_46)
 set(gdk_minimum_version 3.16)
 set(gdk_encoded_version GDK_VERSION_3_16)
 
+# Keep these two definitions in agreement.
+set(gdk4_minimum_version 4.4)
+set(gdk4_encoded_version GDK_VERSION_4_4)
+
 # Keep these two definitions in agreement.
 set(soup_minimum_version 2.58)
 set(soup_encoded_version SOUP_VERSION_2_58)
 
 # Warn about API usage that violates our minimum requirements.
 add_definitions(-DGLIB_VERSION_MAX_ALLOWED=${glib_encoded_version})
-add_definitions(-DGDK_VERSION_MAX_ALLOWED=${gdk_encoded_version})
 add_definitions(-DSOUP_VERSION_MAX_ALLOWED=${soup_encoded_version})
 
 # These will suppress warnings about newly-deprecated symbols. Ideally
@@ -106,7 +114,6 @@ add_definitions(-DSOUP_VERSION_MAX_ALLOWED=${soup_encoded_version})
 # up any new deprecation warnings after bumping our minimum requirements.
 # But if the warnings get to be overwhelming, use fixed versions instead.
 add_definitions(-DGLIB_VERSION_MIN_REQUIRED=${glib_encoded_version})
-add_definitions(-DGDK_VERSION_MIN_REQUIRED=${gdk_encoded_version})
 add_definitions(-DSOUP_VERSION_MIN_REQUIRED=${soup_encoded_version})
 
 set(gcr_minimum_version 3.4)
@@ -123,6 +130,7 @@ set(libaccounts_glib_minimum_version 1.4)
 set(libsignon_glib_minimum_version 1.8)
 set(json_glib_minimum_version 1.0.4)
 set(webkit2gtk_minimum_version 2.28.0)
+set(webkit2gtk4_minimum_version 2.36.0)
 set(libcanberra_gtk_minimum_version 0.25)
 
 # Load modules from the source tree
@@ -388,6 +396,14 @@ if(ENABLE_GTK)
        set(HAVE_GTK 1)
 endif(ENABLE_GTK)
 
+add_printable_option(ENABLE_GTK4 "Enable gtk4 support" ON)
+if(ENABLE_GTK4)
+       pkg_check_modules_for_option(ENABLE_GTK4 "gtk4 support" GTK4 gtk4>=${gdk4_minimum_version})
+       #pkg_check_modules_for_option(ENABLE_GTK4 "gtk4 support" GCR4 gcr-4>=${gcr4_minimum_version})
+
+       set(HAVE_GTK4 1)
+endif(ENABLE_GTK4)
+
 # **************************************************************
 # Check for WebKitGTK+ and json-glib for OAuth2 authentications
 # **************************************************************
@@ -399,9 +415,16 @@ if(ENABLE_OAUTH2)
                json-glib-1.0>=${json_glib_minimum_version}
        )
 
-       pkg_check_modules_for_option(ENABLE_OAUTH2 "OAuth2 authentication support" OAUTH2_WEBKIT2GTK
-               webkit2gtk-4.0>=${webkit2gtk_minimum_version}
-       )
+       if(ENABLE_GTK)
+               pkg_check_modules_for_option(ENABLE_OAUTH2 "OAuth2 authentication support" OAUTH2_WEBKIT2GTK
+                       webkit2gtk-4.0>=${webkit2gtk_minimum_version}
+               )
+       endif(ENABLE_GTK)
+       if(ENABLE_GTK4)
+               #pkg_check_modules_for_option(ENABLE_OAUTH2 "OAuth2 authentication support" OAUTH2_WEBKIT2GTK4
+               #       webkit2gtk-4.0>=${webkit2gtk4_minimum_version}
+               #)
+       endif(ENABLE_GTK4)
 
        add_printable_variable(WITH_GOOGLE_CLIENT_ID "Google OAuth 2.0 client id" "")
        add_printable_variable(WITH_GOOGLE_CLIENT_SECRET "Google OAuth 2.0 client secret" "")
@@ -447,7 +470,7 @@ add_printable_option(ENABLE_EXAMPLES "Enable the build of examples" ON)
 if(ENABLE_EXAMPLES)
        pkg_check_modules_for_option(ENABLE_EXAMPLES "build the example program(s)" EXAMPLES
                gtk+-3.0>=3.10
-               glib-2.0>=2.38
+               glib-2.0>=${glib_minimum_version}
        )
 
        set(BUILD_EXAMPLES 1)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 50ab1cdc9..f7d24f098 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,5 +1,6 @@
 add_subdirectory(camel)
 add_subdirectory(libedataserver)
+add_subdirectory(libedataserverui)
 add_subdirectory(libebackend)
 add_subdirectory(addressbook)
 add_subdirectory(calendar)
@@ -8,10 +9,6 @@ add_subdirectory(private)
 add_subdirectory(services)
 add_subdirectory(tools)
 
-if(HAVE_GTK)
-       add_subdirectory(libedataserverui)
-endif(HAVE_GTK)
-
 if(ENABLE_EXAMPLES)
        add_subdirectory(examples)
 endif(ENABLE_EXAMPLES)
diff --git a/src/libedataserverui/CMakeLists.txt b/src/libedataserverui/CMakeLists.txt
index 63f594fbe..79ecd73f0 100644
--- a/src/libedataserverui/CMakeLists.txt
+++ b/src/libedataserverui/CMakeLists.txt
@@ -1,4 +1,8 @@
-add_pkgconfig_file(libedataserverui.pc.in libedataserverui-${API_VERSION}.pc)
+if(ENABLE_GTK)
+set(GTK_DEPENDENCY gtk+-3.0)
+set(UI_VERSION)
+set(UI_API_VERSION ${API_VERSION})
+add_pkgconfig_file(libedataserverui.pc.in libedataserverui-${UI_API_VERSION}.pc)
 
 set(SOURCES
        e-buffer-tagger.c
@@ -45,7 +49,7 @@ add_dependencies(edataserverui
 set_target_properties(edataserverui PROPERTIES
        VERSION "${LIBEDATASERVERUI_CURRENT}.${LIBEDATASERVERUI_REVISION}.${LIBEDATASERVERUI_AGE}"
        SOVERSION ${LIBEDATASERVERUI_CURRENT}
-       OUTPUT_NAME edataserverui-${API_VERSION}
+       OUTPUT_NAME edataserverui-${UI_API_VERSION}
 )
 
 target_compile_definitions(edataserverui PRIVATE
@@ -53,6 +57,8 @@ target_compile_definitions(edataserverui PRIVATE
        -DLIBEDATASERVERUI_COMPILATION
        -DE_DATA_SERVER_PREFIX=\"${CMAKE_INSTALL_PREFIX}\"
        -DE_DATA_SERVER_UIMODULEDIR=\"${uimoduledir}\"
+       -DGDK_VERSION_MAX_ALLOWED=${gdk_encoded_version}
+       -DGDK_VERSION_MIN_REQUIRED=${gdk_encoded_version}
 )
 
 target_compile_options(edataserverui PUBLIC
@@ -133,7 +139,7 @@ set(gir_deps
 gir_add_introspection_simple(
        EDataServerUI
        libedataserverui
-       ${API_VERSION}
+       ${UI_API_VERSION}
        "libedataserverui/libedataserverui.h"
        gir_identifies_prefixes
        gir_includes
@@ -143,3 +149,159 @@ gir_add_introspection_simple(
        gir_deps
        gir_sources
 )
+endif(ENABLE_GTK)
+
+#-----------------------------------------------------------------------------
+
+if(ENABLE_GTK4)
+set(GTK_DEPENDENCY gtk4)
+set(UI_VERSION 4)
+set(UI_API_VERSION ${LIBEDATASERVERUI4_API_VERSION})
+add_pkgconfig_file(libedataserverui.pc.in libedataserverui4-${UI_API_VERSION}.pc)
+
+set(SOURCES
+       #e-buffer-tagger4.c
+       e-cell-renderer-color.c
+       e-credentials-prompter.c
+       e-credentials-prompter-impl.c
+       #e-credentials-prompter-impl-oauth2.c
+       e-credentials-prompter-impl-password.c
+       e-reminders-widget.c
+       #e-trust-prompt.c
+       e-webdav-discover-widget.c
+       libedataserverui-private.h
+       libedataserverui-private.c
+)
+
+set(HEADERS
+       libedataserverui.h
+       e-buffer-tagger.h
+       e-cell-renderer-color.h
+       e-credentials-prompter.h
+       e-credentials-prompter-impl.h
+       #e-credentials-prompter-impl-oauth2.h
+       e-credentials-prompter-impl-password.h
+       e-reminders-widget.h
+       #e-trust-prompt.h
+       e-webdav-discover-widget.h
+)
+
+set(DEPENDENCIES
+       camel
+       ecal
+       edataserver
+)
+
+add_library(edataserverui${UI_VERSION} SHARED
+       ${SOURCES}
+       ${HEADERS}
+)
+
+add_dependencies(edataserverui${UI_VERSION}
+       ${DEPENDENCIES}
+)
+
+set_target_properties(edataserverui PROPERTIES
+       VERSION "${LIBEDATASERVERUI4_CURRENT}.${LIBEDATASERVERUI4_REVISION}.${LIBEDATASERVERUI4_AGE}"
+       SOVERSION ${LIBEDATASERVERUI_CURRENT}
+       OUTPUT_NAME edataserverui${UI_VERSION}-${UI_API_VERSION}
+)
+
+target_compile_definitions(edataserverui${UI_VERSION} PRIVATE
+       -DG_LOG_DOMAIN=\"e-data-server-ui${UI_VERSION}\"
+       -DLIBEDATASERVERUI_COMPILATION
+       -DE_DATA_SERVER_PREFIX=\"${CMAKE_INSTALL_PREFIX}\"
+       -DE_DATA_SERVER_UIMODULEDIR=\"${uimoduledir}${UI_VERSION}\"
+       -DGDK_VERSION_MAX_ALLOWED=${gdk4_encoded_version}
+       -DGDK_VERSION_MIN_REQUIRED=${gdk4_encoded_version}
+)
+
+target_compile_options(edataserverui${UI_VERSION} PUBLIC
+       ${DATA_SERVER_CFLAGS}
+       ${CAMEL_CFLAGS}
+       ${CODE_COVERAGE_CFLAGS}
+       ${GNOME_PLATFORM_CFLAGS}
+       ${GCR4_BASE_CFLAGS}
+       ${GCR4_CFLAGS}
+       ${GTK4_CFLAGS}
+       ${OAUTH2_JSON_GLIB_CFLAGS}
+       ${OAUTH2_WEBKIT2GTK4_CFLAGS}
+)
+
+target_include_directories(edataserverui${UI_VERSION} PUBLIC
+       ${CMAKE_BINARY_DIR}
+       ${CMAKE_BINARY_DIR}/src
+       ${CMAKE_SOURCE_DIR}/src
+       ${CMAKE_CURRENT_BINARY_DIR}
+       ${DATA_SERVER_INCLUDE_DIRS}
+       ${CAMEL_INCLUDE_DIRS}
+       ${CODE_COVERAGE_INCLUDE_DIRS}
+       ${GNOME_PLATFORM_INCLUDE_DIRS}
+       ${GCR4_BASE_INCLUDE_DIRS}
+       ${GCR4_INCLUDE_DIRS}
+       ${GTK4_INCLUDE_DIRS}
+       ${OAUTH2_JSON_GLIB_INCLUDE_DIRS}
+       ${OAUTH2_WEBKIT2GTK4_INCLUDE_DIRS}
+)
+
+target_link_libraries(edataserverui${UI_VERSION}
+       ${DEPENDENCIES}
+       ${DATA_SERVER_LDFLAGS}
+       ${CAMEL_LDFLAGS}
+       ${CODE_COVERAGE_LDFLAGS}
+       ${GNOME_PLATFORM_LDFLAGS}
+       ${GCR4_BASE_LDFLAGS}
+       ${GCR4_LDFLAGS}
+       ${GTK4_LDFLAGS}
+       ${OAUTH2_JSON_GLIB_LDFLAGS}
+       ${OAUTH2_WEBKIT2GTK4_LDFLAGS}
+)
+
+install(TARGETS edataserverui${UI_VERSION}
+       DESTINATION ${LIB_INSTALL_DIR}
+)
+
+install(FILES ${HEADERS}
+       DESTINATION ${privincludedir}/libedataserverui${UI_VERSION}
+)
+
+set(gir_sources ${SOURCES} ${HEADERS})
+set(gir_identifies_prefixes E)
+set(gir_includes GObject-2.0 Gio-2.0 Gtk4 Soup-2.4)
+set(gir_cflags
+       -DLIBEDATASERVERUI_COMPILATION
+       -I${CMAKE_BINARY_DIR}/src/calendar
+       -I${CMAKE_SOURCE_DIR}/src/calendar
+)
+set(gir_libdirs
+       ${CMAKE_BINARY_DIR}/src/private
+       ${CMAKE_BINARY_DIR}/src/calendar/libecal
+       ${CMAKE_BINARY_DIR}/src/camel
+       ${CMAKE_BINARY_DIR}/src/libedataserver
+)
+set(gir_libs
+       camel
+       ecal
+       edataserver
+       edataserverui${UI_VERSION}
+)
+set(gir_deps
+       ${CMAKE_BINARY_DIR}/src/camel/Camel-${API_VERSION}.gir
+       ${CMAKE_BINARY_DIR}/src/libedataserver/EDataServer-${API_VERSION}.gir
+       ${CMAKE_BINARY_DIR}/src/calendar/libecal/ECal-${CAL_API_VERSION}.gir
+)
+
+gir_add_introspection_simple(
+       EDataServerUI${UI_VERSION}
+       libedataserverui${UI_VERSION}
+       ${UI_API_VERSION}
+       "libedataserverui${UI_VERSION}/libedataserverui.h"
+       gir_identifies_prefixes
+       gir_includes
+       gir_cflags
+       gir_libdirs
+       gir_libs
+       gir_deps
+       gir_sources
+)
+endif(ENABLE_GTK4)
diff --git a/src/libedataserverui/e-buffer-tagger4.c b/src/libedataserverui/e-buffer-tagger4.c
new file mode 100644
index 000000000..468c9d3c4
--- /dev/null
+++ b/src/libedataserverui/e-buffer-tagger4.c
@@ -0,0 +1,946 @@
+/*
+ * 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.
+ *
+ * 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 General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n-lib.h>
+#include <regex.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "e-buffer-tagger.h"
+
+static void
+e_show_uri (GtkWindow *parent,
+           const gchar *uri)
+{
+       GtkWidget *dialog;
+       GdkScreen *screen = NULL;
+       gchar *scheme;
+       GError *error = NULL;
+       guint32 timestamp;
+       gboolean success;
+
+       g_return_if_fail (uri != NULL);
+
+       timestamp = gtk_get_current_event_time ();
+
+       if (parent != NULL)
+               screen = gtk_widget_get_screen (GTK_WIDGET (parent));
+
+       scheme = g_uri_parse_scheme (uri);
+
+       if (!scheme || !*scheme) {
+               gchar *schemed_uri;
+
+               schemed_uri = g_strconcat ("http://";, uri, NULL);
+               success = gtk_show_uri (screen, schemed_uri, timestamp, &error);
+               g_free (schemed_uri);
+       } else {
+               success = gtk_show_uri (screen, uri, timestamp, &error);
+       }
+
+       g_free (scheme);
+
+       if (success)
+               return;
+
+       dialog = gtk_message_dialog_new_with_markup (
+               parent, GTK_DIALOG_DESTROY_WITH_PARENT,
+               GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+               "<big><b>%s</b></big>",
+               _("Could not open the link."));
+
+       gtk_message_dialog_format_secondary_text (
+               GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
+
+       gtk_dialog_run (GTK_DIALOG (dialog));
+
+       gtk_widget_destroy (dialog);
+       g_error_free (error);
+}
+
+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_IS_HOVERING_TOOLTIP = 1 << 3, /* mouse is over the link and the tooltip can be 
shown */
+       E_BUFFER_TAGGER_STATE_CTRL_DOWN = 1 << 4  /* 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_$.+!*(),;:@%&=?/~#']*[^]'.}>\\)
 \n\r\t,?!;:\"]?)?", NULL, NULL },
+       { 
"(sip|h323|callto|tel):([-_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_$.+!*(),;:@%&=?/~#]*[^]'.}>\\) 
\n\r\t,?!;:\"]?)?", NULL, "http://"; },
+       { "ftp\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(([.])?/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]'.}>\\) 
\n\r\t,?!;:\"]?)?", 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;
+
+       done = TRUE;
+
+       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;
+       gint i;
+       regmatch_t pmatch[2];
+       gboolean any;
+       const gchar *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)) {
+                               gint char_so, char_eo, rm_eo;
+
+                               /* Stop on the angle brackets, which cannot be part of the URL (see RFC 3986 
Appendix C) */
+                               for (rm_eo = pmatch[0].rm_eo - 1; rm_eo > pmatch[0].rm_so; rm_eo--) {
+                                       if (str[rm_eo] == '<' || str[rm_eo] == '>') {
+                                               pmatch[0].rm_eo = rm_eo;
+                                               break;
+                                       }
+                               }
+
+                               rm_eo = pmatch[0].rm_eo;
+
+                               /* URLs are extremely unlikely to end with any
+                                * punctuation, so strip any trailing
+                                * punctuation off. Also strip off any closing
+                                * double-quotes. */
+                               while (rm_eo > pmatch[0].rm_so && strchr (",.:;?!-|}])\">", str[rm_eo - 1])) {
+                                       gchar open_bracket = 0, close_bracket = str[rm_eo - 1];
+
+                                       if (close_bracket == ')')
+                                               open_bracket = '(';
+                                       else if (close_bracket == '}')
+                                               open_bracket = '{';
+                                       else if (close_bracket == ']')
+                                               open_bracket = '[';
+                                       else if (close_bracket == '>')
+                                               open_bracket = '<';
+
+                                       if (open_bracket != 0) {
+                                               const gchar *ptr, *endptr;
+                                               gint n_opened = 0, n_closed = 0;
+
+                                               endptr = str + rm_eo;
+
+                                               for (ptr = str + pmatch[0].rm_so; ptr < endptr; ptr++) {
+                                                       if (*ptr == open_bracket)
+                                                               n_opened++;
+                                                       else if (*ptr == close_bracket)
+                                                               n_closed++;
+                                               }
+
+                                               /* The closing bracket can match one inside the URL,
+                                                  thus keep it there. */
+                                               if (n_opened > 0 && n_opened - n_closed >= 0)
+                                                       break;
+                                       }
+
+                                       rm_eo--;
+                                       pmatch[0].rm_eo--;
+                               }
+
+                               char_so = g_utf8_pointer_to_offset (str, str + pmatch[0].rm_so);
+                               char_eo = g_utf8_pointer_to_offset (str, str + pmatch[0].rm_eo);
+
+                               gtk_text_buffer_get_iter_at_offset (buffer, &start, offset + char_so);
+                               gtk_text_buffer_get_iter_at_offset (buffer, &end, offset + char_eo);
+                               gtk_text_buffer_apply_tag_by_name (buffer, E_BUFFER_TAGGER_LINK_TAG, &start, 
&end);
+
+                               any = TRUE;
+                               str += pmatch[0].rm_eo;
+                               offset += char_eo;
+                               break;
+                       }
+               }
+       }
+
+       g_free (text);
+}
+
+static void
+get_pointer_position (GtkTextView *text_view,
+                      gint *x,
+                      gint *y)
+{
+       GdkWindow *window;
+       GdkDisplay *display;
+       GdkDeviceManager *device_manager;
+       GdkDevice *device;
+
+       window = gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_WIDGET);
+       display = gdk_window_get_display (window);
+       device_manager = gdk_display_get_device_manager (display);
+       device = gdk_device_manager_get_client_pointer (device_manager);
+
+       gdk_window_get_device_position (window, device, x, y, NULL);
+}
+
+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 gchar *
+get_url_at_iter (GtkTextBuffer *buffer,
+                 GtkTextIter *iter)
+{
+       GtkTextTagTable *tag_table;
+       GtkTextTag *tag;
+       GtkTextIter start, end;
+       gchar *url = NULL;
+
+       g_return_val_if_fail (buffer != NULL, 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_val_if_fail (tag != NULL, NULL);
+
+       if (get_tag_bounds (iter, tag, &start, &end))
+               url = gtk_text_iter_get_text (&start, &end);
+
+       return url;
+}
+
+static gboolean
+invoke_link_if_present (GtkTextBuffer *buffer,
+                        GtkTextIter *iter)
+{
+       gboolean res;
+       gchar *url;
+
+       g_return_val_if_fail (buffer != NULL, FALSE);
+
+       url = get_url_at_iter (buffer, iter);
+
+       res = url && *url;
+       if (res)
+               e_show_uri (NULL, url);
+
+       g_free (url);
+
+       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, hovering_real;
+       guint32 state;
+       GtkTextBuffer *buffer = gtk_text_view_get_buffer (text_view);
+       GtkTextTagTable *tag_table;
+       GtkTextTag *tag;
+       GtkTextIter iter;
+
+       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);
+
+       gtk_text_view_get_iter_at_location (text_view, &iter, x, y);
+       hovering_real = gtk_text_iter_has_tag (&iter, tag);
+
+       hovering_over_link = (state & E_BUFFER_TAGGER_STATE_IS_HOVERING) != 0;
+       if ((state & E_BUFFER_TAGGER_STATE_CTRL_DOWN) == 0) {
+               hovering = FALSE;
+       } else {
+               hovering = hovering_real;
+       }
+
+       if (hovering != hovering_over_link) {
+               update_state (buffer, E_BUFFER_TAGGER_STATE_IS_HOVERING, hovering);
+
+               if (hovering && gtk_widget_has_focus (GTK_WIDGET (text_view)))
+                       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);
+
+               /* XXX Is this necessary?  Appears to be a no-op. */
+               get_pointer_position (text_view, NULL, NULL);
+       }
+
+       hovering_over_link = (state & E_BUFFER_TAGGER_STATE_IS_HOVERING_TOOLTIP) != 0;
+
+       if (hovering_real != hovering_over_link) {
+               update_state (buffer, E_BUFFER_TAGGER_STATE_IS_HOVERING_TOOLTIP, hovering_real);
+
+               gtk_widget_trigger_tooltip_query (GTK_WIDGET (text_view));
+       }
+}
+
+static void
+textview_style_updated_cb (GtkWidget *textview,
+                          gpointer user_data)
+{
+       GtkStyleContext *context;
+       GtkStateFlags state;
+       GtkTextBuffer *buffer;
+       GtkTextTagTable *tag_table;
+       GtkTextTag *tag;
+       GdkRGBA rgba;
+
+       g_return_if_fail (GTK_IS_WIDGET (textview));
+
+       buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (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)
+               return;
+
+       context = gtk_widget_get_style_context (textview);
+
+       rgba.red = 0;
+       rgba.green = 0;
+       rgba.blue = 1;
+       rgba.alpha = 1;
+
+       state = gtk_style_context_get_state (context);
+       state = state & (~(GTK_STATE_FLAG_VISITED | GTK_STATE_FLAG_LINK));
+       state = state | GTK_STATE_FLAG_LINK;
+
+       gtk_style_context_save (context);
+       /* Remove the 'view' style, because it can "confuse" some themes */
+       gtk_style_context_remove_class (context, GTK_STYLE_CLASS_VIEW);
+       gtk_style_context_set_state (context, state);
+       gtk_style_context_get_color (context, state, &rgba);
+       gtk_style_context_restore (context);
+
+       g_object_set (G_OBJECT (tag), "foreground-rgba", &rgba, NULL);
+}
+
+static gboolean
+textview_query_tooltip (GtkTextView *text_view,
+                        gint x,
+                        gint y,
+                        gboolean keyboard_mode,
+                        GtkTooltip *tooltip,
+                        gpointer user_data)
+{
+       GtkTextBuffer *buffer;
+       guint32 state;
+       gboolean res = FALSE;
+
+       if (keyboard_mode)
+               return FALSE;
+
+       buffer = gtk_text_view_get_buffer (text_view);
+       g_return_val_if_fail (buffer != NULL, FALSE);
+
+       state = get_state (buffer);
+
+       if ((state & E_BUFFER_TAGGER_STATE_IS_HOVERING_TOOLTIP) != 0) {
+               gchar *url;
+               GtkTextIter iter;
+
+               gtk_text_view_window_to_buffer_coords (
+                       text_view,
+                       GTK_TEXT_WINDOW_WIDGET,
+                       x, y, &x, &y);
+               gtk_text_view_get_iter_at_location (text_view, &iter, x, y);
+
+               url = get_url_at_iter (buffer, &iter);
+               res = url && *url;
+
+               if (res) {
+                       gchar *str;
+
+                       /* To Translators: The text is concatenated to a form: "Ctrl-click to open a link 
http://www.example.com"; */
+                       str = g_strconcat (_("Ctrl-click to open a link"), " ", url, NULL);
+                       gtk_tooltip_set_text (tooltip, str);
+                       g_free (str);
+               }
+
+               g_free (url);
+       }
+
+       return res;
+}
+
+/* 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_KEY_Return:
+       case GDK_KEY_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);
+               }
+
+               get_pointer_position (textview, &x, &y);
+               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 *event)
+{
+       GtkTextIter start, end, iter;
+       GtkTextBuffer *buffer;
+       gint x, y;
+       GdkModifierType mt = 0;
+       guint event_button = 0;
+       gdouble event_x_win = 0;
+       gdouble event_y_win = 0;
+
+       g_return_val_if_fail (GTK_IS_TEXT_VIEW (textview), FALSE);
+
+       if (event->type == GDK_KEY_PRESS || event->type == GDK_KEY_RELEASE) {
+               guint event_keyval = 0;
+
+               gdk_event_get_keyval (event, &event_keyval);
+
+               switch (event_keyval) {
+                       case GDK_KEY_Control_L:
+                       case GDK_KEY_Control_R:
+                               update_ctrl_state (
+                                       textview,
+                                       event->type == GDK_KEY_PRESS);
+                               break;
+               }
+
+               return FALSE;
+       }
+
+       if (!gdk_event_get_state (event, &mt)) {
+               GdkWindow *window;
+               GdkDisplay *display;
+               GdkDeviceManager *device_manager;
+               GdkDevice *device;
+
+               window = gtk_widget_get_parent_window (GTK_WIDGET (textview));
+               display = gdk_window_get_display (window);
+               device_manager = gdk_display_get_device_manager (display);
+               device = gdk_device_manager_get_client_pointer (device_manager);
+
+               gdk_window_get_device_position (window, device, NULL, NULL, &mt);
+       }
+
+       update_ctrl_state (textview, (mt & GDK_CONTROL_MASK) != 0);
+
+       if (event->type != GDK_BUTTON_RELEASE)
+               return FALSE;
+
+       gdk_event_get_button (event, &event_button);
+       gdk_event_get_coords (event, &event_x_win, &event_y_win);
+
+       if (event_button != 1 || (mt & 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_win, event_y_win, &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);
+
+       get_pointer_position (textview, &wx, &wy);
+
+       gtk_text_view_window_to_buffer_coords (
+               textview,
+               GTK_TEXT_WINDOW_WIDGET,
+               wx, wy, &bx, &by);
+
+       update_mouse_cursor (textview, bx, by);
+
+       return FALSE;
+}
+
+static void
+textview_open_uri_cb (GtkWidget *widget,
+                     gpointer user_data)
+{
+       const gchar *uri = user_data;
+
+       g_return_if_fail (uri != NULL);
+
+       e_show_uri (NULL, uri);
+}
+
+static void
+textview_copy_uri_cb (GtkWidget *widget,
+                     gpointer user_data)
+{
+       GtkClipboard *clipboard;
+       const gchar *uri = user_data;
+
+       g_return_if_fail (uri != NULL);
+
+       clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
+       gtk_clipboard_set_text (clipboard, uri, -1);
+       gtk_clipboard_store (clipboard);
+
+       clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+       gtk_clipboard_set_text (clipboard, uri, -1);
+       gtk_clipboard_store (clipboard);
+}
+
+static void
+textview_populate_popup_cb (GtkTextView *textview,
+                           GtkWidget *widget,
+                           gpointer user_data)
+{
+       GtkTextIter iter;
+       GtkTextBuffer *buffer;
+       GdkDisplay *display;
+       gboolean iter_set = FALSE;
+       gchar *uri;
+
+       if (!GTK_IS_MENU (widget))
+               return;
+
+       buffer = gtk_text_view_get_buffer (textview);
+
+       display = gtk_widget_get_display (GTK_WIDGET (textview));
+
+       if (display && gtk_widget_get_window (GTK_WIDGET (textview))) {
+               GdkDeviceManager *device_manager;
+               GdkDevice *pointer;
+               gint px = 0, py = 0, xx = 0, yy = 0;
+
+               device_manager = gdk_display_get_device_manager (display);
+               pointer = gdk_device_manager_get_client_pointer (device_manager);
+
+               gdk_device_get_position (pointer, NULL, &px, &py);
+               gdk_window_get_origin (gtk_widget_get_window (GTK_WIDGET (textview)), &xx, &yy);
+
+               px -= xx;
+               py -= yy;
+
+               gtk_text_view_window_to_buffer_coords (
+                       textview,
+                       GTK_TEXT_WINDOW_WIDGET,
+                       px, py, &xx, &yy);
+
+               iter_set = gtk_text_view_get_iter_at_location (textview, &iter, xx, yy);
+       }
+
+       if (!iter_set) {
+               GtkTextMark *mark;
+
+               mark = gtk_text_buffer_get_selection_bound (buffer);
+
+               if (!mark)
+                       return;
+
+               gtk_text_buffer_get_iter_at_mark (buffer, &iter, mark);
+       }
+
+       uri = get_url_at_iter (buffer, &iter);
+
+       if (uri && *uri) {
+               GtkMenuShell *menu = GTK_MENU_SHELL (widget);
+               GtkWidget *item;
+
+               item = gtk_separator_menu_item_new ();
+               gtk_widget_show (item);
+               gtk_menu_shell_prepend (menu, item);
+
+               item = gtk_menu_item_new_with_mnemonic (_("Copy _Link Location"));
+               gtk_widget_show (item);
+               gtk_menu_shell_prepend (menu, item);
+
+               g_signal_connect_data (item, "activate",
+                       G_CALLBACK (textview_copy_uri_cb), g_strdup (uri), (GClosureNotify) g_free, 0);
+
+               item = gtk_menu_item_new_with_mnemonic (_("O_pen Link in Browser"));
+               gtk_widget_show (item);
+               gtk_menu_shell_prepend (menu, item);
+
+               g_signal_connect_data (item, "activate",
+                       G_CALLBACK (textview_open_uri_cb), uri, (GClosureNotify) g_free, 0);
+       } else {
+               g_free (uri);
+       }
+}
+
+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);
+
+       gtk_widget_set_has_tooltip (GTK_WIDGET (textview), TRUE);
+
+       g_signal_connect (
+               textview, "style-updated",
+               G_CALLBACK (textview_style_updated_cb), NULL);
+       g_signal_connect (
+               textview, "query-tooltip",
+               G_CALLBACK (textview_query_tooltip), 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);
+       g_signal_connect (
+               textview, "populate-popup",
+               G_CALLBACK (textview_populate_popup_cb), 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);
+
+       gtk_widget_set_has_tooltip (GTK_WIDGET (textview), FALSE);
+
+       g_signal_handlers_disconnect_by_func (textview, G_CALLBACK (textview_style_updated_cb), NULL);
+       g_signal_handlers_disconnect_by_func (textview, G_CALLBACK (textview_query_tooltip), 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);
+       g_signal_handlers_disconnect_by_func (textview, G_CALLBACK (textview_populate_popup_cb), 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/src/libedataserverui/libedataserverui.pc.in b/src/libedataserverui/libedataserverui.pc.in
index f631fe1ec..bf5b8ac47 100644
--- a/src/libedataserverui/libedataserverui.pc.in
+++ b/src/libedataserverui/libedataserverui.pc.in
@@ -8,13 +8,13 @@ datadir=@SHARE_INSTALL_PREFIX@
 privlibdir=@privlibdir@
 privincludedir=@privincludedir@
 
-credentialmoduledir=@credentialmoduledir@
-uimoduledir=@uimoduledir@
+credentialmoduledir=@credentialmoduledir@@UI_VERSION@
+uimoduledir=@uimoduledir@@UI_VERSION@
 
-Name: libedataserverui
+Name: libedataserverui@UI_VERSION@
 Description: UI utility library for Evolution Data Server
 Version: @PROJECT_VERSION@
-Requires: gio-2.0 gmodule-2.0 libsecret-1 libxml-2.0 libsoup-2.4 gtk+-3.0 libedataserver-@API_VERSION@ 
libecal-@CAL_API_VERSION@
+Requires: gio-2.0 gmodule-2.0 libsecret-1 libxml-2.0 libsoup-2.4 @GTK_DEPENDENCY@ 
libedataserver-@API_VERSION@ libecal-@CAL_API_VERSION@
 Requires.private: camel-@API_VERSION@
-Libs: -L${libdir} -ledataserver-@API_VERSION@ -ledataserverui-@API_VERSION@
+Libs: -L${libdir} -ledataserver-@API_VERSION@ -ledataserverui@UI_VERSION@-@UI_API_VERSION@
 Cflags: -I${privincludedir}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]