- ack

On 08/28/2012 05:33 PM, Jakub Filak wrote:
- implementation of widget event callbacks is taken from gtk3-demo

Signed-off-by: Jakub Filak <[email protected]>
---
  src/gtk-helpers/hyperlinks.c             | 116 +++++++++++++----
  src/gtk-helpers/internal_libreport_gtk.h |  10 ++
  src/gui-wizard-gtk/wizard.c              | 207 ++++++++++++++++++++++++++++++-
  3 files changed, 308 insertions(+), 25 deletions(-)

diff --git a/src/gtk-helpers/hyperlinks.c b/src/gtk-helpers/hyperlinks.c
index 3f8b570..044afe5 100644
--- a/src/gtk-helpers/hyperlinks.c
+++ b/src/gtk-helpers/hyperlinks.c
@@ -19,38 +19,110 @@ static char *find_end(const char* url_start)
      return url_end;
  }

-char *tag_url(const char* line, const char* prefix)
+/* Returns a list of pointers to url token begining and it's lenght
+ * This implementation parses the following line:
+ *   
https://partner-bugzilla.redhat.com/ftp://ftp.kernel.org/http://bugzilla.redhat.com/http://google.com/https://gmail.com/
+ * to the following tokens:
+ *   https://partner-bugzilla.redhat.com/
+ *   ftp://ftp.kernel.org/
+ *   http://bugzilla.redhat.com/
+ *   http://google.com/
+ *   https://gmail.com/
+ */
+GList *find_url_tokens(const char *line)
  {
      static const char *const known_url_prefixes[] = {"http://";, "https://";, "ftp://";, 
"file://", NULL};
-
-    char *result = xstrdup(line);
-
      const char *const *pfx = known_url_prefixes;
+    GList *tokens = NULL;
      while (*pfx != NULL)
      {
-        char *cur_pos = result;
-        char *url_start;
+        const char *cur_pos = line;
+        const char *url_start;
          while ((url_start = strstr(cur_pos, *pfx)) != NULL)
          {
              char *url_end = find_end(url_start);
              int len = url_end - url_start;
-            char *hyperlink = xasprintf("%s<a href=\"%.*s\">%.*s</a>",
-                            prefix,
-                            len, url_start,
-                            len, url_start
-            );
-            len = url_start - result;
-            char *old = result;
-            result = xasprintf("%.*s%s%s",
-                            len, result,
-                            hyperlink,
-                            url_end
-            );
-            cur_pos = result + len + strlen(hyperlink);
-            free(old);
-            free(hyperlink);
+
+            GList *anc = tokens;
+            for (; anc; anc = g_list_next(anc))
+            {
+                const struct url_token *const t = (struct url_token 
*)anc->data;
+                if (t->start >= url_start)
+                    break;
+            }
+
+            /* need it for overlap correction */
+            GList *prev = NULL;
+            /* initialize it after overlap correction */
+            struct url_token *tok = xmalloc(sizeof(*tok));
+            if (anc)
+            {   /* insert a new token before token following in the str*/
+                prev = g_list_previous(anc);
+
+                struct url_token *following = anc->data;
+                if (url_end > following->start)
+                    /* correct ovrelaps with following token */
+                    len -= url_end - following->start;
+
+                GList *new = g_list_prepend(anc, tok);
+                /* a new token is to become head of the list*/
+                if (anc == tokens)
+                    tokens = new;
+            }
+            else
+            {   /* append a new token to the end of list */
+                prev = g_list_last(tokens);
+                tokens = g_list_append(tokens, tok);
+            }
+
+            if (prev)
+            {   /* correct overlaps with previous token */
+                struct url_token *previous = prev->data;
+                const char *prev_end = previous->start + previous->len;
+
+                if (prev_end > url_start)
+                    previous->len -= prev_end - url_start;
+            }
+
+            tok->start = url_start;
+            tok->len = len;
+
+            /* move right behind the current prefix */
+            cur_pos = url_start + strlen(*pfx);
          }
          pfx++;
      }
-    return result;
+
+    return tokens;
+}
+
+char *tag_url(const char* line, const char* prefix)
+{
+    struct strbuf *result = strbuf_new();
+    const char *last = line;
+    GList *urls = find_url_tokens(line);
+    for (GList *u = urls; u; u = g_list_next(u))
+    {
+        const struct url_token *const t = (struct url_token *)u->data;
+
+        /* add text between hyperlinks */
+        if (last < t->start)
+            /* TODO : add strbuf_append_strn() */
+            strbuf_append_strf(result, "%.*s", t->start - last, last);
+
+        strbuf_append_strf(result, "%s<a href=\"%.*s\">%.*s</a>",
+                                   prefix,
+                                   t->len, t->start,
+                                   t->len, t->start);
+
+        last = t->start + t->len;
+    }
+
+    g_list_free_full(urls, g_free);
+
+    /* add a text following the last link */
+    if (last[0] != '\0')
+        strbuf_append_str(result, last);
+
+    return strbuf_free_nobuf(result);
  }
