- implementation of widget event callbacks is taken from gtk3-demo
Signed-off-by: Jakub Filak <jfilak(a)redhat.com>
---
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....
+ * 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);
}
--
1.7.11.4