diff options
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/chatinput.cpp | 410 | ||||
-rw-r--r-- | src/components/chatinput.hpp | 111 | ||||
-rw-r--r-- | src/components/chatlist.cpp | 7 | ||||
-rw-r--r-- | src/components/chatlist.hpp | 3 | ||||
-rw-r--r-- | src/components/chatmessage.cpp | 4 | ||||
-rw-r--r-- | src/components/chatwindow.cpp | 82 | ||||
-rw-r--r-- | src/components/chatwindow.hpp | 8 | ||||
-rw-r--r-- | src/components/progressbar.cpp | 24 | ||||
-rw-r--r-- | src/components/progressbar.hpp | 11 |
9 files changed, 628 insertions, 32 deletions
diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 3e1db15..13e2550 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -1,6 +1,9 @@ #include "chatinput.hpp" +#include "abaddon.hpp" +#include "constants.hpp" +#include <filesystem> -ChatInput::ChatInput() { +ChatInputText::ChatInputText() { get_style_context()->add_class("message-input"); set_propagate_natural_height(true); set_min_content_height(20); @@ -20,22 +23,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<Gtk::TextBuffer> ChatInput::GetBuffer() { +Glib::RefPtr<Gtk::TextBuffer> 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 +60,399 @@ 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(); + if (pb) { + m_signal_image_paste.emit(pb); + + return true; + } else { + return false; + } +} + +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"); + + m_box.set_halign(Gtk::ALIGN_START); + + 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 *item : m_attachments) { + item->RemoveIfTemp(); + delete item; + } + m_attachments.clear(); +} + +void ChatInputAttachmentContainer::ClearNoPurge() { + for (auto *item : m_attachments) { + delete item; + } + m_attachments.clear(); +} + +bool ChatInputAttachmentContainer::AddImage(const Glib::RefPtr<Gdk::Pixbuf> &pb) { + if (m_attachments.size() == 10) return false; + + static unsigned go_up = 0; + std::string dest_name = "pasted-image-" + std::to_string(go_up++); + const auto path = (std::filesystem::temp_directory_path() / "abaddon-cache" / dest_name).string(); + + try { + pb->save(path, "png"); + } catch (...) { + fprintf(stderr, "pasted image save error\n"); + return false; + } + + auto *item = Gtk::make_managed<ChatInputAttachmentItem>(Gio::File::create_for_path(path), pb); + item->set_valign(Gtk::ALIGN_FILL); + item->set_vexpand(true); + item->set_margin_bottom(5); + item->show(); + m_box.add(*item); + + m_attachments.push_back(item); + + item->signal_item_removed().connect([this, item] { + item->RemoveIfTemp(); + if (auto it = std::find(m_attachments.begin(), m_attachments.end(), item); it != m_attachments.end()) + m_attachments.erase(it); + delete item; + if (m_attachments.empty()) + m_signal_emptied.emit(); + }); + + return true; +} + +bool ChatInputAttachmentContainer::AddFile(const Glib::RefPtr<Gio::File> &file, Glib::RefPtr<Gdk::Pixbuf> pb) { + if (m_attachments.size() == 10) return false; + + ChatInputAttachmentItem *item; + if (pb) + item = Gtk::make_managed<ChatInputAttachmentItem>(file, pb, true); + else + item = Gtk::make_managed<ChatInputAttachmentItem>(file); + item->set_valign(Gtk::ALIGN_FILL); + item->set_vexpand(true); + item->set_margin_bottom(5); + item->show(); + m_box.add(*item); + + m_attachments.push_back(item); + + item->signal_item_removed().connect([this, item] { + if (auto it = std::find(m_attachments.begin(), m_attachments.end(), item); it != m_attachments.end()) + m_attachments.erase(it); + delete item; + if (m_attachments.empty()) + m_signal_emptied.emit(); + }); + + return true; +} + +std::vector<ChatSubmitParams::Attachment> ChatInputAttachmentContainer::GetAttachments() const { + std::vector<ChatSubmitParams::Attachment> ret; + for (auto *x : m_attachments) { + if (!x->GetFile()->query_exists()) + puts("bad!"); + ret.push_back({ x->GetFile(), x->GetType(), x->GetFilename() }); + } + return ret; +} + +ChatInputAttachmentContainer::type_signal_emptied ChatInputAttachmentContainer::signal_emptied() { + return m_signal_emptied; +} + +ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr<Gio::File> &file) + : m_file(file) + , m_img(Gtk::make_managed<Gtk::Image>()) + , m_type(ChatSubmitParams::ExtantFile) + , m_box(Gtk::ORIENTATION_VERTICAL) { + get_style_context()->add_class("attachment-item"); + + set_size_request(AttachmentItemSize, AttachmentItemSize); + set_halign(Gtk::ALIGN_START); + m_box.set_hexpand(true); + m_box.set_vexpand(true); + m_box.set_halign(Gtk::ALIGN_FILL); + m_box.set_valign(Gtk::ALIGN_FILL); + m_box.add(*m_img); + m_box.add(m_label); + add(m_box); + show_all_children(); + + m_label.set_valign(Gtk::ALIGN_END); + m_label.set_max_width_chars(0); // will constrain to given size + m_label.set_ellipsize(Pango::ELLIPSIZE_MIDDLE); + m_label.set_margin_start(7); + m_label.set_margin_end(7); + + m_img->set_vexpand(true); + m_img->property_icon_name() = "document-send-symbolic"; + m_img->property_icon_size() = Gtk::ICON_SIZE_DIALOG; // todo figure out how to not use this weird property??? i dont know how icons work (screw your theme) + + SetFilenameFromFile(); + + SetupMenu(); + UpdateTooltip(); +} + +ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr<Gio::File> &file, const Glib::RefPtr<Gdk::Pixbuf> &pb, bool is_extant) + : m_file(file) + , m_img(Gtk::make_managed<Gtk::Image>()) + , m_type(is_extant ? ChatSubmitParams::ExtantFile : ChatSubmitParams::PastedImage) + , m_filename("unknown.png") + , m_label("unknown.png") + , m_box(Gtk::ORIENTATION_VERTICAL) { + 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); + set_halign(Gtk::ALIGN_START); + m_box.set_hexpand(true); + m_box.set_vexpand(true); + m_box.set_halign(Gtk::ALIGN_FILL); + m_box.set_valign(Gtk::ALIGN_FILL); + m_box.add(*m_img); + m_box.add(m_label); + add(m_box); + show_all_children(); + + m_label.set_valign(Gtk::ALIGN_END); + m_label.set_max_width_chars(0); // will constrain to given size + m_label.set_ellipsize(Pango::ELLIPSIZE_MIDDLE); + m_label.set_margin_start(7); + m_label.set_margin_end(7); + + m_img->set_vexpand(true); + + if (is_extant) + SetFilenameFromFile(); + + SetupMenu(); + UpdateTooltip(); +} + +Glib::RefPtr<Gio::File> ChatInputAttachmentItem::GetFile() const { + return m_file; +} + +ChatSubmitParams::AttachmentType ChatInputAttachmentItem::GetType() const { + return m_type; +} + +std::string ChatInputAttachmentItem::GetFilename() const { + return m_filename; +} + +bool ChatInputAttachmentItem::IsTemp() const noexcept { + return m_type == ChatSubmitParams::PastedImage; +} + +void ChatInputAttachmentItem::RemoveIfTemp() { + if (IsTemp()) + m_file->remove(); +} + +void ChatInputAttachmentItem::SetFilenameFromFile() { + auto info = m_file->query_info(G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME); + m_filename = info->get_attribute_string(G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME); + m_label.set_text(m_filename); +} + +void ChatInputAttachmentItem::SetupMenu() { + m_menu_remove.set_label("Remove"); + m_menu_remove.signal_activate().connect([this] { + m_signal_item_removed.emit(); + }); + + m_menu_set_filename.set_label("Change Filename"); + m_menu_set_filename.signal_activate().connect([this] { + const auto name = Abaddon::Get().ShowTextPrompt("Enter new filename for attachment", "Enter filename", m_filename); + if (name.has_value()) { + m_filename = *name; + m_label.set_text(m_filename); + UpdateTooltip(); + } + }); + + m_menu.add(m_menu_set_filename); + 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<GdkEvent *>(ev)); + return true; + } + + return false; + }); +} + +void ChatInputAttachmentItem::UpdateTooltip() { + set_tooltip_text(m_filename); +} + +ChatInputAttachmentItem::type_signal_item_removed ChatInputAttachmentItem::signal_item_removed() { + return m_signal_item_removed; +} + +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 { + ChatSubmitParams data; + data.Message = input; + data.Attachments = m_attachments.GetAttachments(); + + bool b = m_signal_submit.emit(data); + if (b) { + m_attachments_revealer.set_reveal_child(false); + m_attachments.ClearNoPurge(); + } + 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<Gdk::Pixbuf> &pb) { + const bool can_attach_files = m_signal_check_permission.emit(Permission::ATTACH_FILES); + + if (can_attach_files && m_attachments.AddImage(pb)) + m_attachments_revealer.set_reveal_child(true); + }); + + // double hack ! + auto cb = [this](GdkEventKey *e) -> bool { + return event(reinterpret_cast<GdkEvent *>(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<Gtk::TextBuffer> ChatInput::GetBuffer() { + return m_input.GetBuffer(); +} + +bool ChatInput::ProcessKeyPress(GdkEventKey *event) { + return m_input.ProcessKeyPress(event); +} + +void ChatInput::AddAttachment(const Glib::RefPtr<Gio::File> &file) { + const bool can_attach_files = m_signal_check_permission.emit(Permission::ATTACH_FILES); + if (!can_attach_files) return; + + std::string content_type; + + try { + const auto info = file->query_info(G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE); + content_type = info->get_attribute_string(G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE); + } catch (const Gio::Error &err) { + printf("io error: %s\n", err.what().c_str()); + return; + } catch (...) { + puts("attachment query exception"); + return; + } + + static const std::unordered_set<std::string> image_exts { + ".png", + ".jpg", + }; + + if (image_exts.find(content_type) != image_exts.end()) { + if (AddFileAsImageAttachment(file)) { + m_attachments_revealer.set_reveal_child(true); + m_input.grab_focus(); + } + } else if (m_attachments.AddFile(file)) { + m_attachments_revealer.set_reveal_child(true); + m_input.grab_focus(); + } +} + +void ChatInput::IndicateTooLarge() { + m_input.get_style_context()->add_class("bad-input"); + const auto cb = [this] { + m_input.get_style_context()->remove_class("bad-input"); + }; + Glib::signal_timeout().connect_seconds_once(sigc::track_obj(cb, *this), 2); +} + +void ChatInput::StartReplying() { + m_input.grab_focus(); + m_input.get_style_context()->add_class("replying"); +} + +void ChatInput::StopReplying() { + m_input.get_style_context()->remove_class("replying"); +} + +bool ChatInput::AddFileAsImageAttachment(const Glib::RefPtr<Gio::File> &file) { + try { + const auto read_stream = file->read(); + if (!read_stream) return false; + const auto pb = Gdk::Pixbuf::create_from_stream(read_stream); + return m_attachments.AddFile(file, pb); + } catch (...) { + return m_attachments.AddFile(file); + } +} + ChatInput::type_signal_submit ChatInput::signal_submit() { return m_signal_submit; } @@ -64,3 +460,7 @@ ChatInput::type_signal_submit ChatInput::signal_submit() { ChatInput::type_signal_escape ChatInput::signal_escape() { return m_signal_escape; } + +ChatInput::type_signal_check_permission ChatInput::signal_check_permission() { + return m_signal_check_permission; +} diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index ad7f0b1..d8484b8 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -1,9 +1,72 @@ #pragma once #include <gtkmm.h> +#include "discord/chatsubmitparams.hpp" +#include "discord/permissions.hpp" -class ChatInput : public Gtk::ScrolledWindow { +class ChatInputAttachmentItem : public Gtk::EventBox { public: - ChatInput(); + ChatInputAttachmentItem(const Glib::RefPtr<Gio::File> &file); + ChatInputAttachmentItem(const Glib::RefPtr<Gio::File> &file, const Glib::RefPtr<Gdk::Pixbuf> &pb, bool is_extant = false); + + [[nodiscard]] Glib::RefPtr<Gio::File> GetFile() const; + [[nodiscard]] ChatSubmitParams::AttachmentType GetType() const; + [[nodiscard]] std::string GetFilename() const; + [[nodiscard]] bool IsTemp() const noexcept; + void RemoveIfTemp(); + +private: + void SetFilenameFromFile(); + void SetupMenu(); + void UpdateTooltip(); + + Gtk::Menu m_menu; + Gtk::MenuItem m_menu_remove; + Gtk::MenuItem m_menu_set_filename; + + Gtk::Box m_box; + Gtk::Label m_label; + Gtk::Image *m_img = nullptr; + + Glib::RefPtr<Gio::File> m_file; + ChatSubmitParams::AttachmentType m_type; + std::string m_filename; + +private: + using type_signal_item_removed = sigc::signal<void>; + + type_signal_item_removed m_signal_item_removed; + +public: + type_signal_item_removed signal_item_removed(); +}; + +class ChatInputAttachmentContainer : public Gtk::ScrolledWindow { +public: + ChatInputAttachmentContainer(); + + void Clear(); + void ClearNoPurge(); + bool AddImage(const Glib::RefPtr<Gdk::Pixbuf> &pb); + bool AddFile(const Glib::RefPtr<Gio::File> &file, Glib::RefPtr<Gdk::Pixbuf> pb = {}); + [[nodiscard]] std::vector<ChatSubmitParams::Attachment> GetAttachments() const; + +private: + std::vector<ChatInputAttachmentItem *> m_attachments; + + Gtk::Box m_box; + +private: + using type_signal_emptied = sigc::signal<void>; + + 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<Gtk::TextBuffer> GetBuffer(); @@ -15,14 +78,54 @@ protected: private: Gtk::TextView m_textview; + bool CheckHandleClipboardPaste(); + +public: + using type_signal_submit = sigc::signal<bool, Glib::ustring>; + using type_signal_escape = sigc::signal<void>; + using type_signal_image_paste = sigc::signal<void, Glib::RefPtr<Gdk::Pixbuf>>; + + 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<Gtk::TextBuffer> GetBuffer(); + bool ProcessKeyPress(GdkEventKey *event); + void AddAttachment(const Glib::RefPtr<Gio::File> &file); + void IndicateTooLarge(); + + void StartReplying(); + void StopReplying(); + +private: + bool AddFileAsImageAttachment(const Glib::RefPtr<Gio::File> &file); + + Gtk::Revealer m_attachments_revealer; + ChatInputAttachmentContainer m_attachments; + ChatInputText m_input; + public: - typedef sigc::signal<bool, Glib::ustring> type_signal_submit; - typedef sigc::signal<void> type_signal_escape; + using type_signal_submit = sigc::signal<bool, ChatSubmitParams>; + using type_signal_escape = sigc::signal<void>; + using type_signal_check_permission = sigc::signal<bool, Permission>; type_signal_submit signal_submit(); type_signal_escape signal_escape(); + type_signal_check_permission signal_check_permission(); private: type_signal_submit m_signal_submit; type_signal_escape m_signal_escape; + type_signal_check_permission m_signal_check_permission; }; diff --git a/src/components/chatlist.cpp b/src/components/chatlist.cpp index 5b923b5..d2995ee 100644 --- a/src/components/chatlist.cpp +++ b/src/components/chatlist.cpp @@ -34,6 +34,9 @@ void ChatList::Clear() { delete *it; it++; } + m_id_to_widget.clear(); + m_num_messages = 0; + m_num_rows = 0; } void ChatList::SetActiveChannel(Snowflake id) { @@ -352,10 +355,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<void, Snowflake, Snowflake>; - using type_signal_action_chat_submit = sigc::signal<void, std::string, Snowflake, Snowflake>; using type_signal_action_chat_load_history = sigc::signal<void, Snowflake>; using type_signal_action_channel_click = sigc::signal<void, Snowflake>; using type_signal_action_insert_mention = sigc::signal<void, Snowflake>; @@ -73,7 +72,6 @@ public: using type_signal_action_reply_to = sigc::signal<void, Snowflake>; 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/chatmessage.cpp b/src/components/chatmessage.cpp index 4d6eec5..863349f 100644 --- a/src/components/chatmessage.cpp +++ b/src/components/chatmessage.cpp @@ -1122,7 +1122,7 @@ ChatMessageHeader::ChatMessageHeader(const Message &data) m_meta_ev.signal_button_press_event().connect(sigc::mem_fun(*this, &ChatMessageHeader::on_author_button_press)); - if (author->IsBot || data.WebhookID.has_value()) { + if (author->IsABot() || data.WebhookID.has_value()) { m_extra = Gtk::manage(new Gtk::Label); m_extra->get_style_context()->add_class("message-container-extra"); m_extra->set_single_line_mode(true); @@ -1130,7 +1130,7 @@ ChatMessageHeader::ChatMessageHeader(const Message &data) m_extra->set_can_focus(false); m_extra->set_use_markup(true); } - if (author->IsBot) + if (author->IsABot()) m_extra->set_markup("<b>BOT</b>"); else if (data.WebhookID.has_value()) m_extra->set_markup("<b>Webhook</b>"); diff --git a/src/components/chatwindow.cpp b/src/components/chatwindow.cpp index 5aab4e6..b68ceba 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -4,12 +4,14 @@ #include "ratelimitindicator.hpp" #include "chatinput.hpp" #include "chatlist.hpp" +#include "constants.hpp" #ifdef WITH_LIBHANDY #include "channeltabswitcherhandy.hpp" #endif ChatWindow::ChatWindow() { - Abaddon::Get().GetDiscordClient().signal_message_send_fail().connect(sigc::mem_fun(*this, &ChatWindow::OnMessageSendFail)); + auto &discord = Abaddon::Get().GetDiscordClient(); + discord.signal_message_send_fail().connect(sigc::mem_fun(*this, &ChatWindow::OnMessageSendFail)); m_main = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); m_chat = Gtk::manage(new ChatList); @@ -45,6 +47,11 @@ ChatWindow::ChatWindow() { m_topic_text.set_halign(Gtk::ALIGN_START); m_topic_text.show(); + m_input->set_valign(Gtk::ALIGN_END); + + m_input->signal_check_permission().connect([this](Permission perm) { + return Abaddon::Get().GetDiscordClient().HasSelfChannelPermission(m_active_channel, perm); + }); m_input->signal_submit().connect(sigc::mem_fun(*this, &ChatWindow::OnInputSubmit)); m_input->signal_escape().connect([this]() { if (m_is_replying) @@ -54,11 +61,11 @@ ChatWindow::ChatWindow() { m_input->show(); m_completer.SetBuffer(m_input->GetBuffer()); - m_completer.SetGetChannelID([this]() -> auto { + m_completer.SetGetChannelID([this]() { return m_active_channel; }); - m_completer.SetGetRecentAuthors([this]() -> auto { + m_completer.SetGetRecentAuthors([this]() { return m_chat->GetRecentAuthors(); }); @@ -70,9 +77,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); @@ -107,6 +111,10 @@ ChatWindow::ChatWindow() { m_main->add(m_completer); m_main->add(*m_input); m_main->add(*m_meta); + m_main->add(m_progress); + + m_progress.show(); + m_main->show(); } @@ -168,6 +176,10 @@ void ChatWindow::SetTopic(const std::string &text) { m_topic.set_visible(text.length() > 0); } +void ChatWindow::AddAttachment(const Glib::RefPtr<Gio::File> &file) { + m_input->AddAttachment(file); +} + #ifdef WITH_LIBHANDY void ChatWindow::OpenNewTab(Snowflake id) { // open if its the first tab (in which case it really isnt a tab but whatever) @@ -210,15 +222,62 @@ Snowflake ChatWindow::GetActiveChannel() const { return m_active_channel; } -bool ChatWindow::OnInputSubmit(const Glib::ustring &text) { +bool ChatWindow::OnInputSubmit(ChatSubmitParams data) { + auto &discord = Abaddon::Get().GetDiscordClient(); + + int nitro_restriction = BaseAttachmentSizeLimit; + const auto nitro = discord.GetUserData().PremiumType; + if (!nitro.has_value() || nitro == EPremiumType::None) { + nitro_restriction = BaseAttachmentSizeLimit; + } else if (nitro == EPremiumType::NitroClassic) { + nitro_restriction = NitroClassicAttachmentSizeLimit; + } else if (nitro == EPremiumType::Nitro) { + nitro_restriction = NitroAttachmentSizeLimit; + } + + int guild_restriction = BaseAttachmentSizeLimit; + if (const auto channel = discord.GetChannel(m_active_channel); channel.has_value() && channel->GuildID.has_value()) { + if (const auto guild = discord.GetGuild(*channel->GuildID); guild.has_value()) { + if (!guild->PremiumTier.has_value() || guild->PremiumTier == GuildPremiumTier::NONE || guild->PremiumTier == GuildPremiumTier::TIER_1) { + guild_restriction = BaseAttachmentSizeLimit; + } else if (guild->PremiumTier == GuildPremiumTier::TIER_2) { + guild_restriction = BoostLevel2AttachmentSizeLimit; + } else if (guild->PremiumTier == GuildPremiumTier::TIER_3) { + guild_restriction = BoostLevel3AttachmentSizeLimit; + } + } + } + + int restriction = std::max(nitro_restriction, guild_restriction); + + goffset total_size = 0; + for (const auto &attachment : data.Attachments) { + const auto info = attachment.File->query_info(); + if (info) { + const auto size = info->get_size(); + if (size > restriction) { + m_input->IndicateTooLarge(); + return false; + } + total_size += size; + if (total_size > MaxMessagePayloadSize) { + m_input->IndicateTooLarge(); + return false; + } + } + } + if (!m_rate_limit_indicator->CanSpeak()) return false; - if (text.empty()) + if (data.Message.empty() && data.Attachments.empty()) return false; + data.ChannelID = m_active_channel; + data.InReplyToID = m_replying_to; + 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(data); // m_replying_to is checked for invalid in the handler if (m_is_replying) StopReplying(); @@ -241,8 +300,7 @@ void ChatWindow::StartReplying(Snowflake message_id) { const auto author = discord.GetUser(message.Author.ID); m_replying_to = message_id; m_is_replying = true; - m_input->grab_focus(); - m_input->get_style_context()->add_class("replying"); + m_input->StartReplying(); if (author.has_value()) m_input_indicator->SetCustomMarkup("Replying to " + author->GetEscapedBoldString<false>()); else @@ -252,7 +310,7 @@ void ChatWindow::StartReplying(Snowflake message_id) { void ChatWindow::StopReplying() { m_is_replying = false; m_replying_to = Snowflake::Invalid; - m_input->get_style_context()->remove_class("replying"); + m_input->StopReplying(); m_input_indicator->ClearCustom(); } diff --git a/src/components/chatwindow.hpp b/src/components/chatwindow.hpp index 9b27ff1..802826b 100644 --- a/src/components/chatwindow.hpp +++ b/src/components/chatwindow.hpp @@ -3,8 +3,10 @@ #include <string> #include <set> #include "discord/discord.hpp" +#include "discord/chatsubmitparams.hpp" #include "completer.hpp" #include "state.hpp" +#include "progressbar.hpp" #ifdef WITH_LIBHANDY class ChannelTabSwitcherHandy; @@ -34,6 +36,7 @@ public: Snowflake GetOldestListedMessage(); // oldest message that is currently in the ListBox void UpdateReactions(Snowflake id); void SetTopic(const std::string &text); + void AddAttachment(const Glib::RefPtr<Gio::File> &file); #ifdef WITH_LIBHANDY void OpenNewTab(Snowflake id); @@ -55,7 +58,7 @@ protected: Snowflake m_active_channel; - bool OnInputSubmit(const Glib::ustring &text); + bool OnInputSubmit(ChatSubmitParams data); bool OnKeyPressEvent(GdkEventKey *e); void OnScrollEdgeOvershot(Gtk::PositionType pos); @@ -77,6 +80,7 @@ protected: ChatInputIndicator *m_input_indicator; RateLimitIndicator *m_rate_limit_indicator; Gtk::Box *m_meta; + MessageUploadProgressBar m_progress; #ifdef WITH_LIBHANDY ChannelTabSwitcherHandy *m_tab_switcher; @@ -84,7 +88,7 @@ protected: public: using type_signal_action_message_edit = sigc::signal<void, Snowflake, Snowflake>; - using type_signal_action_chat_submit = sigc::signal<void, std::string, Snowflake, Snowflake>; + using type_signal_action_chat_submit = sigc::signal<void, ChatSubmitParams>; using type_signal_action_chat_load_history = sigc::signal<void, Snowflake>; using type_signal_action_channel_click = sigc::signal<void, Snowflake, bool>; using type_signal_action_insert_mention = sigc::signal<void, Snowflake>; diff --git a/src/components/progressbar.cpp b/src/components/progressbar.cpp new file mode 100644 index 0000000..aa5d748 --- /dev/null +++ b/src/components/progressbar.cpp @@ -0,0 +1,24 @@ +#include "progressbar.hpp" +#include "abaddon.hpp" + +MessageUploadProgressBar::MessageUploadProgressBar() { + get_style_context()->add_class("message-progress"); + auto &discord = Abaddon::Get().GetDiscordClient(); + discord.signal_message_progress().connect([this](const std::string &nonce, float percent) { + if (nonce == m_last_nonce) { + set_fraction(percent); + } + }); + discord.signal_message_send_fail().connect([this](const std::string &nonce, float) { + if (nonce == m_last_nonce) + set_fraction(0.0); + }); + discord.signal_message_create().connect([this](const Message &msg) { + if (msg.IsPending) { + m_last_nonce = *msg.Nonce; + } else if (msg.Nonce.has_value() && (*msg.Nonce == m_last_nonce)) { + m_last_nonce = ""; + set_fraction(0.0); + } + }); +} diff --git a/src/components/progressbar.hpp b/src/components/progressbar.hpp new file mode 100644 index 0000000..b73521b --- /dev/null +++ b/src/components/progressbar.hpp @@ -0,0 +1,11 @@ +#pragma once +#include <gtkmm/progressbar.h> +#include <string> + +class MessageUploadProgressBar : public Gtk::ProgressBar { +public: + MessageUploadProgressBar(); + +private: + std::string m_last_nonce; +}; |