--- 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>
--- 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;
- 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 + 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( - 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);
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;
-} TalkatuViewOpenLinkData;
@@ -83,39 +84,149 @@
G_DEFINE_TYPE_WITH_PRIVATE(TalkatuView, talkatu_view, GTK_TYPE_TEXT_VIEW)
/******************************************************************************
+ *****************************************************************************/ +talkatu_view_url_from_iter(TalkatuView *view, GtkTextIter *iter) { + tag = gtk_text_iter_get_tags(iter); + for(; tag != NULL; tag = g_slist_delete_link(tag, tag)) { + if(tag->data == NULL) { + url = g_object_get_data(G_OBJECT(tag->data), "talkatu-anchor-url"); +/****************************************************************************** *****************************************************************************/
-talkatu_view_insert_anchor_cb(GtkTextBuffer *buffer,
- GtkTextChildAnchor *anchor,
- GtkTextView *view = GTK_TEXT_VIEW(user_data);
+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); +talkatu_view_copy_url_cb(GtkMenuItem *item, gpointer data) { + GtkClipboard *clipboard = gtk_widget_get_clipboard( + 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");
- GtkWidget *link = NULL;
- gpointer label = g_object_get_data(G_OBJECT(anchor), "talkatu-url-label");
+talkatu_view_anchor_tag_event_cb(GtkTextTag *tag, + GdkEventType event_type = gdk_event_get_event_type(event); + if(event_type == GDK_BUTTON_PRESS) { + GdkEventButton *event_button = (GdkEventButton *)event;
- link = gtk_link_button_new_with_label((gchar *)data, (gchar *)label);
- 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) { + } else if(gdk_event_triggers_context_menu(event)) { + GdkEventButton *event_button = (GdkEventButton *)event; + TalkatuView *view = TALKATU_VIEW(object); + url = talkatu_view_url_from_iter(view, iter); + /* if we didn't find a url, bail */ - 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)) { + g_signal_emit(view, signals[SIG_OPEN_URL], 0, url); + } else if(gdk_event_triggers_context_menu(event)) { + GtkWidget *menu = gtk_menu_new(); + GtkWidget *item = NULL;
+ 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_set_data_full( + G_CALLBACK(talkatu_view_open_url_cb), + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + item = gtk_menu_item_new_with_label(_("Copy Link")); + G_CALLBACK(talkatu_view_copy_url_cb), + 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 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); 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_CALLBACK(talkatu_view_anchor_tag_event_cb), - g_signal_connect_after(
- gtk_text_view_get_buffer(GTK_TEXT_VIEW(view)),
- G_CALLBACK(talkatu_view_insert_anchor_cb),
+ gspell_text_view_basic_setup(priv->gspell_view); /******************************************************************************
@@ -191,30 +306,6 @@
-talkatu_view_open_url_cb(GtkMenuItem *item, gpointer data) {
- TalkatuViewOpenLinkData *open_link_data = (TalkatuViewOpenLinkData *)data;
- g_slice_free(TalkatuViewOpenLinkData, data);
-talkatu_view_copy_url_cb(GtkMenuItem *item, gpointer data) {
- GtkClipboard *clipboard = gtk_widget_get_clipboard(
- GDK_SELECTION_CLIPBOARD
- gtk_clipboard_set_text(clipboard, (gchar *)data, -1);
talkatu_view_populate_popup_cb(GtkTextView *view, GtkWidget *popup) {
TalkatuViewPrivate *priv = NULL;
GtkTextBuffer *buffer = NULL;
@@ -251,10 +342,90 @@
+talkatu_view_query_tooltip(GtkWidget *widget, + 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( + /* now find the iter for what we're at */ + gtk_text_view_get_iter_at_location( + /* look for a url, if we have one, add it to tooltip */ + url = talkatu_view_url_from_iter(TALKATU_VIEW(widget), &iter); + gtk_tooltip_set_text(tooltip, url); + return GTK_WIDGET_CLASS(talkatu_view_parent_class)->query_tooltip(widget, x, y, keyboard, tooltip); +talkatu_view_motion_notify_event(GtkWidget *widget, GdkEventMotion *event) { + TalkatuViewPrivate *priv = talkatu_view_get_instance_private(TALKATU_VIEW(widget)); + GdkCursor *cursor = NULL; + gtk_text_view_window_to_buffer_coords( + 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 GTK_WIDGET_CLASS(talkatu_view_parent_class)->motion_notify_event(widget, event); /******************************************************************************
*****************************************************************************/
+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); 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 */
@@ -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;