Adds link clicking support
This commit is contained in:
@@ -30,6 +30,22 @@ public class Message : Object, Comparable<Message>, Hashable<Message>
|
||||
}
|
||||
}
|
||||
|
||||
public string markup {
|
||||
owned get {
|
||||
const string link_regex_pattern = "https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)";
|
||||
|
||||
try {
|
||||
var regex = new GLib.Regex(link_regex_pattern);
|
||||
|
||||
var escaped_text = GLib.Markup.escape_text(this.text);
|
||||
return regex.replace(escaped_text, escaped_text.length, 0, "<u>\\0</u>");
|
||||
} catch (GLib.RegexError e) {
|
||||
GLib.warning("Error linking text: %s", e.message);
|
||||
return GLib.Markup.escape_text(this.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Message(string text, DateTime date, string? sender) {
|
||||
this.text = text;
|
||||
this.date = date;
|
||||
|
||||
@@ -24,7 +24,8 @@ public class TextBubbleLayout : BubbleLayout
|
||||
this.from_me = message.from_me;
|
||||
this.message = message;
|
||||
|
||||
layout = parent.create_pango_layout(message.text);
|
||||
layout = parent.create_pango_layout(null);
|
||||
layout.set_markup(message.markup, -1);
|
||||
|
||||
var font_desc = TextBubbleLayout.body_font;
|
||||
layout.set_font_description(font_desc);
|
||||
|
||||
@@ -50,6 +50,7 @@ public class TranscriptView : Adw.Bin
|
||||
private Graphene.Point _hovering_text_view_origin = Graphene.Point() { x = 0, y = 0 };
|
||||
private TextView _hovering_text_view = new TextView();
|
||||
|
||||
private bool _queued_url_open = false;
|
||||
private VisibleTextLayout? hovered_text_bubble = null;
|
||||
private VisibleTextLayout? locked_text_bubble = null;
|
||||
|
||||
@@ -103,7 +104,8 @@ public class TranscriptView : Adw.Bin
|
||||
|
||||
// When the selection changes, lock the text bubble so that if the cursor moves to another bubble we don't clear it.
|
||||
_hovering_text_view.buffer.mark_set.connect((location, end) => {
|
||||
self.lock_text_bubble(self.hovered_text_bubble);
|
||||
if (location.get_offset() == 0) { return; }
|
||||
self.on_hovering_text_view_clicked(location, end);
|
||||
});
|
||||
|
||||
// When the mouse hovers over a text bubble, configure the hovering text view to show the text of the bubble.
|
||||
@@ -152,6 +154,53 @@ public class TranscriptView : Adw.Bin
|
||||
});
|
||||
}
|
||||
|
||||
private void on_hovering_text_view_clicked(TextIter location, TextMark end) {
|
||||
lock_text_bubble(hovered_text_bubble);
|
||||
|
||||
if (!_queued_url_open) {
|
||||
_queued_url_open = true;
|
||||
|
||||
// 100ms timeout to let the selection state settle. (this is a workaround)
|
||||
GLib.Timeout.add(100, () => {
|
||||
open_url_at_location(location);
|
||||
_queued_url_open = false;
|
||||
return false;
|
||||
}, GLib.Priority.HIGH);
|
||||
}
|
||||
}
|
||||
|
||||
private void open_url_at_location(TextIter location) {
|
||||
Gtk.TextTag? underline_tag = null;
|
||||
foreach (unowned Gtk.TextTag tag in location.get_tags()) {
|
||||
if (tag.underline != Pango.Underline.NONE) {
|
||||
underline_tag = tag;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (underline_tag == null) {
|
||||
return; // Click wasn't on an underlined (i.e. link) region
|
||||
}
|
||||
|
||||
// Determine the full extent (start/end iters) of this underlined region
|
||||
Gtk.TextIter start_iter = location;
|
||||
Gtk.TextIter end_iter = location;
|
||||
start_iter.backward_to_tag_toggle(underline_tag);
|
||||
end_iter.forward_to_tag_toggle(underline_tag);
|
||||
|
||||
string url = _hovering_text_view.buffer.get_text(start_iter, end_iter, false);
|
||||
|
||||
// Try to open the URL – guard against malformed data
|
||||
if (url != null && url.strip().length > 0) {
|
||||
try {
|
||||
GLib.AppInfo.launch_default_for_uri(url.strip(), null);
|
||||
} catch (GLib.Error e) {
|
||||
warning("Failed to open URL %s: %s", url, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void reset_for_conversation_change() {
|
||||
locked_text_bubble = null;
|
||||
hovered_text_bubble = null;
|
||||
@@ -176,7 +225,11 @@ public class TranscriptView : Adw.Bin
|
||||
|
||||
if (locked_text_bubble == null) {
|
||||
_hovering_text_view_origin = visible_text_layout.text_bubble.get_text_origin();
|
||||
_hovering_text_view.buffer.text = visible_text_layout.text_bubble.message.text;
|
||||
|
||||
_hovering_text_view.buffer.text = "";
|
||||
Gtk.TextIter start_iter;
|
||||
_hovering_text_view.buffer.get_start_iter(out start_iter);
|
||||
_hovering_text_view.buffer.insert_markup(ref start_iter, visible_text_layout.text_bubble.message.markup, -1);
|
||||
overlay.queue_allocate();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user