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.hpp | 89 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 85 insertions(+), 4 deletions(-) (limited to 'src/components/chatinput.hpp') 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(); -- cgit v1.2.3 From c6182e8923026108957227b41249294abbd85f79 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 6 Jun 2022 03:55:25 -0400 Subject: only save temporary image when theres room in container --- src/components/chatinput.cpp | 45 +++++++++++++++++++++++--------------------- src/components/chatinput.hpp | 5 ++--- 2 files changed, 26 insertions(+), 24 deletions(-) (limited to 'src/components/chatinput.hpp') diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 6afdd82..94bca22 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -70,27 +70,13 @@ bool ChatInputText::CheckHandleClipboardPaste() { 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(); + if (pb) { + m_signal_image_paste.emit(pb); - try { - pb->save(dest_path, "png"); - } catch (...) { - fprintf(stderr, "pasted image save error\n"); return true; + } else { + return false; } - - m_signal_image_paste.emit(pb, dest_path); - - return true; } ChatInputText::type_signal_submit ChatInputText::signal_submit() { @@ -123,9 +109,26 @@ void ChatInputAttachmentContainer::Clear() { m_attachments.clear(); } -bool ChatInputAttachmentContainer::AddImage(const Glib::RefPtr &pb, const std::string &path) { +bool ChatInputAttachmentContainer::AddImage(const Glib::RefPtr &pb) { if (m_attachments.size() == 10) return false; + std::array dest_name {}; + if (std::tmpnam(dest_name.data()) == nullptr) { + fprintf(stderr, "failed to get temporary path\n"); + return false; + } + + std::filesystem::path part1(std::filesystem::temp_directory_path() / "abaddon-cache"); + std::filesystem::path part2(dest_name.data()); + const auto path = (part1 / part2.relative_path()).string(); + + try { + pb->save(path, "png"); + } catch (...) { + fprintf(stderr, "pasted image save error\n"); + return false; + } + auto *item = Gtk::make_managed(path, pb); item->show(); item->set_valign(Gtk::ALIGN_CENTER); @@ -223,8 +226,8 @@ ChatInput::ChatInput() 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_input.signal_image_paste().connect([this](const Glib::RefPtr &pb) { + if (m_attachments.AddImage(pb)) m_attachments_revealer.set_reveal_child(true); }); diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index a6b2a31..83fde95 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -32,7 +32,7 @@ public: ChatInputAttachmentContainer(); void Clear(); - bool AddImage(const Glib::RefPtr &pb, const std::string &path); + bool AddImage(const Glib::RefPtr &pb); [[nodiscard]] std::vector GetFilePaths() const; private: @@ -64,12 +64,11 @@ 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>; + using type_signal_image_paste = sigc::signal>; type_signal_submit signal_submit(); type_signal_escape signal_escape(); -- cgit v1.2.3 From 49ff9a249e4fbc6354810fb56a7488deeacd8cd2 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 9 Jun 2022 01:48:24 -0400 Subject: remove temp attachment files when theyre actually done being uploaded --- src/components/chatinput.cpp | 17 +++++++++++++++-- src/components/chatinput.hpp | 5 +++++ src/components/chatwindow.cpp | 3 +++ src/discord/discord.cpp | 12 +++++++++++- src/discord/discord.hpp | 1 + 5 files changed, 35 insertions(+), 3 deletions(-) (limited to 'src/components/chatinput.hpp') diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 7597bbe..166d04f 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -112,6 +112,13 @@ void ChatInputAttachmentContainer::Clear() { m_attachments.clear(); } +void ChatInputAttachmentContainer::ClearNoPurge() { + for (auto *item : m_attachments) { + delete item; + } + m_attachments.clear(); +} + bool ChatInputAttachmentContainer::AddImage(const Glib::RefPtr &pb) { if (m_attachments.size() == 10) return false; @@ -217,8 +224,8 @@ ChatInput::ChatInput() 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); + m_attachments.ClearNoPurge(); } return b; }); @@ -232,7 +239,9 @@ ChatInput::ChatInput() show_all_children(); m_input.signal_image_paste().connect([this](const Glib::RefPtr &pb) { - if (m_attachments.AddImage(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); }); @@ -266,3 +275,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 83fde95..bdbac5c 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include "discord/permissions.hpp" class ChatInputAttachmentItem : public Gtk::EventBox { public: @@ -32,6 +33,7 @@ public: ChatInputAttachmentContainer(); void Clear(); + void ClearNoPurge(); bool AddImage(const Glib::RefPtr &pb); [[nodiscard]] std::vector GetFilePaths() const; @@ -98,11 +100,14 @@ public: // 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; + using type_signal_check_permission = sigc::signal; 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/chatwindow.cpp b/src/components/chatwindow.cpp index 46c34d4..52c2a60 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -47,6 +47,9 @@ ChatWindow::ChatWindow() { 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) diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 151d88c..ffc1d4d 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -318,6 +318,10 @@ bool DiscordClient::HasGuildPermission(Snowflake user_id, Snowflake guild_id, Pe return (base & perm) == perm; } +bool DiscordClient::HasSelfChannelPermission(Snowflake channel_id, Permission perm) const { + return HasChannelPermission(m_user_data.ID, channel_id, perm); +} + bool DiscordClient::HasAnyChannelPermission(Snowflake user_id, Snowflake channel_id, Permission perm) const { const auto channel = m_store.GetChannel(channel_id); if (!channel.has_value() || !channel->GuildID.has_value()) return false; @@ -439,7 +443,13 @@ void DiscordClient::SendChatMessageAttachments(const std::string &content, const const auto field_name = "files[" + std::to_string(i) + "]"; req.add_file(field_name, attachment_paths.at(i), "unknown.png"); } - m_http.Execute(std::move(req), sigc::bind<0>(sigc::mem_fun(*this, &DiscordClient::ChatMessageCallback), nonce)); + m_http.Execute(std::move(req), [this, attachment_paths, nonce](const http::response_type &res) { + for (const auto &path : attachment_paths) { + std::error_code ec; + std::filesystem::remove(path, ec); + } + ChatMessageCallback(nonce, res); + }); } void DiscordClient::SendChatMessageText(const std::string &content, Snowflake channel, Snowflake referenced_message) { diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index fdb5a5f..9322e3f 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -96,6 +96,7 @@ public: bool IsThreadJoined(Snowflake thread_id) const; bool HasGuildPermission(Snowflake user_id, Snowflake guild_id, Permission perm) const; + bool HasSelfChannelPermission(Snowflake channel_id, Permission perm) const; bool HasAnyChannelPermission(Snowflake user_id, Snowflake channel_id, Permission perm) const; bool HasChannelPermission(Snowflake user_id, Snowflake channel_id, Permission perm) const; Permission ComputePermissions(Snowflake member_id, Snowflake guild_id) const; -- cgit v1.2.3 From 4456c8771da668cbadb411583624fe0e357fa687 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 14 Jun 2022 02:36:04 -0400 Subject: refactor send message params into one struct --- src/abaddon.cpp | 23 ++++++---- src/abaddon.hpp | 2 +- src/components/chatinput.cpp | 20 +++++--- src/components/chatinput.hpp | 9 ++-- src/components/chatwindow.cpp | 9 ++-- src/components/chatwindow.hpp | 5 +- src/discord/chatsubmitparams.hpp | 22 +++++++++ src/discord/discord.cpp | 99 +++++++++++++++++++++------------------- src/discord/discord.hpp | 11 ++--- 9 files changed, 122 insertions(+), 78 deletions(-) create mode 100644 src/discord/chatsubmitparams.hpp (limited to 'src/components/chatinput.hpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 05e1942..28b6262 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -743,17 +743,22 @@ void Abaddon::ActionChatLoadHistory(Snowflake id) { }); } -void Abaddon::ActionChatInputSubmit(std::string msg, const std::vector &attachment_paths, Snowflake channel, Snowflake referenced_message) { - if (msg.substr(0, 7) == "/shrug " || msg == "/shrug") - msg = msg.substr(6) + "\xC2\xAF\x5C\x5F\x28\xE3\x83\x84\x29\x5F\x2F\xC2\xAF"; // this is important +static void ChatMessageSentCallback(const ChatSubmitParams &data) { + printf("completed for %s\n", data.Message.c_str()); + for (const auto &attachment : data.Attachments) { + puts(attachment.Path.c_str()); + } +} - if (!channel.IsValid()) return; - if (!m_discord.HasChannelPermission(m_discord.GetUserData().ID, channel, Permission::VIEW_CHANNEL)) return; +void Abaddon::ActionChatInputSubmit(ChatSubmitParams data) { + if (data.Message.substr(0, 7) == "/shrug " || data.Message == "/shrug") + data.Message = data.Message.substr(6) + "\xC2\xAF\x5C\x5F\x28\xE3\x83\x84\x29\x5F\x2F\xC2\xAF"; // this is important - if (referenced_message.IsValid()) - m_discord.SendChatMessage(msg, attachment_paths, channel, referenced_message); - else - m_discord.SendChatMessage(msg, attachment_paths, channel); + if (!m_discord.HasChannelPermission(m_discord.GetUserData().ID, data.ChannelID, Permission::VIEW_CHANNEL)) return; + + m_discord.SendChatMessage(data, [data](DiscordError code) { + ChatMessageSentCallback(data); + }); } void Abaddon::ActionChatEditMessage(Snowflake channel_id, Snowflake id) { diff --git a/src/abaddon.hpp b/src/abaddon.hpp index 9efb891..1ba1251 100644 --- a/src/abaddon.hpp +++ b/src/abaddon.hpp @@ -36,7 +36,7 @@ public: void ActionSetToken(); void ActionJoinGuildDialog(); void ActionChannelOpened(Snowflake id, bool expand_to = true); - void ActionChatInputSubmit(std::string msg, const std::vector &attachment_paths, Snowflake channel, Snowflake referenced_message); + void ActionChatInputSubmit(ChatSubmitParams data); void ActionChatLoadHistory(Snowflake id); void ActionChatEditMessage(Snowflake channel_id, Snowflake id); void ActionInsertMention(Snowflake id); diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index f9bbce4..2b8b5a5 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -152,10 +152,10 @@ bool ChatInputAttachmentContainer::AddImage(const Glib::RefPtr &pb) return true; } -std::vector ChatInputAttachmentContainer::GetFilePaths() const { - std::vector ret; +std::vector ChatInputAttachmentContainer::GetAttachments() const { + std::vector ret; for (auto *x : m_attachments) - ret.push_back(x->GetPath()); + ret.push_back({ x->GetPath(), x->GetType() }); return ret; } @@ -165,7 +165,8 @@ ChatInputAttachmentContainer::type_signal_emptied ChatInputAttachmentContainer:: ChatInputAttachmentItem::ChatInputAttachmentItem(std::string path, const Glib::RefPtr &pb) : m_path(std::move(path)) - , m_img(Gtk::make_managed()) { + , m_img(Gtk::make_managed()) + , m_type(ChatSubmitParams::PastedImage) { get_style_context()->add_class("attachment-item"); int outw, outh; @@ -184,6 +185,10 @@ std::string ChatInputAttachmentItem::GetPath() const { return m_path; } +ChatSubmitParams::AttachmentType ChatInputAttachmentItem::GetType() const { + return m_type; +} + void ChatInputAttachmentItem::SetupMenu() { m_menu_remove.set_label("Remove"); m_menu_remove.signal_activate().connect([this] { @@ -215,8 +220,11 @@ ChatInput::ChatInput() 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); + 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(); diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index bdbac5c..ba3ab36 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include "discord/chatsubmitparams.hpp" #include "discord/permissions.hpp" class ChatInputAttachmentItem : public Gtk::EventBox { @@ -7,6 +8,7 @@ public: ChatInputAttachmentItem(std::string path, const Glib::RefPtr &pb); [[nodiscard]] std::string GetPath() const; + [[nodiscard]] ChatSubmitParams::AttachmentType GetType() const; private: void SetupMenu(); @@ -18,6 +20,7 @@ private: Gtk::Image *m_img = nullptr; std::string m_path; + ChatSubmitParams::AttachmentType m_type; private: using type_signal_remove = sigc::signal; @@ -35,7 +38,7 @@ public: void Clear(); void ClearNoPurge(); bool AddImage(const Glib::RefPtr &pb); - [[nodiscard]] std::vector GetFilePaths() const; + [[nodiscard]] std::vector GetAttachments() const; private: std::set m_attachments; @@ -96,9 +99,7 @@ private: ChatInputText m_input; public: - // 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_submit = sigc::signal; using type_signal_escape = sigc::signal; using type_signal_check_permission = sigc::signal; diff --git a/src/components/chatwindow.cpp b/src/components/chatwindow.cpp index 52c2a60..3e1885b 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -212,15 +212,18 @@ Snowflake ChatWindow::GetActiveChannel() const { return m_active_channel; } -bool ChatWindow::OnInputSubmit(const Glib::ustring &text, const std::vector &attachment_paths) { +bool ChatWindow::OnInputSubmit(ChatSubmitParams data) { if (!m_rate_limit_indicator->CanSpeak()) return false; - if (text.empty() && attachment_paths.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, attachment_paths, 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(); diff --git a/src/components/chatwindow.hpp b/src/components/chatwindow.hpp index ab0bee1..ecbf666 100644 --- a/src/components/chatwindow.hpp +++ b/src/components/chatwindow.hpp @@ -3,6 +3,7 @@ #include #include #include "discord/discord.hpp" +#include "discord/chatsubmitparams.hpp" #include "completer.hpp" #include "state.hpp" @@ -55,7 +56,7 @@ protected: Snowflake m_active_channel; - bool OnInputSubmit(const Glib::ustring &text, const std::vector &attachment_paths); + bool OnInputSubmit(ChatSubmitParams data); bool OnKeyPressEvent(GdkEventKey *e); void OnScrollEdgeOvershot(Gtk::PositionType pos); @@ -84,7 +85,7 @@ protected: public: using type_signal_action_message_edit = sigc::signal; - using type_signal_action_chat_submit = sigc::signal, Snowflake, Snowflake>; + 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; diff --git a/src/discord/chatsubmitparams.hpp b/src/discord/chatsubmitparams.hpp new file mode 100644 index 0000000..0f0a0cd --- /dev/null +++ b/src/discord/chatsubmitparams.hpp @@ -0,0 +1,22 @@ +#pragma once +#include +#include +#include +#include "discord/snowflake.hpp" + +struct ChatSubmitParams { + enum AttachmentType { + PastedImage, + ExtantFile, + }; + + struct Attachment { + std::string Path; + AttachmentType Type; + }; + + Snowflake ChannelID; + Snowflake InReplyToID; + Glib::ustring Message; + std::vector Attachments; +}; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index ffc1d4d..9849ae6 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -413,7 +413,7 @@ bool DiscordClient::CanManageMember(Snowflake guild_id, Snowflake actor, Snowfla return actor_highest->Position > target_highest->Position; } -void DiscordClient::ChatMessageCallback(const std::string &nonce, const http::response_type &response) { +void DiscordClient::ChatMessageCallback(const std::string &nonce, const http::response_type &response, const sigc::slot &callback) { if (!CheckCode(response)) { if (response.status_code == http::TooManyRequests) { try { // not sure if this body is guaranteed @@ -425,75 +425,79 @@ void DiscordClient::ChatMessageCallback(const std::string &nonce, const http::re } else { m_signal_message_send_fail.emit(nonce, 0); } + + // todo actually callback with correct error code (not necessary rn) + callback(DiscordError::GENERIC); + } else { + callback(DiscordError::NONE); } } -void DiscordClient::SendChatMessageAttachments(const std::string &content, const std::vector &attachment_paths, Snowflake channel, Snowflake referenced_message) { +void DiscordClient::SendChatMessageNoAttachments(const ChatSubmitParams ¶ms, const sigc::slot &callback) { const auto nonce = std::to_string(Snowflake::FromNow()); + CreateMessageObject obj; - obj.Content = content; + obj.Content = params.Message; obj.Nonce = nonce; - if (referenced_message.IsValid()) - obj.MessageReference.emplace().MessageID = referenced_message; + if (params.InReplyToID.IsValid()) + obj.MessageReference.emplace().MessageID = params.InReplyToID; - auto req = m_http.CreateRequest(http::REQUEST_POST, "/channels/" + std::to_string(channel) + "/messages"); - req.make_form(); - req.add_field("payload_json", nlohmann::json(obj).dump().c_str(), CURL_ZERO_TERMINATED); - for (size_t i = 0; i < attachment_paths.size(); i++) { - const auto field_name = "files[" + std::to_string(i) + "]"; - req.add_file(field_name, attachment_paths.at(i), "unknown.png"); - } - m_http.Execute(std::move(req), [this, attachment_paths, nonce](const http::response_type &res) { - for (const auto &path : attachment_paths) { - std::error_code ec; - std::filesystem::remove(path, ec); - } - ChatMessageCallback(nonce, res); - }); -} + m_http.MakePOST("/channels/" + std::to_string(params.ChannelID) + "/messages", + nlohmann::json(obj).dump(), + [this, nonce, callback](const http::response_type &r) { + ChatMessageCallback(nonce, r, callback); + }); -void DiscordClient::SendChatMessageText(const std::string &content, Snowflake channel, Snowflake referenced_message) { - // @([^@#]{1,32})#(\\d{4}) - const auto nonce = std::to_string(Snowflake::FromNow()); - CreateMessageObject obj; - obj.Content = content; - obj.Nonce = nonce; - if (referenced_message.IsValid()) - obj.MessageReference.emplace().MessageID = referenced_message; - m_http.MakePOST("/channels/" + std::to_string(channel) + "/messages", nlohmann::json(obj).dump(), sigc::bind<0>(sigc::mem_fun(*this, &DiscordClient::ChatMessageCallback), nonce)); - // dummy data so the content can be shown while waiting for MESSAGE_CREATE + // dummy preview data Message tmp; - tmp.Content = content; + tmp.Content = params.Message; tmp.ID = nonce; - tmp.ChannelID = channel; + tmp.ChannelID = params.ChannelID; tmp.Author = GetUserData(); tmp.IsTTS = false; tmp.DoesMentionEveryone = false; tmp.Type = MessageType::DEFAULT; tmp.IsPinned = false; tmp.Timestamp = "2000-01-01T00:00:00.000000+00:00"; - tmp.Nonce = obj.Nonce; + tmp.Nonce = nonce; tmp.IsPending = true; + m_store.SetMessage(tmp.ID, tmp); - m_signal_message_sent.emit(tmp); + m_signal_message_create.emit(tmp); } -void DiscordClient::SendChatMessage(const std::string &content, const std::vector &attachment_paths, Snowflake channel) { - if (attachment_paths.empty()) - SendChatMessageText(content, channel); - else { - puts("attach"); - SendChatMessageAttachments(content, attachment_paths, channel, Snowflake::Invalid); +void DiscordClient::SendChatMessageAttachments(const ChatSubmitParams ¶ms, const sigc::slot &callback) { + const auto nonce = std::to_string(Snowflake::FromNow()); + + CreateMessageObject obj; + obj.Content = params.Message; + obj.Nonce = nonce; + if (params.InReplyToID.IsValid()) + obj.MessageReference.emplace().MessageID = params.InReplyToID; + + auto req = m_http.CreateRequest(http::REQUEST_POST, "/channels/" + std::to_string(params.ChannelID) + "/messages"); + req.make_form(); + req.add_field("payload_json", nlohmann::json(obj).dump().c_str(), CURL_ZERO_TERMINATED); + for (size_t i = 0; i < params.Attachments.size(); i++) { + const auto field_name = "files[" + std::to_string(i) + "]"; + req.add_file(field_name, params.Attachments.at(i).Path, "unknown.png"); } + m_http.Execute(std::move(req), [this, params, nonce, callback](const http::response_type &res) { + for (const auto &attachment : params.Attachments) { + if (attachment.Type == ChatSubmitParams::AttachmentType::PastedImage) { + std::error_code ec; + std::filesystem::remove(attachment.Path, ec); + } + } + ChatMessageCallback(nonce, res, callback); + }); } -void DiscordClient::SendChatMessage(const std::string &content, const std::vector &attachment_paths, Snowflake channel, Snowflake referenced_message) { - if (attachment_paths.empty()) - SendChatMessageText(content, channel, referenced_message); - else { - puts("attach"); - SendChatMessageAttachments(content, attachment_paths, channel, referenced_message); - } +void DiscordClient::SendChatMessage(const ChatSubmitParams ¶ms, const sigc::slot &callback) { + if (params.Attachments.empty()) + SendChatMessageNoAttachments(params, callback); + else + SendChatMessageAttachments(params, callback); } void DiscordClient::DeleteMessage(Snowflake channel_id, Snowflake id) { @@ -1302,6 +1306,7 @@ void DiscordClient::HandleGatewayMessage(std::string str) { HandleGatewayInvalidSession(m); } break; case GatewayOp::Dispatch: { + puts(m.Type.c_str()); auto iter = m_event_map.find(m.Type); if (iter == m_event_map.end()) { printf("Unknown event %s\n", m.Type.c_str()); diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 9322e3f..41d5aab 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -3,6 +3,7 @@ #include "httpclient.hpp" #include "objects.hpp" #include "store.hpp" +#include "chatsubmitparams.hpp" #include #include #include @@ -103,10 +104,11 @@ public: Permission ComputeOverwrites(Permission base, Snowflake member_id, Snowflake channel_id) const; bool CanManageMember(Snowflake guild_id, Snowflake actor, Snowflake target) const; // kick, ban, edit nickname (cant think of a better name) - void ChatMessageCallback(const std::string &nonce, const http::response_type &response); + void ChatMessageCallback(const std::string &nonce, const http::response_type &response, const sigc::slot &callback); + void SendChatMessageNoAttachments(const ChatSubmitParams ¶ms, const sigc::slot &callback); + void SendChatMessageAttachments(const ChatSubmitParams ¶ms, const sigc::slot &callback); - void SendChatMessage(const std::string &content, const std::vector &attachment_paths, Snowflake channel); - void SendChatMessage(const std::string &content, const std::vector &attachment_paths, Snowflake channel, Snowflake referenced_message); + void SendChatMessage(const ChatSubmitParams ¶ms, const sigc::slot &callback); void DeleteMessage(Snowflake channel_id, Snowflake id); void EditMessage(Snowflake channel_id, Snowflake id, std::string content); void SendLazyLoad(Snowflake id); @@ -224,9 +226,6 @@ private: std::vector m_decompress_buf; z_stream m_zstream; - void SendChatMessageAttachments(const std::string &content, const std::vector &attachment_paths, Snowflake channel, Snowflake referenced_message = Snowflake::Invalid); - void SendChatMessageText(const std::string &content, Snowflake channel, Snowflake referenced_message = Snowflake::Invalid); - static std::string GetAPIURL(); static std::string GetGatewayURL(); -- cgit v1.2.3 From d0fa308f6e339b94044d39bf0e76b8221da48c3a Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 16 Jun 2022 01:09:54 -0400 Subject: preserve attachment insertion order --- src/components/chatinput.cpp | 5 +++-- src/components/chatinput.hpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'src/components/chatinput.hpp') diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 2b8b5a5..5a6e71e 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -138,12 +138,13 @@ bool ChatInputAttachmentContainer::AddImage(const Glib::RefPtr &pb) item->set_valign(Gtk::ALIGN_CENTER); m_box.add(*item); - m_attachments.insert(item); + m_attachments.push_back(item); item->signal_remove().connect([this, item] { std::error_code ec; std::filesystem::remove(item->GetPath(), ec); - m_attachments.erase(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(); diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index ba3ab36..254f96f 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -41,7 +41,7 @@ public: [[nodiscard]] std::vector GetAttachments() const; private: - std::set m_attachments; + std::vector m_attachments; Gtk::Box m_box; -- cgit v1.2.3 From 4ee7025ab09b606a2556bf9f42c1218d7fd72843 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 17 Jun 2022 02:46:55 -0400 Subject: add file upload via dnd + rework http --- src/abaddon.cpp | 2 +- src/components/chatinput.cpp | 92 +++++++++++++++++++++++++++++++++------- src/components/chatinput.hpp | 19 ++++++--- src/components/chatwindow.cpp | 4 ++ src/components/chatwindow.hpp | 1 + src/discord/chatsubmitparams.hpp | 4 +- src/discord/discord.cpp | 5 +-- src/http.cpp | 24 +++++++++-- src/http.hpp | 8 ++-- src/windows/mainwindow.cpp | 17 ++++++++ src/windows/mainwindow.hpp | 3 ++ 11 files changed, 146 insertions(+), 33 deletions(-) (limited to 'src/components/chatinput.hpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 28b6262..cfbf6b8 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -746,7 +746,7 @@ void Abaddon::ActionChatLoadHistory(Snowflake id) { static void ChatMessageSentCallback(const ChatSubmitParams &data) { printf("completed for %s\n", data.Message.c_str()); for (const auto &attachment : data.Attachments) { - puts(attachment.Path.c_str()); + puts(attachment.File->get_path().c_str()); } } diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 5a6e71e..269a2ac 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -105,8 +105,7 @@ ChatInputAttachmentContainer::ChatInputAttachmentContainer() void ChatInputAttachmentContainer::Clear() { for (auto *item : m_attachments) { - std::error_code ec; - std::filesystem::remove(item->GetPath(), ec); + item->RemoveIfTemp(); delete item; } m_attachments.clear(); @@ -133,16 +132,36 @@ bool ChatInputAttachmentContainer::AddImage(const Glib::RefPtr &pb) return false; } - auto *item = Gtk::make_managed(path, pb); + auto *item = Gtk::make_managed(Gio::File::create_for_path(path), pb); item->show(); item->set_valign(Gtk::ALIGN_CENTER); m_box.add(*item); m_attachments.push_back(item); - item->signal_remove().connect([this, item] { - std::error_code ec; - std::filesystem::remove(item->GetPath(), ec); + 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 &file) { + if (m_attachments.size() == 10) return false; + + auto *item = Gtk::make_managed(file); + item->show(); + item->set_valign(Gtk::ALIGN_CENTER); + 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; @@ -155,8 +174,11 @@ bool ChatInputAttachmentContainer::AddImage(const Glib::RefPtr &pb) std::vector ChatInputAttachmentContainer::GetAttachments() const { std::vector ret; - for (auto *x : m_attachments) - ret.push_back({ x->GetPath(), x->GetType() }); + for (auto *x : m_attachments) { + if (!x->GetFile()->query_exists()) + puts("bad!"); + ret.push_back({ x->GetFile(), x->GetType(), x->GetFilename() }); + } return ret; } @@ -164,10 +186,28 @@ ChatInputAttachmentContainer::type_signal_emptied ChatInputAttachmentContainer:: return m_signal_emptied; } -ChatInputAttachmentItem::ChatInputAttachmentItem(std::string path, const Glib::RefPtr &pb) - : m_path(std::move(path)) +ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr &file) + : m_file(file) + , m_img(Gtk::make_managed(Abaddon::Get().GetImageManager().GetPlaceholder(AttachmentItemSize))) + , m_type(ChatSubmitParams::ExtantFile) { + get_style_context()->add_class("attachment-item"); + + set_size_request(AttachmentItemSize, AttachmentItemSize); + m_box.add(*m_img); + add(m_box); + show_all_children(); + + SetupMenu(); + + auto info = m_file->query_info(G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME); + m_filename = info->get_attribute_string(G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME); +} + +ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr &file, const Glib::RefPtr &pb) + : m_file(file) , m_img(Gtk::make_managed()) - , m_type(ChatSubmitParams::PastedImage) { + , m_type(ChatSubmitParams::PastedImage) + , m_filename("unknown.png") { get_style_context()->add_class("attachment-item"); int outw, outh; @@ -182,18 +222,31 @@ ChatInputAttachmentItem::ChatInputAttachmentItem(std::string path, const Glib::R SetupMenu(); } -std::string ChatInputAttachmentItem::GetPath() const { - return m_path; +Glib::RefPtr 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::SetupMenu() { m_menu_remove.set_label("Remove"); m_menu_remove.signal_activate().connect([this] { - m_signal_remove.emit(); + m_signal_item_removed.emit(); }); m_menu.add(m_menu_remove); @@ -209,8 +262,8 @@ void ChatInputAttachmentItem::SetupMenu() { }); } -ChatInputAttachmentItem::type_signal_remove ChatInputAttachmentItem::signal_remove() { - return m_signal_remove; +ChatInputAttachmentItem::type_signal_item_removed ChatInputAttachmentItem::signal_item_removed() { + return m_signal_item_removed; } ChatInput::ChatInput() @@ -271,6 +324,13 @@ bool ChatInput::ProcessKeyPress(GdkEventKey *event) { return m_input.ProcessKeyPress(event); } +void ChatInput::AddAttachment(const Glib::RefPtr &file) { + const bool can_attach_files = m_signal_check_permission.emit(Permission::ATTACH_FILES); + + if (can_attach_files && m_attachments.AddFile(file)) + m_attachments_revealer.set_reveal_child(true); +} + ChatInput::type_signal_submit ChatInput::signal_submit() { return m_signal_submit; } diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index 254f96f..865b23e 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -5,10 +5,14 @@ class ChatInputAttachmentItem : public Gtk::EventBox { public: - ChatInputAttachmentItem(std::string path, const Glib::RefPtr &pb); + ChatInputAttachmentItem(const Glib::RefPtr &file); + ChatInputAttachmentItem(const Glib::RefPtr &file, const Glib::RefPtr &pb); - [[nodiscard]] std::string GetPath() const; + [[nodiscard]] Glib::RefPtr GetFile() const; [[nodiscard]] ChatSubmitParams::AttachmentType GetType() const; + [[nodiscard]] std::string GetFilename() const; + [[nodiscard]] bool IsTemp() const noexcept; + void RemoveIfTemp(); private: void SetupMenu(); @@ -19,16 +23,17 @@ private: Gtk::Box m_box; Gtk::Image *m_img = nullptr; - std::string m_path; + Glib::RefPtr m_file; ChatSubmitParams::AttachmentType m_type; + std::string m_filename; private: - using type_signal_remove = sigc::signal; + using type_signal_item_removed = sigc::signal; - type_signal_remove m_signal_remove; + type_signal_item_removed m_signal_item_removed; public: - type_signal_remove signal_remove(); + type_signal_item_removed signal_item_removed(); }; class ChatInputAttachmentContainer : public Gtk::ScrolledWindow { @@ -38,6 +43,7 @@ public: void Clear(); void ClearNoPurge(); bool AddImage(const Glib::RefPtr &pb); + bool AddFile(const Glib::RefPtr &file); [[nodiscard]] std::vector GetAttachments() const; private: @@ -92,6 +98,7 @@ public: void InsertText(const Glib::ustring &text); Glib::RefPtr GetBuffer(); bool ProcessKeyPress(GdkEventKey *event); + void AddAttachment(const Glib::RefPtr &file); private: Gtk::Revealer m_attachments_revealer; diff --git a/src/components/chatwindow.cpp b/src/components/chatwindow.cpp index 3e1885b..d9d490b 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -170,6 +170,10 @@ void ChatWindow::SetTopic(const std::string &text) { m_topic.set_visible(text.length() > 0); } +void ChatWindow::AddAttachment(const Glib::RefPtr &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) diff --git a/src/components/chatwindow.hpp b/src/components/chatwindow.hpp index ecbf666..917456b 100644 --- a/src/components/chatwindow.hpp +++ b/src/components/chatwindow.hpp @@ -35,6 +35,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 &file); #ifdef WITH_LIBHANDY void OpenNewTab(Snowflake id); diff --git a/src/discord/chatsubmitparams.hpp b/src/discord/chatsubmitparams.hpp index 0f0a0cd..6199634 100644 --- a/src/discord/chatsubmitparams.hpp +++ b/src/discord/chatsubmitparams.hpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "discord/snowflake.hpp" struct ChatSubmitParams { @@ -11,8 +12,9 @@ struct ChatSubmitParams { }; struct Attachment { - std::string Path; + Glib::RefPtr File; AttachmentType Type; + std::string Filename; }; Snowflake ChannelID; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 9849ae6..d26f1fe 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -480,13 +480,12 @@ void DiscordClient::SendChatMessageAttachments(const ChatSubmitParams ¶ms, c req.add_field("payload_json", nlohmann::json(obj).dump().c_str(), CURL_ZERO_TERMINATED); for (size_t i = 0; i < params.Attachments.size(); i++) { const auto field_name = "files[" + std::to_string(i) + "]"; - req.add_file(field_name, params.Attachments.at(i).Path, "unknown.png"); + req.add_file(field_name, params.Attachments.at(i).File, params.Attachments.at(i).Filename); } m_http.Execute(std::move(req), [this, params, nonce, callback](const http::response_type &res) { for (const auto &attachment : params.Attachments) { if (attachment.Type == ChatSubmitParams::AttachmentType::PastedImage) { - std::error_code ec; - std::filesystem::remove(attachment.Path, ec); + attachment.File->remove(); } } ChatMessageCallback(nonce, res, callback); diff --git a/src/http.cpp b/src/http.cpp index beb1944..14fe3f5 100644 --- a/src/http.cpp +++ b/src/http.cpp @@ -35,7 +35,8 @@ request::request(request &&other) noexcept , m_method(std::exchange(other.m_method, nullptr)) , m_header_list(std::exchange(other.m_header_list, nullptr)) , m_error_buf(other.m_error_buf) - , m_form(std::exchange(other.m_form, nullptr)) { + , m_form(std::exchange(other.m_form, nullptr)) + , m_read_streams(std::move(other.m_read_streams)) { // i think this is correct??? } @@ -82,12 +83,29 @@ void request::make_form() { m_form = curl_mime_init(m_curl); } +static size_t http_readfunc(char *buffer, size_t size, size_t nitems, void *arg) { + auto stream = Glib::wrap(G_FILE_INPUT_STREAM(arg), true); + int r = stream->read(buffer, size * nitems); + if (r == -1) { + // https://github.com/curl/curl/blob/ad9bc5976d6661cd5b03ebc379313bf657701c14/lib/mime.c#L724 + return size_t(-1); + } + return r; +} + // file must exist until request completes -void request::add_file(std::string_view name, std::string_view file_path, std::string_view filename) { +void request::add_file(std::string_view name, const Glib::RefPtr &file, std::string_view filename) { + if (!file->query_exists()) return; + auto *field = curl_mime_addpart(m_form); curl_mime_name(field, name.data()); - curl_mime_filedata(field, file_path.data()); + auto info = file->query_info(); + auto stream = file->read(); + curl_mime_data_cb(field, info->get_size(), http_readfunc, nullptr, nullptr, stream->gobj()); curl_mime_filename(field, filename.data()); + + // hold ref + m_read_streams.insert(stream); } // copied diff --git a/src/http.hpp b/src/http.hpp index 0c570db..a6f479d 100644 --- a/src/http.hpp +++ b/src/http.hpp @@ -1,9 +1,9 @@ #pragma once #include +#include #include #include - -// i regret not using snake case for everything oh well +#include namespace http { enum EStatusCode : int { @@ -115,7 +115,7 @@ struct request { void set_body(const std::string &data); void set_user_agent(const std::string &data); void make_form(); - void add_file(std::string_view name, std::string_view file_path, std::string_view filename); + void add_file(std::string_view name, const Glib::RefPtr &file, std::string_view filename); void add_field(std::string_view name, const char *data, size_t size); response execute(); @@ -129,6 +129,8 @@ private: curl_slist *m_header_list = nullptr; std::array m_error_buf = { 0 }; curl_mime *m_form = nullptr; + + std::set> m_read_streams; }; using response_type = response; diff --git a/src/windows/mainwindow.cpp b/src/windows/mainwindow.cpp index a6a17c5..17edfa3 100644 --- a/src/windows/mainwindow.cpp +++ b/src/windows/mainwindow.cpp @@ -76,6 +76,7 @@ MainWindow::MainWindow() add(m_main_box); SetupMenu(); + SetupDND(); } void MainWindow::UpdateComponents() { @@ -350,6 +351,22 @@ void MainWindow::SetupMenu() { #endif } +void MainWindow::SetupDND() { + std::vector targets; + targets.emplace_back("text/uri-list", Gtk::TargetFlags(0), 0); + drag_dest_set(targets, Gtk::DEST_DEFAULT_DROP | Gtk::DEST_DEFAULT_MOTION | Gtk::DEST_DEFAULT_HIGHLIGHT, Gdk::DragAction::ACTION_COPY); + signal_drag_data_received().connect([this](const Glib::RefPtr &ctx, int x, int y, const Gtk::SelectionData &selection, guint info, guint time) { + HandleDroppedURIs(selection); + }); +} + +void MainWindow::HandleDroppedURIs(const Gtk::SelectionData &selection) { + for (const auto &uri : selection.get_uris()) { + // not using Glib::get_filename_for_uri or whatever because the conversion is BAD (on windows at least) + m_chat.AddAttachment(Gio::File::create_for_uri(uri)); + } +} + MainWindow::type_signal_action_connect MainWindow::signal_action_connect() { return m_signal_action_connect; } diff --git a/src/windows/mainwindow.hpp b/src/windows/mainwindow.hpp index e67f6d2..b5b6fc1 100644 --- a/src/windows/mainwindow.hpp +++ b/src/windows/mainwindow.hpp @@ -39,6 +39,9 @@ public: private: void SetupMenu(); + void SetupDND(); + + void HandleDroppedURIs(const Gtk::SelectionData &selection); Gtk::Box m_main_box; Gtk::Box m_content_box; -- cgit v1.2.3 From d841a2c862eddc6b2053b48907f8e1400e2d1391 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 23 Jun 2022 00:48:00 -0400 Subject: add change filename --- src/abaddon.cpp | 10 ++++++++++ src/abaddon.hpp | 1 + src/components/chatinput.cpp | 15 +++++++++++++++ src/components/chatinput.hpp | 2 ++ src/dialogs/textinput.cpp | 26 ++++++++++++++++++++++++++ src/dialogs/textinput.hpp | 14 ++++++++++++++ 6 files changed, 68 insertions(+) create mode 100644 src/dialogs/textinput.cpp create mode 100644 src/dialogs/textinput.hpp (limited to 'src/components/chatinput.hpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index cfbf6b8..a34f444 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -11,6 +11,7 @@ #include "dialogs/setstatus.hpp" #include "dialogs/friendpicker.hpp" #include "dialogs/verificationgate.hpp" +#include "dialogs/textinput.hpp" #include "abaddon.hpp" #include "windows/guildsettingswindow.hpp" #include "windows/profilewindow.hpp" @@ -867,6 +868,15 @@ void Abaddon::ActionViewThreads(Snowflake channel_id) { window->show(); } +std::optional Abaddon::ShowTextPrompt(const Glib::ustring &prompt, const Glib::ustring &title, const Glib::ustring &placeholder, Gtk::Window *window) { + TextInputDialog dlg(prompt, title, placeholder, window != nullptr ? *window : *m_main_window); + const auto code = dlg.run(); + if (code == Gtk::RESPONSE_OK) + return dlg.GetInput(); + else + return {}; +} + bool Abaddon::ShowConfirm(const Glib::ustring &prompt, Gtk::Window *window) { ConfirmDialog dlg(window != nullptr ? *window : *m_main_window); dlg.SetConfirmText(prompt); diff --git a/src/abaddon.hpp b/src/abaddon.hpp index 1ba1251..c267269 100644 --- a/src/abaddon.hpp +++ b/src/abaddon.hpp @@ -51,6 +51,7 @@ public: void ActionViewPins(Snowflake channel_id); void ActionViewThreads(Snowflake channel_id); + std::optional ShowTextPrompt(const Glib::ustring &prompt, const Glib::ustring &title, const Glib::ustring &placeholder = "", Gtk::Window *window = nullptr); bool ShowConfirm(const Glib::ustring &prompt, Gtk::Window *window = nullptr); void ActionReloadCSS(); diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 269a2ac..a734dec 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -220,6 +220,7 @@ ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr & show_all_children(); SetupMenu(); + UpdateTooltip(); } Glib::RefPtr ChatInputAttachmentItem::GetFile() const { @@ -249,6 +250,16 @@ void ChatInputAttachmentItem::SetupMenu() { 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; + UpdateTooltip(); + } + }); + + m_menu.add(m_menu_set_filename); m_menu.add(m_menu_remove); m_menu.show_all(); @@ -262,6 +273,10 @@ void ChatInputAttachmentItem::SetupMenu() { }); } +void ChatInputAttachmentItem::UpdateTooltip() { + set_tooltip_text(m_filename); +} + ChatInputAttachmentItem::type_signal_item_removed ChatInputAttachmentItem::signal_item_removed() { return m_signal_item_removed; } diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index 865b23e..8a4ad8d 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -16,9 +16,11 @@ public: private: void SetupMenu(); + void UpdateTooltip(); Gtk::Menu m_menu; Gtk::MenuItem m_menu_remove; + Gtk::MenuItem m_menu_set_filename; Gtk::Box m_box; Gtk::Image *m_img = nullptr; diff --git a/src/dialogs/textinput.cpp b/src/dialogs/textinput.cpp new file mode 100644 index 0000000..ae75f70 --- /dev/null +++ b/src/dialogs/textinput.cpp @@ -0,0 +1,26 @@ +#include "textinput.hpp" + +TextInputDialog::TextInputDialog(const Glib::ustring &prompt, const Glib::ustring &title, const Glib::ustring &placeholder, Gtk::Window &parent) + : Gtk::Dialog(title, parent, true) + , m_label(prompt) { + get_style_context()->add_class("app-window"); + get_style_context()->add_class("app-popup"); + + auto ok = add_button("OK", Gtk::RESPONSE_OK); + auto cancel = add_button("Cancel", Gtk::RESPONSE_CANCEL); + + get_content_area()->add(m_label); + get_content_area()->add(m_entry); + + m_entry.set_text(placeholder); + + m_entry.set_activates_default(true); + ok->set_can_default(true); + ok->grab_default(); + + show_all_children(); +} + +Glib::ustring TextInputDialog::GetInput() const { + return m_entry.get_text(); +} diff --git a/src/dialogs/textinput.hpp b/src/dialogs/textinput.hpp new file mode 100644 index 0000000..fd2d2b8 --- /dev/null +++ b/src/dialogs/textinput.hpp @@ -0,0 +1,14 @@ +#pragma once +#include +#include + +class TextInputDialog : public Gtk::Dialog { +public: + TextInputDialog(const Glib::ustring &prompt, const Glib::ustring &title, const Glib::ustring &placeholder, Gtk::Window &parent); + + Glib::ustring GetInput() const; + +private: + Gtk::Label m_label; + Gtk::Entry m_entry; +}; -- cgit v1.2.3 From e87766f106c1b8e4b57ce59c713fcf4efe6df39a Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 30 Jun 2022 04:20:42 -0400 Subject: dnd'd files show preview images if displayable --- src/components/chatinput.cpp | 63 ++++++++++++++++++++++++++++++++++++++------ src/components/chatinput.hpp | 7 +++-- 2 files changed, 60 insertions(+), 10 deletions(-) (limited to 'src/components/chatinput.hpp') diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index cd508e8..203f960 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -67,6 +67,7 @@ void ChatInputText::on_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(); @@ -151,10 +152,14 @@ bool ChatInputAttachmentContainer::AddImage(const Glib::RefPtr &pb) return true; } -bool ChatInputAttachmentContainer::AddFile(const Glib::RefPtr &file) { +bool ChatInputAttachmentContainer::AddFile(const Glib::RefPtr &file, Glib::RefPtr pb) { if (m_attachments.size() == 10) return false; - auto *item = Gtk::make_managed(file); + ChatInputAttachmentItem *item; + if (pb) + item = Gtk::make_managed(file, pb, true); + else + item = Gtk::make_managed(file); item->show(); item->set_valign(Gtk::ALIGN_CENTER); m_box.add(*item); @@ -202,16 +207,16 @@ ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr & 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) - SetupMenu(); + 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); + SetupMenu(); + UpdateTooltip(); } -ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr &file, const Glib::RefPtr &pb) +ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr &file, const Glib::RefPtr &pb, bool is_extant) : m_file(file) , m_img(Gtk::make_managed()) - , m_type(ChatSubmitParams::PastedImage) + , m_type(is_extant ? ChatSubmitParams::ExtantFile : ChatSubmitParams::PastedImage) , m_filename("unknown.png") { get_style_context()->add_class("attachment-item"); @@ -226,6 +231,9 @@ ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr & add(m_box); show_all_children(); + if (is_extant) + SetFilenameFromFile(); + SetupMenu(); UpdateTooltip(); } @@ -251,6 +259,11 @@ void ChatInputAttachmentItem::RemoveIfTemp() { 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); +} + void ChatInputAttachmentItem::SetupMenu() { m_menu_remove.set_label("Remove"); m_menu_remove.signal_activate().connect([this] { @@ -348,9 +361,43 @@ bool ChatInput::ProcessKeyPress(GdkEventKey *event) { void ChatInput::AddAttachment(const Glib::RefPtr &file) { const bool can_attach_files = m_signal_check_permission.emit(Permission::ATTACH_FILES); + if (!can_attach_files) return; + + std::string content_type; - if (can_attach_files && m_attachments.AddFile(file)) + 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()); + return; + } catch (...) { + puts("attachment query exception"); + return; + } + + static const std::unordered_set image_exts { + ".png", + ".jpg", + }; + + if (image_exts.find(content_type) != image_exts.end()) { + if (AddFileAsImageAttachment(file)) + m_attachments_revealer.set_reveal_child(true); + } else if (m_attachments.AddFile(file)) { m_attachments_revealer.set_reveal_child(true); + } +} + +bool ChatInput::AddFileAsImageAttachment(const Glib::RefPtr &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() { diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index 8a4ad8d..625161a 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -6,7 +6,7 @@ class ChatInputAttachmentItem : public Gtk::EventBox { public: ChatInputAttachmentItem(const Glib::RefPtr &file); - ChatInputAttachmentItem(const Glib::RefPtr &file, const Glib::RefPtr &pb); + ChatInputAttachmentItem(const Glib::RefPtr &file, const Glib::RefPtr &pb, bool is_extant = false); [[nodiscard]] Glib::RefPtr GetFile() const; [[nodiscard]] ChatSubmitParams::AttachmentType GetType() const; @@ -15,6 +15,7 @@ public: void RemoveIfTemp(); private: + void SetFilenameFromFile(); void SetupMenu(); void UpdateTooltip(); @@ -45,7 +46,7 @@ public: void Clear(); void ClearNoPurge(); bool AddImage(const Glib::RefPtr &pb); - bool AddFile(const Glib::RefPtr &file); + bool AddFile(const Glib::RefPtr &file, Glib::RefPtr pb = {}); [[nodiscard]] std::vector GetAttachments() const; private: @@ -103,6 +104,8 @@ public: void AddAttachment(const Glib::RefPtr &file); private: + bool AddFileAsImageAttachment(const Glib::RefPtr &file); + Gtk::Revealer m_attachments_revealer; ChatInputAttachmentContainer m_attachments; ChatInputText m_input; -- cgit v1.2.3 From 02ce353c6d35af004dc1b6f5ae9f68fbb8540b54 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 9 Jul 2022 01:57:56 -0400 Subject: check nitro size restriction + fix replying border --- res/css/main.css | 5 +++++ src/components/chatinput.cpp | 17 +++++++++++++++++ src/components/chatinput.hpp | 4 ++++ src/components/chatwindow.cpp | 24 +++++++++++++++++++++--- src/constants.hpp | 5 +++++ 5 files changed, 52 insertions(+), 3 deletions(-) (limited to 'src/components/chatinput.hpp') diff --git a/res/css/main.css b/res/css/main.css index a3d0421..142dede 100644 --- a/res/css/main.css +++ b/res/css/main.css @@ -102,12 +102,17 @@ background-color: #242424; color: #adadad; border-radius: 15px; + border: 1px solid transparent; } .message-input.replying { border: 1px solid #026FB9; } +.message-input.bad-input { + border: 1px solid #dd3300; +} + .message-input { padding: 0px 0px 0px 5px; } diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index b71a234..fc5ef38 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -392,6 +392,23 @@ void ChatInput::AddAttachment(const Glib::RefPtr &file) { } } +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 &file) { try { const auto read_stream = file->read(); diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index 625161a..2b88fd1 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -102,6 +102,10 @@ public: Glib::RefPtr GetBuffer(); bool ProcessKeyPress(GdkEventKey *event); void AddAttachment(const Glib::RefPtr &file); + void IndicateTooLarge(); + + void StartReplying(); + void StopReplying(); private: bool AddFileAsImageAttachment(const Glib::RefPtr &file); diff --git a/src/components/chatwindow.cpp b/src/components/chatwindow.cpp index 5cbeea1..61d3712 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -4,6 +4,7 @@ #include "ratelimitindicator.hpp" #include "chatinput.hpp" #include "chatlist.hpp" +#include "constants.hpp" #ifdef WITH_LIBHANDY #include "channeltabswitcherhandy.hpp" #endif @@ -222,6 +223,24 @@ Snowflake ChatWindow::GetActiveChannel() const { } bool ChatWindow::OnInputSubmit(ChatSubmitParams data) { + int restriction = BaseAttachmentSizeLimit; + const auto nitro = Abaddon::Get().GetDiscordClient().GetUserData().PremiumType; + if (!nitro.has_value() || nitro == EPremiumType::None) { + restriction = BaseAttachmentSizeLimit; + } else if (nitro == EPremiumType::NitroClassic) { + restriction = NitroClassicAttachmentSizeLimit; + } else if (nitro == EPremiumType::Nitro) { + restriction = NitroAttachmentSizeLimit; + } + + for (const auto &attachment : data.Attachments) { + const auto info = attachment.File->query_info(); + if (info && info->get_size() > restriction) { + m_input->IndicateTooLarge(); + return false; + } + } + if (!m_rate_limit_indicator->CanSpeak()) return false; @@ -255,8 +274,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()); else @@ -266,7 +284,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/constants.hpp b/src/constants.hpp index 30de2ae..9b2413d 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -3,3 +3,8 @@ constexpr static uint64_t SnowflakeSplitDifference = 600; constexpr static int MaxMessagesForChatCull = 50; // this has to be 50 (for now) cuz that magic number is used in a couple other places and i dont feel like replacing them constexpr static int AttachmentItemSize = 128; +constexpr static int BaseAttachmentSizeLimit = 8 * 1024 * 1024; +constexpr static int NitroClassicAttachmentSizeLimit = 50 * 1024 * 1024; +constexpr static int NitroAttachmentSizeLimit = 100 * 1024 * 1024; +constexpr static int BoostLevel2AttachmentSizeLimit = 50 * 1024 * 1024; +constexpr static int BoostLevel3AttachmentSizeLimit = 100 * 1024 * 1024; -- cgit v1.2.3 From 59acd0f82f500d743ca4be7324f913903c8de237 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 23 Jul 2022 18:34:33 -0400 Subject: handle max message payload + show filename label --- src/components/chatinput.cpp | 32 +++++++++++++++++++++++++++----- src/components/chatinput.hpp | 1 + src/components/chatwindow.cpp | 15 ++++++++++++--- src/constants.hpp | 1 + 4 files changed, 41 insertions(+), 8 deletions(-) (limited to 'src/components/chatinput.hpp') diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index fc5ef38..04ba0d9 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -2,7 +2,6 @@ #include "abaddon.hpp" #include "constants.hpp" #include -#include ChatInputText::ChatInputText() { get_style_context()->add_class("message-input"); @@ -96,6 +95,8 @@ 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(); @@ -194,16 +195,25 @@ ChatInputAttachmentContainer::type_signal_emptied ChatInputAttachmentContainer:: ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr &file) : m_file(file) , m_img(Gtk::make_managed()) - , m_type(ChatSubmitParams::ExtantFile) { + , m_type(ChatSubmitParams::ExtantFile) + , m_box(Gtk::ORIENTATION_VERTICAL) { get_style_context()->add_class("attachment-item"); set_size_request(AttachmentItemSize, AttachmentItemSize); - m_box.set_halign(Gtk::ALIGN_CENTER); + set_halign(Gtk::ALIGN_START); + m_box.set_hexpand(true); + m_box.set_halign(Gtk::ALIGN_FILL); m_box.set_valign(Gtk::ALIGN_CENTER); m_box.add(*m_img); + m_box.add(m_label); add(m_box); show_all_children(); + 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->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) @@ -217,7 +227,9 @@ ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr & : m_file(file) , m_img(Gtk::make_managed()) , m_type(is_extant ? ChatSubmitParams::ExtantFile : ChatSubmitParams::PastedImage) - , m_filename("unknown.png") { + , m_filename("unknown.png") + , m_label("unknown.png") + , m_box(Gtk::ORIENTATION_VERTICAL) { get_style_context()->add_class("attachment-item"); int outw, outh; @@ -225,12 +237,20 @@ ChatInputAttachmentItem::ChatInputAttachmentItem(const Glib::RefPtr & m_img->property_pixbuf() = pb->scale_simple(outw, outh, Gdk::INTERP_BILINEAR); set_size_request(AttachmentItemSize, AttachmentItemSize); - m_box.set_halign(Gtk::ALIGN_CENTER); + set_halign(Gtk::ALIGN_START); + m_box.set_hexpand(true); + m_box.set_halign(Gtk::ALIGN_FILL); m_box.set_valign(Gtk::ALIGN_CENTER); m_box.add(*m_img); + m_box.add(m_label); add(m_box); show_all_children(); + 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); + if (is_extant) SetFilenameFromFile(); @@ -262,6 +282,7 @@ void ChatInputAttachmentItem::RemoveIfTemp() { 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() { @@ -275,6 +296,7 @@ void ChatInputAttachmentItem::SetupMenu() { 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(); } }); diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index 2b88fd1..d8484b8 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -24,6 +24,7 @@ private: Gtk::MenuItem m_menu_set_filename; Gtk::Box m_box; + Gtk::Label m_label; Gtk::Image *m_img = nullptr; Glib::RefPtr m_file; diff --git a/src/components/chatwindow.cpp b/src/components/chatwindow.cpp index 38e0247..b68ceba 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -250,11 +250,20 @@ bool ChatWindow::OnInputSubmit(ChatSubmitParams data) { 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 && info->get_size() > restriction) { - m_input->IndicateTooLarge(); - return false; + 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; + } } } diff --git a/src/constants.hpp b/src/constants.hpp index 9b2413d..14fa076 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -8,3 +8,4 @@ constexpr static int NitroClassicAttachmentSizeLimit = 50 * 1024 * 1024; constexpr static int NitroAttachmentSizeLimit = 100 * 1024 * 1024; constexpr static int BoostLevel2AttachmentSizeLimit = 50 * 1024 * 1024; constexpr static int BoostLevel3AttachmentSizeLimit = 100 * 1024 * 1024; +constexpr static int MaxMessagePayloadSize = 199 * 1024 * 1024; -- cgit v1.2.3 From 344f2694141aa0cc8fc214dfd8ce2011df1a8079 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 7 Aug 2022 02:16:20 -0400 Subject: add file picker to chat input --- res/css/main.css | 6 ++- src/components/chatinput.cpp | 104 ++++++++++++++++++++++++++++++++++++++++--- src/components/chatinput.hpp | 26 ++++++++++- 3 files changed, 128 insertions(+), 8 deletions(-) (limited to 'src/components/chatinput.hpp') diff --git a/res/css/main.css b/res/css/main.css index 6d693bd..3765498 100644 --- a/res/css/main.css +++ b/res/css/main.css @@ -118,8 +118,12 @@ border: 1px solid #dd3300; } +.message-input-browse-icon { + color: #b9bbbe; +} + .message-input { - padding: 0px 0px 0px 5px; + padding: 0px 0px 0px 30px; } .members { diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 13e2550..7ecd4de 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -91,6 +91,95 @@ ChatInputText::type_signal_image_paste ChatInputText::signal_image_paste() { return m_signal_image_paste; } +ChatInputTextContainer::ChatInputTextContainer() { + // triple hack !!! + auto cb = [this](GdkEventKey *e) -> bool { + return event(reinterpret_cast(e)); + }; + m_input.signal_key_press_event().connect(cb, false); + + m_upload_img.property_icon_name() = "document-send-symbolic"; + m_upload_img.property_icon_size() = Gtk::ICON_SIZE_LARGE_TOOLBAR; + m_upload_img.get_style_context()->add_class("message-input-browse-icon"); + + AddPointerCursor(m_upload_ev); + + m_upload_ev.signal_button_press_event().connect([this](GdkEventButton *ev) -> bool { + if (ev->button == GDK_BUTTON_PRIMARY) { + ShowFileChooser(); + return true; + } + return false; + }); + + m_upload_ev.add(m_upload_img); + add_overlay(m_upload_ev); + add(m_input); + + show_all_children(); + + // stop the overlay from using (start) padding + signal_get_child_position().connect(sigc::mem_fun(*this, &ChatInputTextContainer::GetChildPosition), false); +} + +void ChatInputTextContainer::ShowFileChooser() { + auto dlg = Gtk::FileChooserNative::create("Choose file", Gtk::FILE_CHOOSER_ACTION_OPEN); + dlg->set_select_multiple(true); + dlg->set_modal(true); + + dlg->signal_response().connect([this, dlg](int response) { + if (response == Gtk::RESPONSE_ACCEPT) { + for (const auto &file : dlg->get_files()) { + m_signal_add_attachment.emit(file); + } + } + }); + + auto filter_all = Gtk::FileFilter::create(); + filter_all->set_name("All files (*.*)"); + filter_all->add_pattern("*.*"); + dlg->add_filter(filter_all); + + dlg->run(); +} + +ChatInputText &ChatInputTextContainer::Get() { + return m_input; +} + +bool ChatInputTextContainer::GetChildPosition(Gtk::Widget *child, Gdk::Rectangle &pos) { + Gtk::Allocation main_alloc; + { + auto *grandchild = m_input.get_child(); + int x, y; + if (grandchild->translate_coordinates(m_input, 0, 0, x, y)) { + main_alloc.set_x(x); + main_alloc.set_y(y); + } else { + main_alloc.set_x(0); + main_alloc.set_y(0); + } + main_alloc.set_width(grandchild->get_allocated_width()); + main_alloc.set_height(grandchild->get_allocated_height()); + } + + Gtk::Requisition min, req; + child->get_preferred_size(min, req); + + // yummy hardcoded values + pos.set_x(5); + pos.set_width(std::max(min.width, std::min(main_alloc.get_width(), req.width))); + + pos.set_y(12); + pos.set_height(std::max(min.height, std::min(main_alloc.get_height(), req.height))); + + return true; +} + +ChatInputTextContainer::type_signal_add_attachment ChatInputTextContainer::signal_add_attachment() { + return m_signal_add_attachment; +} + ChatInputAttachmentContainer::ChatInputAttachmentContainer() : m_box(Gtk::ORIENTATION_HORIZONTAL) { get_style_context()->add_class("attachment-container"); @@ -336,12 +425,15 @@ ChatInputAttachmentItem::type_signal_item_removed ChatInputAttachmentItem::signa ChatInput::ChatInput() : Gtk::Box(Gtk::ORIENTATION_VERTICAL) { - m_input.signal_escape().connect([this] { + m_input.signal_add_attachment().connect(sigc::mem_fun(*this, &ChatInput::AddAttachment)); + + m_input.Get().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 { + + m_input.Get().signal_submit().connect([this](const Glib::ustring &input) -> bool { ChatSubmitParams data; data.Message = input; data.Attachments = m_attachments.GetAttachments(); @@ -362,7 +454,7 @@ ChatInput::ChatInput() add(m_input); show_all_children(); - m_input.signal_image_paste().connect([this](const Glib::RefPtr &pb) { + m_input.Get().signal_image_paste().connect([this](const Glib::RefPtr &pb) { const bool can_attach_files = m_signal_check_permission.emit(Permission::ATTACH_FILES); if (can_attach_files && m_attachments.AddImage(pb)) @@ -381,15 +473,15 @@ ChatInput::ChatInput() } void ChatInput::InsertText(const Glib::ustring &text) { - m_input.InsertText(text); + m_input.Get().InsertText(text); } Glib::RefPtr ChatInput::GetBuffer() { - return m_input.GetBuffer(); + return m_input.Get().GetBuffer(); } bool ChatInput::ProcessKeyPress(GdkEventKey *event) { - return m_input.ProcessKeyPress(event); + return m_input.Get().ProcessKeyPress(event); } void ChatInput::AddAttachment(const Glib::RefPtr &file) { diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index d8484b8..82ea2fd 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -95,6 +95,30 @@ private: type_signal_image_paste m_signal_image_paste; }; +// file upload, text +class ChatInputTextContainer : public Gtk::Overlay { +public: + ChatInputTextContainer(); + + // not proxying everythign lol!! + ChatInputText &Get(); + +private: + void ShowFileChooser(); + bool GetChildPosition(Gtk::Widget *child, Gdk::Rectangle &pos); + + Gtk::EventBox m_upload_ev; + Gtk::Image m_upload_img; + ChatInputText m_input; + +public: + using type_signal_add_attachment = sigc::signal>; + type_signal_add_attachment signal_add_attachment(); + +private: + type_signal_add_attachment m_signal_add_attachment; +}; + class ChatInput : public Gtk::Box { public: ChatInput(); @@ -113,7 +137,7 @@ private: Gtk::Revealer m_attachments_revealer; ChatInputAttachmentContainer m_attachments; - ChatInputText m_input; + ChatInputTextContainer m_input; public: using type_signal_submit = sigc::signal; -- cgit v1.2.3 From 91527fbd0d11f639e58b9ea6f77aaa1b48e1f593 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 7 Aug 2022 19:54:39 -0400 Subject: pull chat input permission check out of signal --- src/components/chatinput.cpp | 19 ++++++++++--------- src/components/chatinput.hpp | 8 +++++--- src/components/chatwindow.cpp | 4 +--- 3 files changed, 16 insertions(+), 15 deletions(-) (limited to 'src/components/chatinput.hpp') diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 65b3c30..63bd48b 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -457,9 +457,7 @@ ChatInput::ChatInput() show_all_children(); m_input.Get().signal_image_paste().connect([this](const Glib::RefPtr &pb) { - const bool can_attach_files = m_signal_check_permission.emit(Permission::ATTACH_FILES); - - if (can_attach_files && m_attachments.AddImage(pb)) + if (CanAttachFiles() && m_attachments.AddImage(pb)) m_attachments_revealer.set_reveal_child(true); }); @@ -487,8 +485,7 @@ bool ChatInput::ProcessKeyPress(GdkEventKey *event) { } void ChatInput::AddAttachment(const Glib::RefPtr &file) { - const bool can_attach_files = m_signal_check_permission.emit(Permission::ATTACH_FILES); - if (!can_attach_files) return; + if (!CanAttachFiles()) return; std::string content_type; @@ -527,6 +524,10 @@ void ChatInput::IndicateTooLarge() { Glib::signal_timeout().connect_seconds_once(sigc::track_obj(cb, *this), 2); } +void ChatInput::SetActiveChannel(Snowflake id) { + m_active_channel = id; +} + void ChatInput::StartReplying() { m_input.Get().grab_focus(); m_input.Get().get_style_context()->add_class("replying"); @@ -547,6 +548,10 @@ bool ChatInput::AddFileAsImageAttachment(const Glib::RefPtr &file) { } } +bool ChatInput::CanAttachFiles() { + return Abaddon::Get().GetDiscordClient().HasSelfChannelPermission(m_active_channel, Permission::ATTACH_FILES); +} + ChatInput::type_signal_submit ChatInput::signal_submit() { return m_signal_submit; } @@ -554,7 +559,3 @@ 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 82ea2fd..f6c4358 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -129,27 +129,29 @@ public: void AddAttachment(const Glib::RefPtr &file); void IndicateTooLarge(); + void SetActiveChannel(Snowflake id); + void StartReplying(); void StopReplying(); private: bool AddFileAsImageAttachment(const Glib::RefPtr &file); + bool CanAttachFiles(); Gtk::Revealer m_attachments_revealer; ChatInputAttachmentContainer m_attachments; ChatInputTextContainer m_input; + Snowflake m_active_channel; + public: using type_signal_submit = sigc::signal; using type_signal_escape = sigc::signal; - using type_signal_check_permission = sigc::signal; 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/chatwindow.cpp b/src/components/chatwindow.cpp index b68ceba..f7c3459 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -49,9 +49,6 @@ ChatWindow::ChatWindow() { 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) @@ -133,6 +130,7 @@ void ChatWindow::SetMessages(const std::vector &msgs) { void ChatWindow::SetActiveChannel(Snowflake id) { m_active_channel = id; m_chat->SetActiveChannel(id); + m_input->SetActiveChannel(id); m_input_indicator->SetActiveChannel(id); m_rate_limit_indicator->SetActiveChannel(id); if (m_is_replying) -- cgit v1.2.3 From 955b9239b9eac748d3e5ac4dc56864c906c81393 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 8 Aug 2022 00:40:20 -0400 Subject: hide browse icon when not in channel with perms --- res/css/main.css | 7 ++++++- src/components/chatinput.cpp | 17 +++++++++++++++++ src/components/chatinput.hpp | 3 +++ 3 files changed, 26 insertions(+), 1 deletion(-) (limited to 'src/components/chatinput.hpp') diff --git a/res/css/main.css b/res/css/main.css index 3765498..9398d48 100644 --- a/res/css/main.css +++ b/res/css/main.css @@ -122,7 +122,12 @@ color: #b9bbbe; } -.message-input { +/* i dont think theres a way to circumvent having to do this to adjust around the browse icon */ +.message-input:not(.with-browser-icon) { + padding: 0px 0px 0px 5px; +} + +.message-input.with-browse-icon { padding: 0px 0px 0px 30px; } diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 75dda8d..87892e0 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -149,6 +149,14 @@ ChatInputText &ChatInputTextContainer::Get() { return m_input; } +void ChatInputTextContainer::ShowChooserIcon() { + m_upload_ev.show(); +} + +void ChatInputTextContainer::HideChooserIcon() { + m_upload_ev.hide(); +} + bool ChatInputTextContainer::GetChildPosition(Gtk::Widget *child, Gdk::Rectangle &pos) { Gtk::Allocation main_alloc; { @@ -470,6 +478,8 @@ ChatInput::ChatInput() m_attachments.signal_emptied().connect([this] { m_attachments_revealer.set_reveal_child(false); }); + + SetActiveChannel(Snowflake::Invalid); } void ChatInput::InsertText(const Glib::ustring &text) { @@ -526,6 +536,13 @@ void ChatInput::IndicateTooLarge() { void ChatInput::SetActiveChannel(Snowflake id) { m_active_channel = id; + if (CanAttachFiles()) { + m_input.Get().get_style_context()->add_class("with-browse-icon"); + m_input.ShowChooserIcon(); + } else { + m_input.Get().get_style_context()->remove_class("with-browse-icon"); + m_input.HideChooserIcon(); + } } void ChatInput::StartReplying() { diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index f6c4358..807f958 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -103,6 +103,9 @@ public: // not proxying everythign lol!! ChatInputText &Get(); + void ShowChooserIcon(); + void HideChooserIcon(); + private: void ShowFileChooser(); bool GetChildPosition(Gtk::Widget *child, Gdk::Rectangle &pos); -- cgit v1.2.3