diff --git a/src/gtk-helpers/internal_libreport_gtk.h 
b/src/gtk-helpers/internal_libreport_gtk.h
index dd9ff87..aa28b16 100644
--- a/src/gtk-helpers/internal_libreport_gtk.h
+++ b/src/gtk-helpers/internal_libreport_gtk.h
@@ -49,6 +49,16 @@ int show_event_config_dialog(const char *event_name, 
GtkWindow *parent);

  char * tag_url(const char* line, const char* prefix);

+#define url_token libreport_url_token
+struct url_token
+{
+    const char *start;
+    int len;
+};
+
+#define find_url_tokens libreport_find_url_tokens
+GList *find_url_tokens(const char *line);
+
  #ifdef __cplusplus
  }
  #endif
diff --git a/src/gui-wizard-gtk/wizard.c b/src/gui-wizard-gtk/wizard.c
index a669736..ea2394f 100644
--- a/src/gui-wizard-gtk/wizard.c
+++ b/src/gui-wizard-gtk/wizard.c
@@ -475,7 +475,7 @@ static void append_to_textview(GtkTextView *tv, const char 
*str)

      /* Ensure we insert text at the end */
      GtkTextIter text_iter;
-    gtk_text_buffer_get_iter_at_offset(tb, &text_iter, -1);
+    gtk_text_buffer_get_end_iter(tb, &text_iter);
      gtk_text_buffer_place_cursor(tb, &text_iter);

      /* Deal with possible broken Unicode */
@@ -488,14 +488,207 @@ static void append_to_textview(GtkTextView *tv, const 
char *str)
          gtk_text_buffer_insert_at_cursor(tb, buf, len);
          str = end + 1;
      }
-    gtk_text_buffer_insert_at_cursor(tb, str, strlen(str));
+
+    gtk_text_buffer_get_end_iter(tb, &text_iter);
+
+    const char *last = str;
+    GList *urls = find_url_tokens(str);
+    for (GList *u = urls; u; u = g_list_next(u))
+    {
+        const struct url_token *const t = (struct url_token *)u->data;
+        if (last < t->start)
+            gtk_text_buffer_insert(tb, &text_iter, last, t->start - last);
+
+        GtkTextTag *tag;
+        tag = gtk_text_buffer_create_tag (tb, NULL, "foreground", "blue",
+                                          "underline", PANGO_UNDERLINE_SINGLE, 
NULL);
+        char *url = xstrndup(t->start, t->len);
+        g_object_set_data (G_OBJECT (tag), "url", url);
+
+        gtk_text_buffer_insert_with_tags(tb, &text_iter, url, -1, tag, NULL);
+
+        last = t->start + t->len;
+    }
+
+    g_list_free_full(urls, g_free);
+
+    if (last[0] != '\0')
+        gtk_text_buffer_insert(tb, &text_iter, last, strlen(last));

      /* Scroll so that the end of the log is visible */
-    gtk_text_buffer_get_iter_at_offset(tb, &text_iter, -1);
      gtk_text_view_scroll_to_iter(tv, &text_iter,
                  /*within_margin:*/ 0.0, /*use_align:*/ FALSE, /*xalign:*/ 0, 
/*yalign:*/ 0);
  }

