diff --git a/res/css/main.css b/res/css/main.css index 1b703017..b84b5028 100644 --- a/res/css/main.css +++ b/res/css/main.css @@ -114,6 +114,10 @@ border: 1px solid #026FB9; } +.message-input.editing { + border: 1px solid #b9026f; +} + .message-input.bad-input { border: 1px solid #dd3300; } diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 921549bb..2be54bae 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -8,7 +8,6 @@ #include "audio/manager.hpp" #include "discord/discord.hpp" #include "dialogs/token.hpp" -#include "dialogs/editmessage.hpp" #include "dialogs/confirm.hpp" #include "dialogs/setstatus.hpp" #include "dialogs/friendpicker.hpp" @@ -948,19 +947,15 @@ void Abaddon::ActionChatInputSubmit(ChatSubmitParams data) { if (!m_discord.HasChannelPermission(m_discord.GetUserData().ID, data.ChannelID, Permission::VIEW_CHANNEL)) return; - m_discord.SendChatMessage(data, NOOP_CALLBACK); + if (data.EditingID.IsValid()) { + m_discord.EditMessage(data.ChannelID, data.EditingID, data.Message); + } else { + m_discord.SendChatMessage(data, NOOP_CALLBACK); + } } void Abaddon::ActionChatEditMessage(Snowflake channel_id, Snowflake id) { - const auto msg = m_discord.GetMessage(id); - if (!msg.has_value()) return; - EditMessageDialog dlg(*m_main_window); - dlg.SetContent(msg->Content); - auto response = dlg.run(); - if (response == Gtk::RESPONSE_OK) { - auto new_content = dlg.GetContent(); - m_discord.EditMessage(channel_id, id, new_content); - } + m_main_window->EditMessage(id); } void Abaddon::ActionInsertMention(Snowflake id) { @@ -1156,6 +1151,7 @@ int main(int argc, char **argv) { #endif spdlog::cfg::load_env_levels(); + auto log_ui = spdlog::stdout_color_mt("ui"); auto log_audio = spdlog::stdout_color_mt("audio"); auto log_voice = spdlog::stdout_color_mt("voice"); auto log_discord = spdlog::stdout_color_mt("discord"); diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 28ed1ea4..8ad0c3f1 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -565,6 +565,16 @@ void ChatInput::StopReplying() { m_input.Get().get_style_context()->remove_class("replying"); } +void ChatInput::StartEditing(const Message &message) { + m_input.Get().grab_focus(); + m_input.Get().get_style_context()->add_class("editing"); + GetBuffer()->set_text(message.Content); +} + +void ChatInput::StopEditing() { + m_input.Get().get_style_context()->remove_class("editing"); +} + 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 bc6a45de..a7ee27f7 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -139,6 +139,9 @@ class ChatInput : public Gtk::Box { void StartReplying(); void StopReplying(); + void StartEditing(const Message &message); + void StopEditing(); + private: bool AddFileAsImageAttachment(const Glib::RefPtr &file); bool CanAttachFiles(); diff --git a/src/components/chatlist.cpp b/src/components/chatlist.cpp index 93fb46f1..28981b27 100644 --- a/src/components/chatlist.cpp +++ b/src/components/chatlist.cpp @@ -253,6 +253,23 @@ void ChatList::ActuallyRemoveMessage(Snowflake id) { RemoveMessageAndHeader(it->second); } +std::optional ChatList::GetLastSentMessage() { + const auto &discord = Abaddon::Get().GetDiscordClient(); + const auto self_id = discord.GetUserData().ID; + + std::map ordered(m_id_to_widget.begin(), m_id_to_widget.end()); + + for (auto it = ordered.crbegin(); it != ordered.crend(); it++) { + const auto *widget = dynamic_cast(it->second); + if (widget == nullptr) continue; + const auto msg = discord.GetMessage(widget->ID); + if (!msg.has_value()) continue; + if (msg->Author.ID == self_id) return msg->ID; + } + + return std::nullopt; +} + void ChatList::SetupMenu() { m_menu_copy_id = Gtk::manage(new Gtk::MenuItem("Copy ID")); m_menu_copy_id->signal_activate().connect([this] { diff --git a/src/components/chatlist.hpp b/src/components/chatlist.hpp index 0248d7f7..5d4ec4a2 100644 --- a/src/components/chatlist.hpp +++ b/src/components/chatlist.hpp @@ -23,6 +23,7 @@ class ChatList : public Gtk::ScrolledWindow { void SetSeparateAll(bool separate); void SetUsePinnedMenu(); // i think i need a better way to do menus void ActuallyRemoveMessage(Snowflake id); // perhaps not the best method name + std::optional GetLastSentMessage(); private: void SetupMenu(); diff --git a/src/components/chatwindow.cpp b/src/components/chatwindow.cpp index e51d4911..864d0c9c 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -50,8 +50,8 @@ ChatWindow::ChatWindow() { m_input->signal_submit().connect(sigc::mem_fun(*this, &ChatWindow::OnInputSubmit)); m_input->signal_escape().connect([this]() { - if (m_is_replying) - StopReplying(); + if (m_is_replying) StopReplying(); + if (m_is_editing) StopEditing(); }); m_input->signal_key_press_event().connect(sigc::mem_fun(*this, &ChatWindow::OnKeyPressEvent), false); m_input->show(); @@ -132,8 +132,8 @@ void ChatWindow::SetActiveChannel(Snowflake id) { m_input->SetActiveChannel(id); m_input_indicator->SetActiveChannel(id); m_rate_limit_indicator->SetActiveChannel(id); - if (m_is_replying) - StopReplying(); + if (m_is_replying) StopReplying(); + if (m_is_editing) StopEditing(); #ifdef WITH_LIBHANDY m_tab_switcher->ReplaceActiveTab(id); @@ -274,15 +274,31 @@ bool ChatWindow::OnInputSubmit(ChatSubmitParams data) { data.ChannelID = m_active_channel; data.InReplyToID = m_replying_to; + data.EditingID = m_editing_id; if (m_active_channel.IsValid()) m_signal_action_chat_submit.emit(data); // m_replying_to is checked for invalid in the handler - if (m_is_replying) - StopReplying(); + + if (m_is_replying) StopReplying(); + if (m_is_editing) StopEditing(); return true; } +bool ChatWindow::ProcessKeyEvent(GdkEventKey *e) { + if (e->type != GDK_KEY_PRESS) return false; + if (e->keyval == GDK_KEY_Up) { + const auto edit_id = m_chat->GetLastSentMessage(); + if (edit_id.has_value()) { + StartEditing(*edit_id); + } + + return true; + } + + return false; +} + bool ChatWindow::OnKeyPressEvent(GdkEventKey *e) { if (m_completer.ProcessKeyPress(e)) return true; @@ -290,6 +306,9 @@ bool ChatWindow::OnKeyPressEvent(GdkEventKey *e) { if (m_input->ProcessKeyPress(e)) return true; + if (ProcessKeyEvent(e)) + return true; + return false; } @@ -300,10 +319,11 @@ void ChatWindow::StartReplying(Snowflake message_id) { m_replying_to = message_id; m_is_replying = true; m_input->StartReplying(); - if (author.has_value()) + if (author.has_value()) { m_input_indicator->SetCustomMarkup("Replying to " + author->GetUsernameEscapedBold()); - else + } else { m_input_indicator->SetCustomMarkup("Replying..."); + } } void ChatWindow::StopReplying() { @@ -313,6 +333,25 @@ void ChatWindow::StopReplying() { m_input_indicator->ClearCustom(); } +void ChatWindow::StartEditing(Snowflake message_id) { + const auto message = Abaddon::Get().GetDiscordClient().GetMessage(message_id); + if (!message.has_value()) { + spdlog::get("ui")->warn("ChatWindow::StartEditing message is nullopt"); + return; + } + m_is_editing = true; + m_editing_id = message_id; + m_input->StartEditing(*message); + m_input_indicator->SetCustomMarkup("Editing..."); +} + +void ChatWindow::StopEditing() { + m_is_editing = false; + m_editing_id = Snowflake::Invalid; + m_input->StopEditing(); + m_input_indicator->ClearCustom(); +} + void ChatWindow::OnScrollEdgeOvershot(Gtk::PositionType pos) { if (pos == Gtk::POS_TOP) m_signal_action_chat_load_history.emit(m_active_channel); diff --git a/src/components/chatwindow.hpp b/src/components/chatwindow.hpp index e1bb57a5..b3c9d418 100644 --- a/src/components/chatwindow.hpp +++ b/src/components/chatwindow.hpp @@ -37,6 +37,9 @@ class ChatWindow { void SetTopic(const std::string &text); void AddAttachment(const Glib::RefPtr &file); + void StartEditing(Snowflake message_id); + void StopEditing(); + #ifdef WITH_LIBHANDY void OpenNewTab(Snowflake id); TabsState GetTabsState(); @@ -55,10 +58,14 @@ class ChatWindow { void StartReplying(Snowflake message_id); void StopReplying(); + bool m_is_editing = false; + Snowflake m_editing_id; + Snowflake m_active_channel; bool OnInputSubmit(ChatSubmitParams data); + bool ProcessKeyEvent(GdkEventKey *e); bool OnKeyPressEvent(GdkEventKey *e); void OnScrollEdgeOvershot(Gtk::PositionType pos); diff --git a/src/dialogs/editmessage.cpp b/src/dialogs/editmessage.cpp deleted file mode 100644 index b4308a02..00000000 --- a/src/dialogs/editmessage.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "editmessage.hpp" - -EditMessageDialog::EditMessageDialog(Gtk::Window &parent) - : Gtk::Dialog("Edit Message", parent, true) - , m_layout(Gtk::ORIENTATION_VERTICAL) - , m_ok("OK") - , m_cancel("Cancel") - , m_bbox(Gtk::ORIENTATION_HORIZONTAL) { - set_default_size(300, 50); - get_style_context()->add_class("app-window"); - get_style_context()->add_class("app-popup"); - - m_ok.signal_clicked().connect([&]() { - m_content = m_text.get_buffer()->get_text(); - response(Gtk::RESPONSE_OK); - }); - - m_cancel.signal_clicked().connect([&]() { - response(Gtk::RESPONSE_CANCEL); - }); - - m_bbox.pack_start(m_ok, Gtk::PACK_SHRINK); - m_bbox.pack_start(m_cancel, Gtk::PACK_SHRINK); - m_bbox.set_layout(Gtk::BUTTONBOX_END); - - m_text.set_hexpand(true); - - m_scroll.set_hexpand(true); - m_scroll.set_vexpand(true); - m_scroll.add(m_text); - - m_layout.add(m_scroll); - m_layout.add(m_bbox); - get_content_area()->add(m_layout); - - show_all_children(); -} - -Glib::ustring EditMessageDialog::GetContent() { - return m_content; -} - -void EditMessageDialog::SetContent(const Glib::ustring &str) { - m_text.get_buffer()->set_text(str); -} diff --git a/src/dialogs/editmessage.hpp b/src/dialogs/editmessage.hpp deleted file mode 100644 index 38ebedb1..00000000 --- a/src/dialogs/editmessage.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once -#include - -class EditMessageDialog : public Gtk::Dialog { -public: - EditMessageDialog(Gtk::Window &parent); - Glib::ustring GetContent(); - void SetContent(const Glib::ustring &str); - -protected: - Gtk::Box m_layout; - Gtk::Button m_ok; - Gtk::Button m_cancel; - Gtk::ButtonBox m_bbox; - Gtk::ScrolledWindow m_scroll; - Gtk::TextView m_text; - -private: - Glib::ustring m_content; -}; diff --git a/src/discord/chatsubmitparams.hpp b/src/discord/chatsubmitparams.hpp index 24c6f509..e195189d 100644 --- a/src/discord/chatsubmitparams.hpp +++ b/src/discord/chatsubmitparams.hpp @@ -20,6 +20,7 @@ struct ChatSubmitParams { bool Silent = false; Snowflake ChannelID; Snowflake InReplyToID; + Snowflake EditingID; Glib::ustring Message; std::vector Attachments; }; diff --git a/src/windows/mainwindow.cpp b/src/windows/mainwindow.cpp index 0b215677..8e030edc 100644 --- a/src/windows/mainwindow.cpp +++ b/src/windows/mainwindow.cpp @@ -168,6 +168,10 @@ void MainWindow::ToggleMenuVisibility() { m_menu_bar.set_visible(!m_menu_bar.get_visible()); } +void MainWindow::EditMessage(Snowflake message_id) { + m_chat.StartEditing(message_id); +} + #ifdef WITH_LIBHANDY void MainWindow::GoBack() { m_chat.GoBack(); diff --git a/src/windows/mainwindow.hpp b/src/windows/mainwindow.hpp index 604043cd..ce2e6366 100644 --- a/src/windows/mainwindow.hpp +++ b/src/windows/mainwindow.hpp @@ -29,6 +29,7 @@ class MainWindow : public Gtk::Window { void UpdateChatReactionRemove(Snowflake id, const Glib::ustring ¶m); void UpdateMenus(); void ToggleMenuVisibility(); + void EditMessage(Snowflake message_id); #ifdef WITH_LIBHANDY void GoBack();