From 270730d9b36c8fc3a221da7e565632c1432d41c6 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 5 Jun 2022 21:41:57 -0400 Subject: start attachments (image paste and upload) --- src/components/chatinput.cpp | 204 ++++++++++++++++++++++++++++++++++++++++-- src/components/chatinput.hpp | 89 +++++++++++++++++- src/components/chatlist.cpp | 4 - src/components/chatlist.hpp | 3 - src/components/chatwindow.cpp | 11 ++- src/components/chatwindow.hpp | 4 +- 6 files changed, 291 insertions(+), 24 deletions(-) (limited to 'src/components') diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index c3eca32..6afdd82 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -1,6 +1,10 @@ #include "chatinput.hpp" +#include "abaddon.hpp" +#include "constants.hpp" +#include +#include -ChatInput::ChatInput() { +ChatInputText::ChatInputText() { get_style_context()->add_class("message-input"); set_propagate_natural_height(true); set_min_content_height(20); @@ -20,22 +24,26 @@ ChatInput::ChatInput() { add(m_textview); } -void ChatInput::InsertText(const Glib::ustring &text) { +void ChatInputText::InsertText(const Glib::ustring &text) { GetBuffer()->insert_at_cursor(text); m_textview.grab_focus(); } -Glib::RefPtr ChatInput::GetBuffer() { +Glib::RefPtr ChatInputText::GetBuffer() { return m_textview.get_buffer(); } // this isnt connected directly so that the chat window can handle stuff like the completer first -bool ChatInput::ProcessKeyPress(GdkEventKey *event) { +bool ChatInputText::ProcessKeyPress(GdkEventKey *event) { if (event->keyval == GDK_KEY_Escape) { m_signal_escape.emit(); return true; } + if ((event->state & GDK_CONTROL_MASK) && event->keyval == GDK_KEY_v) { + return CheckHandleClipboardPaste(); + } + if (event->keyval == GDK_KEY_Return) { if (event->state & GDK_SHIFT_MASK) return false; @@ -53,10 +61,196 @@ bool ChatInput::ProcessKeyPress(GdkEventKey *event) { return false; } -void ChatInput::on_grab_focus() { +void ChatInputText::on_grab_focus() { m_textview.grab_focus(); } +bool ChatInputText::CheckHandleClipboardPaste() { + auto clip = Gtk::Clipboard::get(); + if (!clip->wait_is_image_available()) return false; + + const auto pb = clip->wait_for_image(); + std::array dest_name {}; + if (std::tmpnam(dest_name.data()) == nullptr) { + fprintf(stderr, "failed to get temporary path\n"); + return true; + } + + // stinky + std::filesystem::path part1(std::filesystem::temp_directory_path() / "abaddon-cache"); + std::filesystem::path part2(dest_name.data()); + const auto dest_path = (part1 / part2.relative_path()).string(); + + try { + pb->save(dest_path, "png"); + } catch (...) { + fprintf(stderr, "pasted image save error\n"); + return true; + } + + m_signal_image_paste.emit(pb, dest_path); + + return true; +} + +ChatInputText::type_signal_submit ChatInputText::signal_submit() { + return m_signal_submit; +} + +ChatInputText::type_signal_escape ChatInputText::signal_escape() { + return m_signal_escape; +} + +ChatInputText::type_signal_image_paste ChatInputText::signal_image_paste() { + return m_signal_image_paste; +} + +ChatInputAttachmentContainer::ChatInputAttachmentContainer() + : m_box(Gtk::ORIENTATION_HORIZONTAL) { + get_style_context()->add_class("attachment-container"); + + add(m_box); + m_box.show(); + + set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_NEVER); + set_vexpand(true); + set_size_request(-1, AttachmentItemSize + 10); +} + +void ChatInputAttachmentContainer::Clear() { + for (auto *x : m_attachments) + delete x; + m_attachments.clear(); +} + +bool ChatInputAttachmentContainer::AddImage(const Glib::RefPtr &pb, const std::string &path) { + if (m_attachments.size() == 10) return false; + + auto *item = Gtk::make_managed(path, pb); + item->show(); + item->set_valign(Gtk::ALIGN_CENTER); + m_box.add(*item); + + m_attachments.insert(item); + + item->signal_remove().connect([this, item] { + m_attachments.erase(item); + delete item; + if (m_attachments.empty()) + m_signal_emptied.emit(); + }); + + return true; +} + +std::vector ChatInputAttachmentContainer::GetFilePaths() const { + std::vector ret; + for (auto *x : m_attachments) + ret.push_back(x->GetPath()); + return ret; +} + +ChatInputAttachmentContainer::type_signal_emptied ChatInputAttachmentContainer::signal_emptied() { + return m_signal_emptied; +} + +ChatInputAttachmentItem::ChatInputAttachmentItem(std::string path, const Glib::RefPtr &pb) + : m_path(std::move(path)) + , m_img(Gtk::make_managed()) { + get_style_context()->add_class("attachment-item"); + + int outw, outh; + GetImageDimensions(pb->get_width(), pb->get_height(), outw, outh, AttachmentItemSize, AttachmentItemSize); + m_img->property_pixbuf() = pb->scale_simple(outw, outh, Gdk::INTERP_BILINEAR); + + set_size_request(AttachmentItemSize, AttachmentItemSize); + m_box.add(*m_img); + add(m_box); + show_all_children(); + + SetupMenu(); +} + +std::string ChatInputAttachmentItem::GetPath() const { + return m_path; +} + +void ChatInputAttachmentItem::SetupMenu() { + m_menu_remove.set_label("Remove"); + m_menu_remove.signal_activate().connect([this] { + m_signal_remove.emit(); + }); + + m_menu.add(m_menu_remove); + m_menu.show_all(); + + signal_button_press_event().connect([this](GdkEventButton *ev) -> bool { + if (ev->button == GDK_BUTTON_SECONDARY) { + m_menu.popup_at_pointer(reinterpret_cast(ev)); + return true; + } + + return false; + }); +} + +ChatInputAttachmentItem::type_signal_remove ChatInputAttachmentItem::signal_remove() { + return m_signal_remove; +} + +ChatInput::ChatInput() + : Gtk::Box(Gtk::ORIENTATION_VERTICAL) { + m_input.signal_escape().connect([this] { + m_attachments.Clear(); + m_attachments_revealer.set_reveal_child(false); + m_signal_escape.emit(); + }); + m_input.signal_submit().connect([this](const Glib::ustring &input) -> bool { + const auto attachments = m_attachments.GetFilePaths(); + bool b = m_signal_submit.emit(input, attachments); + if (b) { + m_attachments.Clear(); + m_attachments_revealer.set_reveal_child(false); + } + return b; + }); + + m_attachments.set_vexpand(false); + + m_attachments_revealer.set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_SLIDE_UP); + m_attachments_revealer.add(m_attachments); + add(m_attachments_revealer); + add(m_input); + show_all_children(); + + m_input.signal_image_paste().connect([this](const Glib::RefPtr &pb, const std::string &path) { + if (m_attachments.AddImage(pb, path)) + m_attachments_revealer.set_reveal_child(true); + }); + + // double hack ! + auto cb = [this](GdkEventKey *e) -> bool { + return event(reinterpret_cast(e)); + }; + m_input.signal_key_press_event().connect(cb, false); + + m_attachments.signal_emptied().connect([this] { + m_attachments_revealer.set_reveal_child(false); + }); +} + +void ChatInput::InsertText(const Glib::ustring &text) { + m_input.InsertText(text); +} + +Glib::RefPtr ChatInput::GetBuffer() { + return m_input.GetBuffer(); +} + +bool ChatInput::ProcessKeyPress(GdkEventKey *event) { + return m_input.ProcessKeyPress(event); +} + ChatInput::type_signal_submit ChatInput::signal_submit() { return m_signal_submit; } diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index ad7f0b1..a6b2a31 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -1,9 +1,57 @@ #pragma once #include -class ChatInput : public Gtk::ScrolledWindow { +class ChatInputAttachmentItem : public Gtk::EventBox { public: - ChatInput(); + ChatInputAttachmentItem(std::string path, const Glib::RefPtr &pb); + + [[nodiscard]] std::string GetPath() const; + +private: + void SetupMenu(); + + Gtk::Menu m_menu; + Gtk::MenuItem m_menu_remove; + + Gtk::Box m_box; + Gtk::Image *m_img = nullptr; + + std::string m_path; + +private: + using type_signal_remove = sigc::signal; + + type_signal_remove m_signal_remove; + +public: + type_signal_remove signal_remove(); +}; + +class ChatInputAttachmentContainer : public Gtk::ScrolledWindow { +public: + ChatInputAttachmentContainer(); + + void Clear(); + bool AddImage(const Glib::RefPtr &pb, const std::string &path); + [[nodiscard]] std::vector GetFilePaths() const; + +private: + std::set m_attachments; + + Gtk::Box m_box; + +private: + using type_signal_emptied = sigc::signal; + + type_signal_emptied m_signal_emptied; + +public: + type_signal_emptied signal_emptied(); +}; + +class ChatInputText : public Gtk::ScrolledWindow { +public: + ChatInputText(); void InsertText(const Glib::ustring &text); Glib::RefPtr GetBuffer(); @@ -15,9 +63,42 @@ protected: private: Gtk::TextView m_textview; + bool CheckHandleClipboardPaste(); + void HandleNewPastedImage(const Glib::RefPtr &pb, const std::string &filename); + +public: + using type_signal_submit = sigc::signal; + using type_signal_escape = sigc::signal; + using type_signal_image_paste = sigc::signal, std::string>; + + type_signal_submit signal_submit(); + type_signal_escape signal_escape(); + type_signal_image_paste signal_image_paste(); + +private: + type_signal_submit m_signal_submit; + type_signal_escape m_signal_escape; + type_signal_image_paste m_signal_image_paste; +}; + +class ChatInput : public Gtk::Box { +public: + ChatInput(); + + void InsertText(const Glib::ustring &text); + Glib::RefPtr GetBuffer(); + bool ProcessKeyPress(GdkEventKey *event); + +private: + Gtk::Revealer m_attachments_revealer; + ChatInputAttachmentContainer m_attachments; + ChatInputText m_input; + public: - typedef sigc::signal type_signal_submit; - typedef sigc::signal type_signal_escape; + // text, attachments -> request sent + // maybe this should be reduced to a single struct, its bound to get more complicated (application commands?) + using type_signal_submit = sigc::signal>; + using type_signal_escape = sigc::signal; type_signal_submit signal_submit(); type_signal_escape signal_escape(); diff --git a/src/components/chatlist.cpp b/src/components/chatlist.cpp index 5b923b5..9bd8daf 100644 --- a/src/components/chatlist.cpp +++ b/src/components/chatlist.cpp @@ -352,10 +352,6 @@ ChatList::type_signal_action_message_edit ChatList::signal_action_message_edit() return m_signal_action_message_edit; } -ChatList::type_signal_action_chat_submit ChatList::signal_action_chat_submit() { - return m_signal_action_chat_submit; -} - ChatList::type_signal_action_chat_load_history ChatList::signal_action_chat_load_history() { return m_signal_action_chat_load_history; } diff --git a/src/components/chatlist.hpp b/src/components/chatlist.hpp index f77bbd6..9cc6992 100644 --- a/src/components/chatlist.hpp +++ b/src/components/chatlist.hpp @@ -63,7 +63,6 @@ private: public: // these are all forwarded by the parent using type_signal_action_message_edit = sigc::signal; - using type_signal_action_chat_submit = sigc::signal; using type_signal_action_chat_load_history = sigc::signal; using type_signal_action_channel_click = sigc::signal; using type_signal_action_insert_mention = sigc::signal; @@ -73,7 +72,6 @@ public: using type_signal_action_reply_to = sigc::signal; type_signal_action_message_edit signal_action_message_edit(); - type_signal_action_chat_submit signal_action_chat_submit(); type_signal_action_chat_load_history signal_action_chat_load_history(); type_signal_action_channel_click signal_action_channel_click(); type_signal_action_insert_mention signal_action_insert_mention(); @@ -84,7 +82,6 @@ public: private: type_signal_action_message_edit m_signal_action_message_edit; - type_signal_action_chat_submit m_signal_action_chat_submit; type_signal_action_chat_load_history m_signal_action_chat_load_history; type_signal_action_channel_click m_signal_action_channel_click; type_signal_action_insert_mention m_signal_action_insert_mention; diff --git a/src/components/chatwindow.cpp b/src/components/chatwindow.cpp index 5aab4e6..46c34d4 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -45,6 +45,8 @@ ChatWindow::ChatWindow() { m_topic_text.set_halign(Gtk::ALIGN_START); m_topic_text.show(); + m_input->set_valign(Gtk::ALIGN_END); + m_input->signal_submit().connect(sigc::mem_fun(*this, &ChatWindow::OnInputSubmit)); m_input->signal_escape().connect([this]() { if (m_is_replying) @@ -70,9 +72,6 @@ ChatWindow::ChatWindow() { m_chat->signal_action_chat_load_history().connect([this](Snowflake id) { m_signal_action_chat_load_history.emit(id); }); - m_chat->signal_action_chat_submit().connect([this](const std::string &str, Snowflake channel_id, Snowflake referenced_id) { - m_signal_action_chat_submit.emit(str, channel_id, referenced_id); - }); m_chat->signal_action_insert_mention().connect([this](Snowflake id) { // lowkey gross m_signal_action_insert_mention.emit(id); @@ -210,15 +209,15 @@ Snowflake ChatWindow::GetActiveChannel() const { return m_active_channel; } -bool ChatWindow::OnInputSubmit(const Glib::ustring &text) { +bool ChatWindow::OnInputSubmit(const Glib::ustring &text, const std::vector &attachment_paths) { if (!m_rate_limit_indicator->CanSpeak()) return false; - if (text.empty()) + if (text.empty() && attachment_paths.empty()) return false; if (m_active_channel.IsValid()) - m_signal_action_chat_submit.emit(text, m_active_channel, m_replying_to); // m_replying_to is checked for invalid in the handler + m_signal_action_chat_submit.emit(text, attachment_paths, m_active_channel, m_replying_to); // m_replying_to is checked for invalid in the handler if (m_is_replying) StopReplying(); diff --git a/src/components/chatwindow.hpp b/src/components/chatwindow.hpp index 9b27ff1..ab0bee1 100644 --- a/src/components/chatwindow.hpp +++ b/src/components/chatwindow.hpp @@ -55,7 +55,7 @@ protected: Snowflake m_active_channel; - bool OnInputSubmit(const Glib::ustring &text); + bool OnInputSubmit(const Glib::ustring &text, const std::vector &attachment_paths); bool OnKeyPressEvent(GdkEventKey *e); void OnScrollEdgeOvershot(Gtk::PositionType pos); @@ -84,7 +84,7 @@ protected: public: using type_signal_action_message_edit = sigc::signal; - using type_signal_action_chat_submit = sigc::signal; + using type_signal_action_chat_submit = sigc::signal, Snowflake, Snowflake>; using type_signal_action_chat_load_history = sigc::signal; using type_signal_action_channel_click = sigc::signal; using type_signal_action_insert_mention = sigc::signal; -- cgit v1.2.3