qulogic/talkatu

Merged in default (pull request #23)

2019-07-15, Gary Kramlich
e7dc9f018169
Merged in default (pull request #23)

This should fix everything up about links...

Approved-by: Elliott Sales de Andrade
--- a/talkatu/data/linkdialog.ui Mon Jul 15 02:25:33 2019 -0500
+++ b/talkatu/data/linkdialog.ui Mon Jul 15 08:18:20 2019 +0000
@@ -39,6 +39,8 @@
<property name="name">insert</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
<property name="receives_default">True</property>
</object>
<packing>
--- a/talkatu/talkatuactiongroup.c Mon Jul 15 02:25:33 2019 -0500
+++ b/talkatu/talkatuactiongroup.c Mon Jul 15 08:18:20 2019 +0000
@@ -331,27 +331,47 @@
GtkWidget *link_dialog = talkatu_link_dialog_new();
if(gtk_dialog_run(GTK_DIALOG(link_dialog)) == GTK_RESPONSE_ACCEPT) {
- GtkTextChildAnchor *anchor = NULL;
+ GtkTextTagTable *table = gtk_text_buffer_get_tag_table(buffer);
+ GtkTextTag *anchor, *anchor_data;
GtkTextMark *insert_mark = NULL;
GtkTextIter insert;
- gchar *url = NULL, *label = NULL;
+ gchar *label = NULL, *url = NULL;
+ /* if any text is selected, delete it */
if(gtk_text_buffer_get_has_selection(buffer)) {
gtk_text_buffer_delete_selection(buffer, TRUE, TRUE);
}
+ /* grab our inputs from the dialog */
+ url = talkatu_link_dialog_get_url(TALKATU_LINK_DIALOG(link_dialog));
+ label = talkatu_link_dialog_get_display_text(TALKATU_LINK_DIALOG(link_dialog));
+
+ /* find the anchor tag from the table */
+ anchor = gtk_text_tag_table_lookup(table, TALKATU_TAG_ANCHOR);
+ g_return_if_fail(GTK_IS_TEXT_TAG(anchor));
+
+ /* now create an anonymous tag that will hold the url.
+ * This should probably be dedupped at some point, but pidgin 2 didn't
+ * bother with that.
+ */
+ anchor_data = gtk_text_tag_new(NULL);
+ g_object_set_data_full(G_OBJECT(anchor_data), "talkatu-anchor-url", url, g_free);
+ gtk_text_tag_table_add(table, anchor_data);
+
insert_mark = gtk_text_buffer_get_insert(buffer);
gtk_text_buffer_get_iter_at_mark(buffer, &insert, insert_mark);
- anchor = gtk_text_buffer_create_child_anchor(buffer, &insert);
-
- url = talkatu_link_dialog_get_url(TALKATU_LINK_DIALOG(link_dialog));
- g_object_set_data_full(G_OBJECT(anchor), "talkatu-url", url, g_free);
+ gtk_text_buffer_insert_with_tags(
+ buffer,
+ &insert,
+ label,
+ -1,
+ anchor,
+ anchor_data,
+ NULL
+ );
- label = talkatu_link_dialog_get_display_text(TALKATU_LINK_DIALOG(link_dialog));
- g_object_set_data_full(G_OBJECT(anchor), "talkatu-url-label", label, g_free);
-
- gtk_text_buffer_insert_child_anchor(buffer, &insert, anchor);
+ g_free(label);
}
gtk_widget_destroy(link_dialog);
--- a/talkatu/talkatuview.c Mon Jul 15 02:25:33 2019 -0500
+++ b/talkatu/talkatuview.c Mon Jul 15 08:18:20 2019 +0000
@@ -57,13 +57,14 @@
* is updated via cursor-moved and button-press callbacks.
*/
GtkTextMark *context_mark;
+
+ /* we cache the cursor that's displayed while hovering over a link as well
+ * the tag for links/anchors to avoid extra lookups.
+ */
+ GdkCursor *cursor_hand;
+ GtkTextTag *tag_anchor;
} TalkatuViewPrivate;
-typedef struct {
- TalkatuView *view;
- const gchar *url;
-} TalkatuViewOpenLinkData;
-
enum {
PROP_0 = 0,
PROP_SEND_BINDING,
@@ -83,39 +84,149 @@
G_DEFINE_TYPE_WITH_PRIVATE(TalkatuView, talkatu_view, GTK_TYPE_TEXT_VIEW)
/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static gchar *
+talkatu_view_url_from_iter(TalkatuView *view, GtkTextIter *iter) {
+ GSList *tag = NULL;
+ gchar *url = NULL;
+
+ tag = gtk_text_iter_get_tags(iter);
+ for(; tag != NULL; tag = g_slist_delete_link(tag, tag)) {
+ if(tag->data == NULL) {
+ continue;
+ }
+
+ url = g_object_get_data(G_OBJECT(tag->data), "talkatu-anchor-url");
+ if(url != NULL) {
+ break;
+ }
+ }
+ g_slist_free(tag);
+
+ return url;
+}
+
+/******************************************************************************
* Callbacks
*****************************************************************************/
static void
-talkatu_view_insert_anchor_cb(GtkTextBuffer *buffer,
- GtkTextIter *iter,
- GtkTextChildAnchor *anchor,
- gpointer user_data)
-{
- GtkTextView *view = GTK_TEXT_VIEW(user_data);
- gpointer data = NULL;
+talkatu_view_open_url_cb(GtkMenuItem *item, gpointer data) {
+ TalkatuView *view = g_object_get_data(G_OBJECT(item), "view");
+ gchar *url = g_object_get_data(G_OBJECT(item), "url");
+
+ g_signal_emit(view, signals[SIG_OPEN_URL], 0, url);
+}
+
+static void
+talkatu_view_copy_url_cb(GtkMenuItem *item, gpointer data) {
+ GtkClipboard *clipboard = gtk_widget_get_clipboard(
+ GTK_WIDGET(item),
+ GDK_SELECTION_CLIPBOARD
+ );
+
+ gtk_clipboard_set_text(clipboard, (gchar *)data, -1);
+}
- /* first check if talkatu-url is added */
- data = g_object_get_data(G_OBJECT(anchor), "talkatu-url");
- if(data != NULL) {
- GtkWidget *link = NULL;
- gpointer label = g_object_get_data(G_OBJECT(anchor), "talkatu-url-label");
+gboolean
+talkatu_view_anchor_tag_event_cb(GtkTextTag *tag,
+ GObject *object,
+ GdkEvent *event,
+ GtkTextIter *iter,
+ gpointer data)
+{
+ GdkEventType event_type = gdk_event_get_event_type(event);
+
+ if(event_type == GDK_BUTTON_PRESS) {
+ GdkEventButton *event_button = (GdkEventButton *)event;
- if(label != NULL) {
- link = gtk_link_button_new_with_label((gchar *)data, (gchar *)label);
- } else {
- link = gtk_link_button_new((gchar *)data);
+ /* the user is right clicking on a link so stop the default handler */
+ if(event_button->button == GDK_BUTTON_SECONDARY) {
+ return TRUE;
+ }
+ } else if(gdk_event_triggers_context_menu(event)) {
+ GdkEventButton *event_button = (GdkEventButton *)event;
+ TalkatuView *view = TALKATU_VIEW(object);
+ gchar *url;
+
+ url = talkatu_view_url_from_iter(view, iter);
+
+ /* if we didn't find a url, bail */
+ if(url == NULL) {
+ return FALSE;
}
- gtk_text_view_add_child_at_anchor(view, link, anchor);
+ if(event_button->button == GDK_BUTTON_PRIMARY) {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
+
+ /* old behavior from pidgin2, if the users has something selected
+ * we don't open links. Other clients don't do this.. but it seems
+ * to be a work around for someone pressing a button over text,
+ * then moving the cursor to the link and then releasing. Without
+ * the selection check that'll cause that to open the link if the
+ * button is released in the middle of the link.
+ */
+ if(gtk_text_buffer_get_has_selection(buffer)) {
+ return FALSE;
+ }
+
+ g_signal_emit(view, signals[SIG_OPEN_URL], 0, url);
+
+ return TRUE;
+ } else if(gdk_event_triggers_context_menu(event)) {
+ GtkWidget *menu = gtk_menu_new();
+ GtkWidget *item = NULL;
- gtk_widget_show(link);
+ item = gtk_menu_item_new_with_label(_("Open Link"));
+ /* to make it easier to deal with the life cycle, we just add data
+ * to the menu item itself with destroy notifies.
+ */
+ g_object_set_data_full(
+ G_OBJECT(item),
+ "view",
+ g_object_ref(view),
+ g_object_unref
+ );
+ g_object_set_data_full(
+ G_OBJECT(item),
+ "url",
+ g_strdup(url),
+ g_free
+ );
+ g_signal_connect(
+ G_OBJECT(item),
+ "activate",
+ G_CALLBACK(talkatu_view_open_url_cb),
+ NULL
+ );
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+
+ item = gtk_menu_item_new_with_label(_("Copy Link"));
+ g_signal_connect(
+ G_OBJECT(item),
+ "activate",
+ G_CALLBACK(talkatu_view_copy_url_cb),
+ url
+ );
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+
+ gtk_widget_show_all(menu);
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 0, event_button->time);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+
+ return TRUE;
+ }
}
+
+ return FALSE;
}
static void
talkatu_view_buffer_set_cb(GObject *view, GParamSpec *pspec, gpointer data) {
TalkatuViewPrivate *priv = talkatu_view_get_instance_private(TALKATU_VIEW(view));
GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
+ GtkTextTagTable *table = gtk_text_buffer_get_tag_table(buffer);
GtkTextIter start;
if(TALKATU_IS_BUFFER(buffer)) {
@@ -125,14 +236,18 @@
gtk_text_buffer_get_start_iter(buffer, &start);
priv->context_mark = gtk_text_buffer_create_mark(buffer, NULL, &start, TRUE);
- gspell_text_view_basic_setup(priv->gspell_view);
+ /* check for the anchor tag, if we have it, add a signal handler to it */
+ priv->tag_anchor = gtk_text_tag_table_lookup(table, TALKATU_TAG_ANCHOR);
+ if(priv->tag_anchor != NULL) {
+ g_signal_connect(
+ priv->tag_anchor,
+ "event",
+ G_CALLBACK(talkatu_view_anchor_tag_event_cb),
+ NULL
+ );
+ }
- g_signal_connect_after(
- gtk_text_view_get_buffer(GTK_TEXT_VIEW(view)),
- "insert-child-anchor",
- G_CALLBACK(talkatu_view_insert_anchor_cb),
- view
- );
+ gspell_text_view_basic_setup(priv->gspell_view);
}
/******************************************************************************
@@ -191,30 +306,6 @@
}
static void
-talkatu_view_open_url_cb(GtkMenuItem *item, gpointer data) {
- TalkatuViewOpenLinkData *open_link_data = (TalkatuViewOpenLinkData *)data;
-
- g_signal_emit(
- open_link_data->view,
- signals[SIG_OPEN_URL],
- 0,
- open_link_data->url
- );
-
- g_slice_free(TalkatuViewOpenLinkData, data);
-}
-
-static void
-talkatu_view_copy_url_cb(GtkMenuItem *item, gpointer data) {
- GtkClipboard *clipboard = gtk_widget_get_clipboard(
- GTK_WIDGET(item),
- GDK_SELECTION_CLIPBOARD
- );
-
- gtk_clipboard_set_text(clipboard, (gchar *)data, -1);
-}
-
-static void
talkatu_view_populate_popup_cb(GtkTextView *view, GtkWidget *popup) {
TalkatuViewPrivate *priv = NULL;
GtkTextBuffer *buffer = NULL;
@@ -251,10 +342,90 @@
}
}
+static gboolean
+talkatu_view_query_tooltip(GtkWidget *widget,
+ gint x,
+ gint y,
+ gboolean keyboard,
+ GtkTooltip *tooltip)
+{
+ GtkTextIter iter;
+ gchar *url = NULL;
+ gint adj_x, adj_y;
+
+ if(keyboard) {
+ return GTK_WIDGET_CLASS(talkatu_view_parent_class)->query_tooltip(widget, x, y, keyboard, tooltip);
+ }
+
+ /* convert the window coordinates to match whats visible */
+ gtk_text_view_window_to_buffer_coords(
+ GTK_TEXT_VIEW(widget),
+ GTK_TEXT_WINDOW_TEXT,
+ x, y,
+ &adj_x, &adj_y
+ );
+
+ /* now find the iter for what we're at */
+ gtk_text_view_get_iter_at_location(
+ GTK_TEXT_VIEW(widget),
+ &iter,
+ adj_x,
+ adj_y
+ );
+
+ /* look for a url, if we have one, add it to tooltip */
+ url = talkatu_view_url_from_iter(TALKATU_VIEW(widget), &iter);
+ if(url != NULL) {
+ gtk_tooltip_set_text(tooltip, url);
+
+ return TRUE;
+ }
+
+ return GTK_WIDGET_CLASS(talkatu_view_parent_class)->query_tooltip(widget, x, y, keyboard, tooltip);
+}
+
+static gboolean
+talkatu_view_motion_notify_event(GtkWidget *widget, GdkEventMotion *event) {
+ TalkatuViewPrivate *priv = talkatu_view_get_instance_private(TALKATU_VIEW(widget));
+ GtkTextIter iter;
+ GdkCursor *cursor = NULL;
+ gint x, y;
+
+ gtk_text_view_window_to_buffer_coords(
+ GTK_TEXT_VIEW(widget),
+ GTK_TEXT_WINDOW_TEXT,
+ event->x, event->y,
+ &x, &y
+ );
+
+ gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget), &iter, x, y);
+
+ if(gtk_text_iter_has_tag(&iter, priv->tag_anchor)) {
+ cursor = priv->cursor_hand;
+ }
+
+ if(cursor != gdk_window_get_cursor(event->window)) {
+ gdk_window_set_cursor(event->window, cursor);
+
+ return TRUE;
+ }
+
+ return GTK_WIDGET_CLASS(talkatu_view_parent_class)->motion_notify_event(widget, event);
+}
+
/******************************************************************************
* GObject Stuff
*****************************************************************************/
static void
+talkatu_view_finalize(GObject *obj) {
+ TalkatuViewPrivate *priv = talkatu_view_get_instance_private(TALKATU_VIEW(obj));
+
+ g_clear_object(&priv->cursor_hand);
+
+ G_OBJECT_CLASS(talkatu_view_parent_class)->finalize(obj);
+}
+
+static void
talkatu_view_get_property(GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec) {
TalkatuView *view = TALKATU_VIEW(obj);
@@ -286,6 +457,13 @@
talkatu_view_init(TalkatuView *view) {
TalkatuViewPrivate *priv = talkatu_view_get_instance_private(view);
+ priv->cursor_hand = gdk_cursor_new_from_name(gdk_display_get_default(), "pointer");
+
+ /* tell the widget class that we support tooltips. This is used to show
+ * link targets, and probably other stuff at some point.
+ */
+ gtk_widget_set_has_tooltip(GTK_WIDGET(view), TRUE);
+
/* set our event mask for the signals we care about */
gtk_widget_set_events(
GTK_WIDGET(view),
@@ -324,8 +502,11 @@
obj_class->get_property = talkatu_view_get_property;
obj_class->set_property = talkatu_view_set_property;
+ obj_class->finalize = talkatu_view_finalize;
+ widget_class->motion_notify_event = talkatu_view_motion_notify_event;
widget_class->popup_menu = talkatu_view_popup_menu;
+ widget_class->query_tooltip = talkatu_view_query_tooltip;
/* add our default signal handlers */
klass->format_activate = talkatu_view_format_activate;