Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve message editing #192

Merged
merged 5 commits into from
Jul 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions res/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@
border: 1px solid #026FB9;
}

.message-input.editing {
border: 1px solid #b9026f;
}

.message-input.bad-input {
border: 1px solid #dd3300;
}
Expand Down
18 changes: 7 additions & 11 deletions src/abaddon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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");
Expand Down
24 changes: 23 additions & 1 deletion src/components/chatinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down Expand Up @@ -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();
Expand All @@ -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() {
Expand Down
8 changes: 8 additions & 0 deletions src/components/chatinput.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ class ChatInput : public Gtk::Box {
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);
Expand All @@ -139,6 +140,11 @@ class ChatInput : public Gtk::Box {
void StartReplying();
void StopReplying();

void StartEditing(const Message &message);
void StopEditing();

bool IsEmpty();

private:
bool AddFileAsImageAttachment(const Glib::RefPtr<Gio::File> &file);
bool CanAttachFiles();
Expand All @@ -149,6 +155,8 @@ class ChatInput : public Gtk::Box {

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>;
Expand Down
24 changes: 21 additions & 3 deletions src/components/chatlist.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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] {
Expand Down
1 change: 1 addition & 0 deletions src/components/chatlist.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Snowflake> GetLastSentEditableMessage();

private:
void SetupMenu();
Expand Down
56 changes: 48 additions & 8 deletions src/components/chatwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -274,22 +274,41 @@ 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;

if (m_input->ProcessKeyPress(e))
return true;

if (ProcessKeyEvent(e))
return true;

return false;
}

Expand All @@ -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() {
Expand All @@ -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);
Expand Down
7 changes: 7 additions & 0 deletions src/components/chatwindow.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ class ChatWindow {
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();
Expand All @@ -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);

Expand Down
45 changes: 0 additions & 45 deletions src/dialogs/editmessage.cpp

This file was deleted.

20 changes: 0 additions & 20 deletions src/dialogs/editmessage.hpp

This file was deleted.

1 change: 1 addition & 0 deletions src/discord/chatsubmitparams.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ struct ChatSubmitParams {
bool Silent = false;
Snowflake ChannelID;
Snowflake InReplyToID;
Snowflake EditingID;
Glib::ustring Message;
std::vector<Attachment> Attachments;
};
4 changes: 4 additions & 0 deletions src/discord/message.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading
Loading