Private
Public Access
1
0

Switch from Entry to TextView for multiline, paste support for attachments

This commit is contained in:
2025-06-12 20:35:56 -07:00
parent 137da5b3d1
commit 6fb88c3a0d
7 changed files with 179 additions and 22 deletions

View File

@@ -1,6 +1,8 @@
using Gtk;
using Adw;
using Gee;
using Gdk;
using GLib;
class TranscriptContainerView : Adw.Bin
{
@@ -11,14 +13,17 @@ class TranscriptContainerView : Adw.Bin
private Button send_button;
private FlowBox attachment_flow_box;
private Entry message_entry;
private TextView message_view;
private TextBuffer message_buffer;
private HashSet<string> pending_uploads;
private HashMap<string, AttachmentPreview> attachment_previews;
private ArrayList<UploadedAttachment> completed_attachments;
public string message_body {
get {
return message_entry.text;
owned get {
TextIter start_iter, end_iter;
message_buffer.get_bounds(out start_iter, out end_iter);
return message_buffer.get_text(start_iter, end_iter, false);
}
}
@@ -36,7 +41,7 @@ class TranscriptContainerView : Adw.Bin
private bool can_send {
get {
return (message_entry.text.length > 0 || completed_attachments.size > 0) && pending_uploads.size == 0;
return (message_body.length > 0 || completed_attachments.size > 0) && pending_uploads.size == 0;
}
}
@@ -69,14 +74,38 @@ class TranscriptContainerView : Adw.Bin
// Connect to repository signals
Repository.get_instance().attachment_uploaded.connect(on_attachment_uploaded);
// Create message entry
message_entry = new Entry();
message_entry.add_css_class("message-input-entry");
message_entry.set_placeholder_text("Type a message...");
message_entry.set_hexpand(true);
message_entry.changed.connect(on_text_changed);
message_entry.activate.connect(on_request_send);
input_box.append(message_entry);
// Create attach button (paperclip)
var attach_button = new Button.from_icon_name("mail-attachment");
attach_button.set_tooltip_text("Attach file…");
attach_button.add_css_class("flat");
attach_button.clicked.connect(on_attach_button_clicked);
input_box.append(attach_button);
// Create message text view (added after attachment button so button stays to the left)
message_buffer = new TextBuffer(null);
message_view = new TextView.with_buffer(message_buffer);
message_view.add_css_class("message-input-entry");
message_view.set_wrap_mode(Gtk.WrapMode.WORD_CHAR);
message_view.set_hexpand(true);
message_view.set_vexpand(false);
message_view.set_size_request(-1, 12); // intrinsic
message_buffer.changed.connect(on_text_changed);
// Key controller for sending on Enter (Shift+Enter for newline)
var send_key_ctrl = new EventControllerKey();
send_key_ctrl.key_pressed.connect((keyval, keycode, state) => {
if (keyval == Gdk.Key.Return && (state & Gdk.ModifierType.SHIFT_MASK) == 0) {
on_request_send();
return true; // consume
}
return false;
});
message_view.add_controller(send_key_ctrl);
// Handle paste events to detect images
message_view.paste_clipboard.connect(on_message_paste_clipboard);
input_box.append(message_view);
// Create send button
send_button = new Button();
@@ -196,8 +225,8 @@ class TranscriptContainerView : Adw.Bin
if (can_send) {
on_send(this);
// Clear the message entry
message_entry.text = "";
// Clear the message text
message_buffer.set_text("");
// Clear the attachment previews
attachment_flow_box.remove_all();
@@ -208,6 +237,65 @@ class TranscriptContainerView : Adw.Bin
update_send_button_sensitivity();
}
}
private void on_attach_button_clicked() {
var dialog = new Gtk.FileDialog();
dialog.set_title("Select attachment");
dialog.set_accept_label("Attach");
dialog.set_modal(true);
// Images only for now
var filter = new Gtk.FileFilter();
filter.set_filter_name("Images");
filter.add_mime_type("image/png");
filter.add_mime_type("image/jpeg");
filter.add_mime_type("image/gif");
filter.add_mime_type("image/bmp");
filter.add_mime_type("image/webp");
filter.add_mime_type("image/svg+xml");
filter.add_mime_type("image/tiff");
dialog.set_default_filter(filter);
var parent_window = get_root() as Gtk.Window;
dialog.open.begin(parent_window, null, (obj, res) => {
try {
var file = dialog.open.end(res);
if (file != null) {
upload_file(file);
}
} catch (Error e) {
warning("Failed to open file dialog: %s", e.message);
}
});
}
private void on_message_paste_clipboard() {
var display = get_display();
if (display == null) {
return;
}
var clipboard = display.get_clipboard();
if (clipboard == null) {
return;
}
clipboard.read_texture_async.begin(null, (obj, res) => {
try {
var clip = obj as Gdk.Clipboard;
var texture = clip.read_texture_async.end(res);
if (texture != null) {
string tmp_path = Path.build_filename(Environment.get_tmp_dir(), "clipboard-" + Uuid.string_random() + ".png");
texture.save_to_png(tmp_path);
var tmp_file = File.new_for_path(tmp_path);
upload_file(tmp_file);
}
} catch (Error e) {
// Ignore if clipboard does not contain image
}
});
}
}
class UploadedAttachment