+/* Looks at all tags covering the position of iter in the text view,
+ * and if one of them is a link, follow it by showing the page identified
+ * by the data attached to it.
+ */
+static void open_browse_if_link(GtkWidget *text_view, GtkTextIter *iter)
+{
+    GSList *tags = NULL, *tagp = NULL;
+
+    tags = gtk_text_iter_get_tags (iter);
+    for (tagp = tags;  tagp != NULL;  tagp = tagp->next)
+    {
+        GtkTextTag *tag = tagp->data;
+        const char *url = g_object_get_data (G_OBJECT (tag), "url");
+
+        if (url != 0)
+        {
+            if(fork() == 0)
+            {
+                execlp("gnome-open", "gnome-open", url, NULL);
+                execlp("xdg-open", "xdg-open", url, NULL);
+                exit(1);
+            }
+            break;
+        }
+    }
+
+    if (tags)
+        g_slist_free (tags);
+}
+
+/* Links can be activated by pressing Enter.
+ */
+static gboolean key_press_event(GtkWidget *text_view, GdkEventKey *event)
+{
+    GtkTextIter iter;
+    GtkTextBuffer *buffer;
+
+    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));
+            open_browse_if_link(text_view, &iter);
+            break;
+
+        default:
+            break;
+    }
+
+    return FALSE;
+}
+
+/* Links can also be activated by clicking.
+ */
+static gboolean event_after(GtkWidget *text_view, GdkEvent *ev)
+{
+    GtkTextIter start, end, iter;
+    GtkTextBuffer *buffer;
+    GdkEventButton *event;
+    gint x, y;
+
+    if (ev->type != GDK_BUTTON_RELEASE)
+        return FALSE;
+
+    event = (GdkEventButton *)ev;
+
+    if (event->button != GDK_BUTTON_PRIMARY)
+        return FALSE;
+
+    buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view));
+
+    /* 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(GTK_TEXT_VIEW (text_view),
+                                          GTK_TEXT_WINDOW_WIDGET,
+                                          event->x, event->y, &x, &y);
+
+    gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW (text_view), &iter, x, y);
+
+    open_browse_if_link(text_view, &iter);
+
+    return FALSE;
+}
+
+static gboolean hovering_over_link = FALSE;
+static GdkCursor *hand_cursor = NULL;
+static GdkCursor *regular_cursor = NULL;
+
+/* Looks at all tags covering the position (x, y) in the text view,
+ * and if one of them is a link, change the cursor to the "hands" cursor
+ * typically used by web browsers.
+ */
+static void set_cursor_if_appropriate(GtkTextView *text_view,
+                                      gint x,
+                                      gint y)
+{
+    GSList *tags = NULL, *tagp = NULL;
+    GtkTextIter iter;
+    gboolean hovering = FALSE;
+
+    gtk_text_view_get_iter_at_location(text_view, &iter, x, y);
+
+    tags = gtk_text_iter_get_tags(&iter);
+    for (tagp = tags; tagp != NULL; tagp = tagp->next)
+    {
+        GtkTextTag *tag = tagp->data;
+        gpointer url = g_object_get_data(G_OBJECT (tag), "url");
+
+        if (url != 0)
+        {
+            hovering = TRUE;
+            break;
+        }
+    }
+
+    if (hovering != hovering_over_link)
+    {
+        hovering_over_link = hovering;
+
+        if (hovering_over_link)
+            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);
+    }
+
+    if (tags)
+        g_slist_free (tags);
+}
+
+
+/* Update the cursor image if the pointer moved.
+ */
+static gboolean motion_notify_event(GtkWidget *text_view, GdkEventMotion 
*event)
+{
+    gint x, y;
+
+    gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text_view),
+                                          GTK_TEXT_WINDOW_WIDGET,
+                                          event->x, event->y, &x, &y);
+
+    set_cursor_if_appropriate(GTK_TEXT_VIEW(text_view), x, y);
+    return FALSE;
+}
+
+/* Also update the cursor image if the window becomes visible
+ * (e.g. when a window covering it got iconified).
+ */
+static gboolean visibility_notify_event(GtkWidget *text_view, 
GdkEventVisibility *event)
+{
+    gint wx, wy, bx, by;
+
+    GdkWindow *win = gtk_text_view_get_window(GTK_TEXT_VIEW(text_view), 
GTK_TEXT_WINDOW_TEXT);
+    GdkDeviceManager *device_manager = 
gdk_display_get_device_manager(gdk_window_get_display (win));
+    GdkDevice *pointer = gdk_device_manager_get_client_pointer(device_manager);
+    gdk_window_get_device_position(gtk_widget_get_window(text_view), pointer, &wx, 
&wy, NULL);
+
+    gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text_view),
+                                          GTK_TEXT_WINDOW_WIDGET,
+                                          wx, wy, &bx, &by);
+
+    set_cursor_if_appropriate(GTK_TEXT_VIEW (text_view), bx, by);
+
+    return FALSE;
+}

  /* event_gui_data_t */

@@ -2680,6 +2873,14 @@ void create_assistant(void)

      g_signal_connect(g_search_entry_bt, "changed", 
G_CALLBACK(search_timeout), NULL);

+    g_signal_connect (g_tv_event_log, "key-press-event", G_CALLBACK 
(key_press_event), NULL);
+    g_signal_connect (g_tv_event_log, "event-after", G_CALLBACK (event_after), 
NULL);
+    g_signal_connect (g_tv_event_log, "motion-notify-event", G_CALLBACK 
(motion_notify_event), NULL);
+    g_signal_connect (g_tv_event_log, "visibility-notify-event", G_CALLBACK 
(visibility_notify_event), NULL);
+
+    hand_cursor = gdk_cursor_new (GDK_HAND2);
+    regular_cursor = gdk_cursor_new (GDK_XTERM);
+
      /* switch to right starting page */
      on_page_prepare(g_assistant, gtk_notebook_get_nth_page(g_assistant, 0), 
NULL);
  }


Reply via email to