--- a/po/POTFILES Mon Jan 31 22:49:01 2022 -0600
+++ b/po/POTFILES Mon Jan 31 22:49:29 2022 -0600
@@ -7,6 +7,7 @@
talkatu/data/historyrow.ui
talkatu/data/linkdialog.ui
talkatu/talkatuactiongroup.c
talkatu/talkatuattachment.c
talkatu/talkatuattachmentdialog.c
--- a/talkatu/data/talkatu.gresource.xml Mon Jan 31 22:49:01 2022 -0600
+++ b/talkatu/data/talkatu.gresource.xml Mon Jan 31 22:49:29 2022 -0600
@@ -9,5 +9,6 @@
<file compressed="true">linkdialog.ui</file>
<file compressed="true">toolbar.ui</file>
<file compressed="true">typinglabel.ui</file>
+ <file compressed="true">view.ui</file> --- a/talkatu/talkatuview.c Mon Jan 31 22:49:01 2022 -0600
+++ b/talkatu/talkatuview.c Mon Jan 31 22:49:29 2022 -0600
@@ -32,8 +32,6 @@
- * @format_activate: The class handler for the #TalkatuView::format_activate
* @open_url: The class handler for the #TalkatuView::open_url signal.
* The backing class to #TalkatuView instances.
@@ -52,7 +50,9 @@
* the tag for links/anchors to avoid extra lookups.
- GtkTextTag *tag_anchor;
@@ -67,7 +67,7 @@
/******************************************************************************
*****************************************************************************/
talkatu_view_url_from_iter(TalkatuView *view, GtkTextIter *iter) {
@@ -89,158 +89,117 @@
/******************************************************************************
*****************************************************************************/
-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");
+talkatu_view_link_open_cb(GtkWidget *widget, const gchar *action_name, + TalkatuView *view = TALKATU_VIEW(widget); + TalkatuViewPrivate *priv = talkatu_view_get_instance_private(view); - g_signal_emit(view, signals[SIG_OPEN_URL], 0, url);
+ g_signal_emit(view, signals[SIG_OPEN_URL], 0, priv->url); -talkatu_view_copy_url_cb(GtkMenuItem *item, gpointer data) {
- GtkClipboard *clipboard = gtk_widget_get_clipboard(
- GDK_SELECTION_CLIPBOARD
+talkatu_view_link_copy_cb(GtkWidget *widget, const gchar *action_name, + TalkatuView *view = TALKATU_VIEW(widget); + TalkatuViewPrivate *priv = talkatu_view_get_instance_private(view); - gtk_clipboard_set_text(clipboard, (gchar *)data, -1);
+ if(priv->url != NULL) { + GdkClipboard *clipboard = NULL; + clipboard = gtk_widget_get_clipboard(widget); + gdk_clipboard_set_text(clipboard, priv->url);
-talkatu_view_anchor_tag_event_cb(GtkTextTag *tag,
+/****************************************************************************** + *****************************************************************************/ +talkatu_view_released_cb(GtkGestureClick *gesture, guint n_press, double wx, + double wy, gpointer data) - GdkEventType event_type = gdk_event_get_event_type(event);
- if(event_type == GDK_BUTTON_PRESS) {
- GdkEventButton *event_button = (GdkEventButton *)event;
- /* 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 */
- 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);
+ TalkatuView *view = TALKATU_VIEW(data); + const gchar *url = NULL;
- } else if(gdk_event_triggers_context_menu(event)) {
- GtkWidget *menu = gtk_menu_new();
- GtkWidget *item = NULL;
+ gtk_text_view_window_to_buffer_coords( + gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(view), &iter, x, y); - 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);
+ url = talkatu_view_url_from_iter(view, &iter); + gint button = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(gesture)); - 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);
+ g_signal_emit(view, signals[SIG_OPEN_URL], 0, url); + } else if(button == 2) { + TalkatuViewPrivate *priv = talkatu_view_get_instance_private(view); - 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
+ g_clear_pointer(&priv->url, g_free); + priv->url = g_strdup(url);
+ gtk_popover_set_pointing_to(GTK_POPOVER(priv->menu), + &(const GdkRectangle){ x, y, 1, 1}); + gtk_popover_popup(GTK_POPOVER(priv->menu));
+talkatu_view_motion_cb(GtkEventControllerMotion *controller, gdouble wx, + gdouble wy, gpointer data) + TalkatuView *view = TALKATU_VIEW(data); + TalkatuViewPrivate *priv = talkatu_view_get_instance_private(view); + GdkCursor *cursor = NULL; + const gchar *url = NULL; + gtk_text_view_window_to_buffer_coords( + gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(view), &iter, x, y); + url = talkatu_view_url_from_iter(view, &iter); + cursor = priv->cursor_hand; + if(cursor != gtk_widget_get_cursor(GTK_WIDGET(view))) { + gtk_widget_set_cursor(GTK_WIDGET(view), cursor); 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)) {
priv->action_group = talkatu_buffer_get_action_group(TALKATU_BUFFER(buffer));
- /* 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),
/******************************************************************************
* Default Signal Handlers
*****************************************************************************/
-talkatu_view_format_activate(TalkatuView *view, const gchar *action_name) {
- TalkatuViewPrivate *priv = talkatu_view_get_instance_private(view);
- if(priv->action_group) {
- GAction *action = g_action_map_lookup_action(G_ACTION_MAP(priv->action_group), action_name);
- g_action_activate(action, NULL);
talkatu_view_query_tooltip(GtkWidget *widget,
@@ -249,7 +208,7 @@
+ const gchar *url = NULL; @@ -283,35 +242,6 @@
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);
/******************************************************************************
* GtkTextViewClass overrides
*****************************************************************************/
@@ -328,6 +258,7 @@
TalkatuViewPrivate *priv = talkatu_view_get_instance_private(TALKATU_VIEW(obj));
g_clear_object(&priv->cursor_hand);
+ g_clear_pointer(&priv->url, g_free); G_OBJECT_CLASS(talkatu_view_parent_class)->finalize(obj);
@@ -335,19 +266,30 @@
talkatu_view_init(TalkatuView *view) {
TalkatuViewPrivate *priv = talkatu_view_get_instance_private(view);
+ GtkEventController *controller = NULL; - priv->cursor_hand = gdk_cursor_new_from_name(gdk_display_get_default(), "pointer");
+ gtk_widget_init_template(GTK_WIDGET(view)); + priv->cursor_hand = gdk_cursor_new_from_name("pointer", NULL); /* 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 */
- GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK
+ /* Connect some signals we care about */ + controller = gtk_event_controller_motion_new(); + g_signal_connect(controller, "motion", G_CALLBACK(talkatu_view_motion_cb), + gtk_widget_add_controller(GTK_WIDGET(view), controller); + controller = GTK_EVENT_CONTROLLER(gtk_gesture_click_new()); + /* check all buttons */ + gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(controller), 0); + g_signal_connect(controller, "released", + G_CALLBACK(talkatu_view_released_cb), view); + gtk_widget_add_controller(GTK_WIDGET(view), controller); /* we need to know when the buffer is changed in our parent so we can
* update our actions and other stuff.
@@ -365,42 +307,21 @@
GObjectClass *obj_class = G_OBJECT_CLASS(klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
GtkTextViewClass *text_view_class = GTK_TEXT_VIEW_CLASS(klass);
- GtkBindingSet *binding_set = NULL;
obj_class->finalize = talkatu_view_finalize;
- widget_class->motion_notify_event = talkatu_view_motion_notify_event;
widget_class->query_tooltip = talkatu_view_query_tooltip;
+ gtk_widget_class_set_template_from_resource( + "/org/imfreedom/keep/talkatu/talkatu/ui/view.ui" text_view_class->create_buffer = talkatu_view_create_buffer;
- /* add our default signal handlers */
- klass->format_activate = talkatu_view_format_activate;
- * TalkatuView::format-activate
- * @talkatutextview: The #TalkatuView instance.
- * @arg1: The name of the action to activated.
- * @user_data: User supplied data.
- * Emitted by the keybindings to apply a format to the underlying buffer.
- signals[SIG_FORMAT_ACTIVATE] = g_signal_new(
- G_TYPE_FROM_CLASS(klass),
- G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
- G_STRUCT_OFFSET(TalkatuViewClass, format_activate),
* @talkatutextview: The #TalkatuView instances.
@@ -421,22 +342,23 @@
- /* setup key bindings */
- binding_set = gtk_binding_set_by_class(talkatu_view_parent_class);
+ gtk_widget_class_bind_template_child_private(widget_class, TalkatuView, menu); - /* remove existing keybindings that we're overriding */
- gtk_binding_entry_remove(binding_set, GDK_KEY_slash, GDK_CONTROL_MASK);
+ gtk_widget_class_install_action(widget_class, "link.open", NULL, + talkatu_view_link_open_cb); + gtk_widget_class_install_action(widget_class, "link.copy", NULL, + talkatu_view_link_copy_cb); /* add our custom keybindings */
- gtk_binding_entry_add_signal(binding_set, GDK_KEY_b, GDK_CONTROL_MASK, "format-activate", 1, G_TYPE_STRING, TALKATU_ACTION_FORMAT_BOLD);
- gtk_binding_entry_add_signal(binding_set, GDK_KEY_i, GDK_CONTROL_MASK, "format-activate", 1, G_TYPE_STRING, TALKATU_ACTION_FORMAT_ITALIC);
- gtk_binding_entry_add_signal(binding_set, GDK_KEY_u, GDK_CONTROL_MASK, "format-activate", 1, G_TYPE_STRING, TALKATU_ACTION_FORMAT_UNDERLINE);
- gtk_binding_entry_add_signal(binding_set, GDK_KEY_slash, GDK_CONTROL_MASK, "format-activate", 1, G_TYPE_STRING, TALKATU_ACTION_FORMAT_STRIKETHROUGH);
- gtk_binding_entry_add_signal(binding_set, GDK_KEY_plus, GDK_CONTROL_MASK, "format-activate", 1, G_TYPE_STRING, TALKATU_ACTION_FORMAT_GROW);
- gtk_binding_entry_add_signal(binding_set, GDK_KEY_equal, GDK_CONTROL_MASK, "format-activate", 1, G_TYPE_STRING, TALKATU_ACTION_FORMAT_GROW);
- gtk_binding_entry_add_signal(binding_set, GDK_KEY_minus, GDK_CONTROL_MASK, "format-activate", 1, G_TYPE_STRING, TALKATU_ACTION_FORMAT_SHRINK);
- gtk_binding_entry_add_signal(binding_set, GDK_KEY_r, GDK_CONTROL_MASK, "format-activate", 1, G_TYPE_STRING, TALKATU_ACTION_FORMAT_RESET);
- gtk_binding_entry_add_signal(binding_set, GDK_KEY_Insert, GDK_MOD1_MASK | GDK_SHIFT_MASK, "insert-at-cursor", 1, G_TYPE_STRING, "🐣");
+ gtk_widget_class_add_binding_action(widget_class, GDK_KEY_b, GDK_CONTROL_MASK, TALKATU_ACTION_FORMAT_BOLD, NULL); + gtk_widget_class_add_binding_action(widget_class, GDK_KEY_i, GDK_CONTROL_MASK, TALKATU_ACTION_FORMAT_ITALIC, NULL); + gtk_widget_class_add_binding_action(widget_class, GDK_KEY_slash, GDK_CONTROL_MASK, TALKATU_ACTION_FORMAT_STRIKETHROUGH, NULL); + gtk_widget_class_add_binding_action(widget_class, GDK_KEY_plus, GDK_CONTROL_MASK, TALKATU_ACTION_FORMAT_GROW, NULL); + gtk_widget_class_add_binding_action(widget_class, GDK_KEY_equal, GDK_CONTROL_MASK, TALKATU_ACTION_FORMAT_GROW, NULL); + gtk_widget_class_add_binding_action(widget_class, GDK_KEY_minus, GDK_CONTROL_MASK, TALKATU_ACTION_FORMAT_SHRINK, NULL); + gtk_widget_class_add_binding_action(widget_class, GDK_KEY_r, GDK_CONTROL_MASK, TALKATU_ACTION_FORMAT_RESET, NULL); + gtk_widget_class_add_binding_signal(widget_class, GDK_KEY_Insert, GDK_META_MASK | GDK_SHIFT_MASK, "insert-at-cursor", "s", "🐣"); /******************************************************************************