Merge pull request #192 from uowuo/message-editing

Improve message editing
This commit is contained in:
ouwou 2023-07-16 04:16:48 +00:00 committed by GitHub
commit 49bbc926e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 135 additions and 92 deletions

View File

@ -114,6 +114,10 @@
border: 1px solid #026FB9;
}
.message-input.editing {
border: 1px solid #b9026f;
}
.message-input.bad-input {
border: 1px solid #dd3300;
}

View File

@ -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");

View File

@ -497,6 +497,10 @@ void ChatInput::InsertText(const Glib::ustring &text) {
m_input.Get().InsertText(text);
}
void ChatInput::Clear() {
GetBuffer()->set_text("");
}
Glib::RefPtr<Gtk::TextBuffer> ChatInput::GetBuffer() {
return m_input.Get().GetBuffer();
}
@ -565,6 +569,24 @@ void ChatInput::StopReplying() {
m_input.Get().get_style_context()->remove_class("replying");
}
void ChatInput::StartEditing(const Message &message) {
m_is_editing = true;
m_input.Get().grab_focus();
m_input.Get().get_style_context()->add_class("editing");
GetBuffer()->set_text(message.Content);
m_attachments.Clear();
m_attachments_revealer.set_reveal_child(false);
}
void ChatInput::StopEditing() {
m_is_editing = false;
m_input.Get().get_style_context()->remove_class("editing");
}
bool ChatInput::IsEmpty() {
return GetBuffer()->get_char_count() == 0;
}
bool ChatInput::AddFileAsImageAttachment(const Glib::RefPtr<Gio::File> &file) {
try {
const auto read_stream = file->read();
@ -577,7 +599,7 @@ bool ChatInput::AddFileAsImageAttachment(const Glib::RefPtr<Gio::File> &file) {
}
bool ChatInput::CanAttachFiles() {
return Abaddon::Get().GetDiscordClient().HasSelfChannelPermission(m_active_channel, Permission::ATTACH_FILES | Permission::SEND_MESSAGES);
return !m_is_editing && Abaddon::Get().GetDiscordClient().HasSelfChannelPermission(m_active_channel, Permission::ATTACH_FILES | Permission::SEND_MESSAGES);
}
ChatInput::type_signal_submit ChatInput::signal_submit() {

View File

@ -129,6 +129,7 @@ public:
ChatInput();
void InsertText(const Glib::ustring &text);
void Clear();
Glib::RefPtr<Gtk::TextBuffer> GetBuffer();
bool ProcessKeyPress(GdkEventKey *event);
void AddAttachment(const Glib::RefPtr<Gio::File> &file);
@ -139,6 +140,11 @@ public:
void StartReplying();
void StopReplying();
void StartEditing(const Message &message);
void StopEditing();
bool IsEmpty();
private:
bool AddFileAsImageAttachment(const Glib::RefPtr<Gio::File> &file);
bool CanAttachFiles();
@ -149,6 +155,8 @@ private:
Snowflake m_active_channel;
bool m_is_editing = false;
public:
using type_signal_submit = sigc::signal<bool, ChatSubmitParams>;
using type_signal_escape = sigc::signal<void>;

View File

@ -133,10 +133,9 @@ void ChatList::ProcessNewMessage(const Message &data, bool prepend) {
m_menu_delete_message->set_sensitive(false);
m_menu_edit_message->set_sensitive(false);
} else {
const bool can_edit = client.GetUserData().ID == data->Author.ID;
const bool can_delete = can_edit || has_manage;
const bool can_delete = (client.GetUserData().ID == data->Author.ID) || has_manage;
m_menu_delete_message->set_sensitive(can_delete);
m_menu_edit_message->set_sensitive(can_edit);
m_menu_edit_message->set_sensitive(data->IsEditable());
}
m_menu.popup_at_pointer(reinterpret_cast<GdkEvent *>(ev));
@ -253,6 +252,25 @@ void ChatList::ActuallyRemoveMessage(Snowflake id) {
RemoveMessageAndHeader(it->second);
}
std::optional<Snowflake> ChatList::GetLastSentEditableMessage() {
const auto &discord = Abaddon::Get().GetDiscordClient();
const auto self_id = discord.GetUserData().ID;
std::map<Snowflake, Gtk::Widget *> 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<ChatMessageItemContainer *>(it->second);
if (widget == nullptr) continue;
const auto msg = discord.GetMessage(widget->ID);
if (!msg.has_value()) continue;
if (msg->Author.ID != self_id) continue;
if (!msg->IsEditable()) continue;
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] {

View File

@ -23,6 +23,7 @@ public:
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<Snowflake> GetLastSentEditableMessage();
private:
void SetupMenu();

View File

@ -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 && !(e->state & GDK_SHIFT_MASK) && m_input->IsEmpty()) {
const auto edit_id = m_chat->GetLastSentEditableMessage();
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,26 @@ 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->Clear();
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);

View File

@ -37,6 +37,9 @@ public:
void SetTopic(const std::string &text);
void AddAttachment(const Glib::RefPtr<Gio::File> &file);
void StartEditing(Snowflake message_id);
void StopEditing();
#ifdef WITH_LIBHANDY
void OpenNewTab(Snowflake id);
TabsState GetTabsState();
@ -55,10 +58,14 @@ protected:
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);

View File

@ -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);
}

View File

@ -1,20 +0,0 @@
#pragma once
#include <string>
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;
};

View File

@ -20,6 +20,7 @@ struct ChatSubmitParams {
bool Silent = false;
Snowflake ChannelID;
Snowflake InReplyToID;
Snowflake EditingID;
Glib::ustring Message;
std::vector<Attachment> Attachments;
};

View File

@ -265,6 +265,10 @@ bool Message::IsEdited() const {
return m_edited;
}
bool Message::IsEditable() const noexcept {
return (Abaddon::Get().GetDiscordClient().GetUserData().ID == Author.ID) && !IsDeleted() && !IsPending && (Type == MessageType::DEFAULT || Type == MessageType::INLINE_REPLY);
}
bool Message::DoesMentionEveryoneOrUser(Snowflake id) const noexcept {
if (DoesMentionEveryone) return true;
return std::any_of(Mentions.begin(), Mentions.end(), [id](const UserData &user) {

View File

@ -219,11 +219,13 @@ struct Message {
void SetDeleted();
void SetEdited();
[[nodiscard]] bool IsDeleted() const;
[[nodiscard]] bool IsEdited() const;
bool IsDeleted() const;
bool IsEdited() const;
[[nodiscard]] bool DoesMentionEveryoneOrUser(Snowflake id) const noexcept;
[[nodiscard]] bool DoesMention(Snowflake id) const noexcept;
bool IsEditable() const noexcept;
bool DoesMentionEveryoneOrUser(Snowflake id) const noexcept;
bool DoesMention(Snowflake id) const noexcept;
private:
bool m_deleted = false;

View File

@ -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();

View File

@ -29,6 +29,7 @@ public:
void UpdateChatReactionRemove(Snowflake id, const Glib::ustring &param);
void UpdateMenus();
void ToggleMenuVisibility();
void EditMessage(Snowflake message_id);
#ifdef WITH_LIBHANDY
void GoBack();