Private
Public Access
1
0
Files
Kordophone/src/transcript/transcript-view.vala

189 lines
6.9 KiB
Vala
Raw Normal View History

using Adw;
using Gtk;
2025-04-30 19:12:00 -07:00
using Gee;
2025-05-03 22:47:56 -07:00
public class TranscriptView : Adw.Bin
{
2025-04-30 19:50:36 -07:00
public MessageListModel? model {
get {
return _model;
}
set {
2025-05-14 17:37:23 -07:00
if (_model != null) {
_model.disconnect(messages_changed_handler_id);
_model.unwatch_updates();
}
2025-04-30 19:50:36 -07:00
_model = value;
2025-05-14 17:37:23 -07:00
if (value != null) {
reset_for_conversation_change();
2025-05-14 17:37:23 -07:00
weak TranscriptView self = this;
messages_changed_handler_id = value.messages_changed.connect(() => {
self.reload_messages();
});
value.load_messages();
value.watch_updates();
2025-04-30 19:50:36 -07:00
} else {
2025-05-14 17:37:23 -07:00
transcript_drawing_area.set_messages(new ArrayList<Message>());
2025-04-30 19:50:36 -07:00
}
}
}
public string title {
get { return title_label.label; }
set { title_label.label = value; }
}
2025-04-30 19:50:36 -07:00
private MessageListModel? _model = null;
private Adw.ToolbarView container;
private Label title_label = new Label("Messages");
private Overlay overlay = new Overlay();
2025-05-03 22:47:56 -07:00
private TranscriptDrawingArea transcript_drawing_area = new TranscriptDrawingArea();
private ScrolledWindow scrolled_window = new ScrolledWindow();
2025-05-14 17:37:23 -07:00
private ulong messages_changed_handler_id = 0;
private bool needs_reload = false;
private Graphene.Point _hovering_text_view_origin = Graphene.Point() { x = 0, y = 0 };
private TextView _hovering_text_view = new TextView();
private VisibleTextLayout? hovered_text_bubble = null;
private VisibleTextLayout? locked_text_bubble = null;
2025-05-14 17:37:23 -07:00
public TranscriptView() {
container = new Adw.ToolbarView();
set_child(container);
// Set minimum width for the transcript view
set_size_request(330, -1);
overlay.set_child(transcript_drawing_area);
weak TranscriptView self = this;
overlay.get_child_position.connect((child, out allocation) => {
allocation = Gtk.Allocation() { x = 0, y = 0, width = 0, height = 0 };
if (self.hovered_text_bubble != null) {
var rect = self.hovered_text_bubble.rect;
allocation.x = (int)(rect.origin.x + self._hovering_text_view_origin.x);
allocation.y = (int)(rect.origin.y - self._hovering_text_view_origin.y);
allocation.width = (int)rect.size.width;
allocation.height = (int)rect.size.height;
return true;
}
return true;
});
scrolled_window.set_child(overlay);
scrolled_window.add_css_class("flipped-y-axis");
transcript_drawing_area.viewport = scrolled_window.vadjustment;
container.set_content(scrolled_window);
// Connect to the adjustment's value_changed signal
scrolled_window.vadjustment.value_changed.connect(() => {
transcript_drawing_area.viewport = scrolled_window.vadjustment;
});
var header_bar = new Adw.HeaderBar();
2025-05-12 20:46:12 -07:00
title_label.single_line_mode = true;
title_label.ellipsize = Pango.EllipsizeMode.END;
header_bar.set_title_widget(title_label);
container.add_top_bar(header_bar);
// This is an invisible text view that's used to handle selection.
_hovering_text_view.add_css_class("hovering-text-view");
_hovering_text_view.add_css_class("flipped-y-axis");
_hovering_text_view.set_editable(false);
_hovering_text_view.set_wrap_mode(Gtk.WrapMode.WORD_CHAR);
overlay.add_overlay(_hovering_text_view);
// 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);
});
// When the mouse hovers over a text bubble, configure the hovering text view to show the text of the bubble.
transcript_drawing_area.on_text_bubble_hover.connect((visible_text_layout) => {
if (visible_text_layout != null) {
configure_hovering_text_view(visible_text_layout);
}
});
// This is triggered when another bubble is currently locked, and the user clicks on a new bubble.
transcript_drawing_area.on_text_bubble_click.connect((visible_text_layout) => {
if (visible_text_layout != null) {
locked_text_bubble = null;
configure_hovering_text_view(visible_text_layout);
}
});
Repository.get_instance().attachment_downloaded.connect((attachment_guid) => {
debug("Attachment downloaded: %s", attachment_guid);
// See if this attachment is part of this transcript.
bool contains_attachment = false;
foreach (var message in _model.messages) {
foreach (var attachment in message.attachments) {
if (attachment.guid == attachment_guid) {
contains_attachment = true;
break;
}
}
}
if (contains_attachment && !needs_reload) {
debug("Queueing reload of messages for attachment download");
needs_reload = true;
GLib.Idle.add(() => {
if (needs_reload) {
debug("Reloading messages for attachment download");
model.load_messages();
needs_reload = false;
}
return false;
}, GLib.Priority.HIGH);
}
});
2025-04-30 19:50:36 -07:00
}
private void reset_for_conversation_change() {
locked_text_bubble = null;
hovered_text_bubble = null;
_hovering_text_view.buffer.text = "";
overlay.queue_allocate();
// Reset scroll position by updating the existing adjustment
scrolled_window.vadjustment.value = 0;
scrolled_window.vadjustment.upper = 0;
scrolled_window.vadjustment.page_size = 0;
}
private void lock_text_bubble(VisibleTextLayout? visible_text_layout) {
if (visible_text_layout != null) {
locked_text_bubble = visible_text_layout;
configure_hovering_text_view(visible_text_layout);
}
}
private void configure_hovering_text_view(VisibleTextLayout? visible_text_layout) {
hovered_text_bubble = visible_text_layout;
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;
overlay.queue_allocate();
}
}
2025-04-30 19:50:36 -07:00
private void reload_messages() {
2025-05-03 23:19:15 -07:00
transcript_drawing_area.show_sender = _model.is_group_chat;
2025-05-03 22:47:56 -07:00
transcript_drawing_area.set_messages(_model.messages);
}
}