diff options
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/channels.cpp | 104 | ||||
-rw-r--r-- | src/components/channels.hpp | 4 | ||||
-rw-r--r-- | src/components/channelscellrenderer.cpp | 27 | ||||
-rw-r--r-- | src/components/chatinput.cpp | 30 | ||||
-rw-r--r-- | src/components/chatinput.hpp | 8 | ||||
-rw-r--r-- | src/components/chatinputindicator.cpp | 14 | ||||
-rw-r--r-- | src/components/chatinputindicator.hpp | 1 | ||||
-rw-r--r-- | src/components/chatlist.cpp | 24 | ||||
-rw-r--r-- | src/components/chatlist.hpp | 1 | ||||
-rw-r--r-- | src/components/chatmessage.cpp | 32 | ||||
-rw-r--r-- | src/components/chatwindow.cpp | 58 | ||||
-rw-r--r-- | src/components/chatwindow.hpp | 7 | ||||
-rw-r--r-- | src/components/completer.cpp | 3 | ||||
-rw-r--r-- | src/components/friendslist.cpp | 12 | ||||
-rw-r--r-- | src/components/memberlist.cpp | 14 |
15 files changed, 261 insertions, 78 deletions
diff --git a/src/components/channels.cpp b/src/components/channels.cpp index 0e97837..9fd4abd 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -42,7 +42,7 @@ ChannelList::ChannelList() const auto type = row[m_columns.m_type]; // text channels should not be allowed to be collapsed // maybe they should be but it seems a little difficult to handle expansion to permit this - if (type != RenderType::TextChannel) { + if (type != RenderType::TextChannel && type != RenderType::DM) { if (row[m_columns.m_expanded]) { m_view.collapse_row(path); row[m_columns.m_expanded] = false; @@ -527,6 +527,7 @@ void ChannelList::OnThreadListSync(const ThreadListSyncData &data) { #ifdef WITH_VOICE void ChannelList::OnVoiceUserConnect(Snowflake user_id, Snowflake channel_id) { auto parent_iter = GetIteratorForRowFromIDOfType(channel_id, RenderType::VoiceChannel); + if (!parent_iter) parent_iter = GetIteratorForRowFromIDOfType(channel_id, RenderType::DM); if (!parent_iter) return; const auto user = Abaddon::Get().GetDiscordClient().GetUser(user_id); if (!user.has_value()) return; @@ -863,7 +864,7 @@ Gtk::TreeModel::iterator ChannelList::CreateVoiceParticipantRow(const UserData & auto row = *m_model->append(parent); row[m_columns.m_type] = RenderType::VoiceParticipant; row[m_columns.m_id] = user.ID; - row[m_columns.m_name] = user.GetEscapedName(); + row[m_columns.m_name] = user.GetDisplayNameEscaped(); const auto voice_state = Abaddon::Get().GetDiscordClient().GetVoiceState(user.ID); if (voice_state.has_value()) { @@ -1015,20 +1016,17 @@ void ChannelList::AddPrivateChannels() { row[m_columns.m_name] = Glib::Markup::escape_text(dm->GetDisplayName()); row[m_columns.m_sort] = static_cast<int64_t>(-(dm->LastMessageID.has_value() ? *dm->LastMessageID : dm_id)); row[m_columns.m_icon] = img.GetPlaceholder(DMIconSize); + row[m_columns.m_expanded] = true; - if (dm->HasIcon()) { - const auto cb = [this, iter](const Glib::RefPtr<Gdk::Pixbuf> &pb) { - if (iter) - (*iter)[m_columns.m_icon] = pb->scale_simple(DMIconSize, DMIconSize, Gdk::INTERP_BILINEAR); - }; - img.LoadFromURL(dm->GetIconURL(), sigc::track_obj(cb, *this)); - } else if (top_recipient.has_value()) { - const auto cb = [this, iter](const Glib::RefPtr<Gdk::Pixbuf> &pb) { - if (iter) - (*iter)[m_columns.m_icon] = pb->scale_simple(DMIconSize, DMIconSize, Gdk::INTERP_BILINEAR); - }; - img.LoadFromURL(top_recipient->GetAvatarURL("png", "32"), sigc::track_obj(cb, *this)); +#ifdef WITH_VOICE + for (auto user_id : discord.GetUsersInVoiceChannel(dm_id)) { + if (const auto user = discord.GetUser(user_id); user.has_value()) { + CreateVoiceParticipantRow(*user, row->children()); + } } +#endif + + SetDMChannelIcon(iter, *dm); } } @@ -1036,11 +1034,6 @@ void ChannelList::UpdateCreateDMChannel(const ChannelData &dm) { auto header_row = m_model->get_iter(m_dm_header); auto &img = Abaddon::Get().GetImageManager(); - std::optional<UserData> top_recipient; - const auto recipients = dm.GetDMRecipients(); - if (!recipients.empty()) - top_recipient = recipients[0]; - auto iter = m_model->append(header_row->children()); auto row = *iter; row[m_columns.m_type] = RenderType::DM; @@ -1049,12 +1042,74 @@ void ChannelList::UpdateCreateDMChannel(const ChannelData &dm) { row[m_columns.m_sort] = static_cast<int64_t>(-(dm.LastMessageID.has_value() ? *dm.LastMessageID : dm.ID)); row[m_columns.m_icon] = img.GetPlaceholder(DMIconSize); - if (top_recipient.has_value()) { + SetDMChannelIcon(iter, dm); +} + +void ChannelList::SetDMChannelIcon(Gtk::TreeIter iter, const ChannelData &dm) { + auto &img = Abaddon::Get().GetImageManager(); + + std::optional<UserData> top_recipient; + const auto recipients = dm.GetDMRecipients(); + if (!recipients.empty()) + top_recipient = recipients[0]; + + if (dm.HasIcon()) { + const auto cb = [this, iter](const Glib::RefPtr<Gdk::Pixbuf> &pb) { + if (iter) + (*iter)[m_columns.m_icon] = pb->scale_simple(DMIconSize, DMIconSize, Gdk::INTERP_BILINEAR); + }; + img.LoadFromURL(dm.GetIconURL(), sigc::track_obj(cb, *this)); + } else if (dm.Type == ChannelType::DM && top_recipient.has_value()) { const auto cb = [this, iter](const Glib::RefPtr<Gdk::Pixbuf> &pb) { if (iter) (*iter)[m_columns.m_icon] = pb->scale_simple(DMIconSize, DMIconSize, Gdk::INTERP_BILINEAR); }; img.LoadFromURL(top_recipient->GetAvatarURL("png", "32"), sigc::track_obj(cb, *this)); + } else { // GROUP_DM + std::string hash; + switch (dm.ID.GetUnixMilliseconds() % 8) { + case 0: + hash = "ee9275c5a437f7dc7f9430ba95f12ebd"; + break; + case 1: + hash = "9baf45aac2a0ec2e2dab288333acb9d9"; + break; + case 2: + hash = "7ba11ffb1900fa2b088cb31324242047"; + break; + case 3: + hash = "f90fca70610c4898bc57b58bce92f587"; + break; + case 4: + hash = "e2779af34b8d9126b77420e5f09213ce"; + break; + case 5: + hash = "c6851bd0b03f1cca5a8c1e720ea6ea17"; + break; + case 6: + hash = "f7e38ac976a2a696161c923502a8345b"; + break; + case 7: + default: + hash = "3cb840d03313467838d658bbec801fcd"; + break; + } + const auto cb = [this, iter](const Glib::RefPtr<Gdk::Pixbuf> &pb) { + if (iter) + (*iter)[m_columns.m_icon] = pb->scale_simple(DMIconSize, DMIconSize, Gdk::INTERP_BILINEAR); + }; + img.LoadFromURL("https://discord.com/assets/" + hash + ".png", sigc::track_obj(cb, *this)); + } +} + +void ChannelList::RedrawUnreadIndicatorsForChannel(const ChannelData &channel) { + if (channel.GuildID.has_value()) { + auto iter = GetIteratorForGuildFromID(*channel.GuildID); + if (iter) m_model->row_changed(m_model->get_path(iter), iter); + } + if (channel.ParentID.has_value()) { + auto iter = GetIteratorForRowFromIDOfType(*channel.ParentID, RenderType::Category); + if (iter) m_model->row_changed(m_model->get_path(iter), iter); } } @@ -1064,9 +1119,8 @@ void ChannelList::OnMessageAck(const MessageAckData &data) { auto iter = GetIteratorForRowFromID(data.ChannelID); if (iter) m_model->row_changed(m_model->get_path(iter), iter); auto channel = Abaddon::Get().GetDiscordClient().GetChannel(data.ChannelID); - if (channel.has_value() && channel->GuildID.has_value()) { - iter = GetIteratorForGuildFromID(*channel->GuildID); - if (iter) m_model->row_changed(m_model->get_path(iter), iter); + if (channel.has_value()) { + RedrawUnreadIndicatorsForChannel(*channel); } } @@ -1079,9 +1133,7 @@ void ChannelList::OnMessageCreate(const Message &msg) { if (iter) (*iter)[m_columns.m_sort] = static_cast<int64_t>(-msg.ID); } - if (channel->GuildID.has_value()) - if ((iter = GetIteratorForGuildFromID(*channel->GuildID))) - m_model->row_changed(m_model->get_path(iter), iter); + RedrawUnreadIndicatorsForChannel(*channel); } bool ChannelList::OnButtonPressEvent(GdkEventButton *ev) { diff --git a/src/components/channels.hpp b/src/components/channels.hpp index 7a23b3d..9d449e4 100644 --- a/src/components/channels.hpp +++ b/src/components/channels.hpp @@ -114,10 +114,12 @@ protected: void AddPrivateChannels(); void UpdateCreateDMChannel(const ChannelData &channel); + void SetDMChannelIcon(Gtk::TreeIter iter, const ChannelData &dm); + void RedrawUnreadIndicatorsForChannel(const ChannelData& channel); void OnMessageAck(const MessageAckData &data); - void OnMessageCreate(const Message &msg); + Gtk::TreeModel::Path m_path_for_menu; // cant be recovered through selection diff --git a/src/components/channelscellrenderer.cpp b/src/components/channelscellrenderer.cpp index 6de7a00..ac98512 100644 --- a/src/components/channelscellrenderer.cpp +++ b/src/components/channelscellrenderer.cpp @@ -410,6 +410,17 @@ void CellRendererChannels::get_preferred_height_for_width_vfunc_category(Gtk::Wi m_renderer_text.get_preferred_height_for_width(widget, width, minimum_height, natural_height); } +void AddUnreadIndicator(const Cairo::RefPtr<Cairo::Context> &cr, const Gdk::Rectangle &background_area) { + static const auto color = Gdk::RGBA(Abaddon::Get().GetSettings().UnreadIndicatorColor); + cr->set_source_rgb(color.get_red(), color.get_green(), color.get_blue()); + const auto x = background_area.get_x(); + const auto y = background_area.get_y(); + const auto w = background_area.get_width(); + const auto h = background_area.get_height(); + cr->rectangle(x, y, 3, h); + cr->fill(); +} + void CellRendererChannels::render_vfunc_category(const Cairo::RefPtr<Cairo::Context> &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags) { // todo: figure out how Gtk::Arrow is rendered because i like it better :^) constexpr static int len = 5; @@ -447,13 +458,18 @@ void CellRendererChannels::render_vfunc_category(const Cairo::RefPtr<Cairo::Cont Gdk::Rectangle text_cell_area(text_x, text_y, text_w, text_h); static const auto color = Gdk::RGBA(Abaddon::Get().GetSettings().ChannelColor); - if (Abaddon::Get().GetDiscordClient().IsChannelMuted(m_property_id.get_value())) { + auto &discord = Abaddon::Get().GetDiscordClient(); + const auto id = m_property_id.get_value(); + if (discord.IsChannelMuted(m_property_id.get_value())) { auto muted = color; muted.set_red(muted.get_red() * 0.5); muted.set_green(muted.get_green() * 0.5); muted.set_blue(muted.get_blue() * 0.5); m_renderer_text.property_foreground_rgba() = muted; } else { + if (discord.GetUnreadChannelsCountForCategory(id) > 0) { + AddUnreadIndicator(cr, background_area); + } m_renderer_text.property_foreground_rgba() = color; } m_renderer_text.render(cr, widget, background_area, text_cell_area, flags); @@ -519,14 +535,7 @@ void CellRendererChannels::render_vfunc_channel(const Cairo::RefPtr<Cairo::Conte if (unread_state < 0) return; if (!is_muted) { - static const auto color = Gdk::RGBA(Abaddon::Get().GetSettings().UnreadIndicatorColor); - cr->set_source_rgb(color.get_red(), color.get_green(), color.get_blue()); - const auto x = background_area.get_x(); - const auto y = background_area.get_y(); - const auto w = background_area.get_width(); - const auto h = background_area.get_height(); - cr->rectangle(x, y, 3, h); - cr->fill(); + AddUnreadIndicator(cr, background_area); } if (unread_state < 1) return; diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index d19ac4b..1133302 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -41,9 +41,15 @@ bool ChatInputText::ProcessKeyPress(GdkEventKey *event) { return true; } +#ifdef __APPLE__ + if ((event->state & GDK_MOD2_MASK) && event->keyval == GDK_KEY_v) { + return CheckHandleClipboardPaste(); + } +#else if ((event->state & GDK_CONTROL_MASK) && event->keyval == GDK_KEY_v) { return CheckHandleClipboardPaste(); } +#endif if (event->keyval == GDK_KEY_Return) { if (event->state & GDK_SHIFT_MASK) @@ -491,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(); } @@ -559,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(); @@ -571,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() { diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index bc6a45d..a3c9742 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -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>; diff --git a/src/components/chatinputindicator.cpp b/src/components/chatinputindicator.cpp index 13315c6..0611e71 100644 --- a/src/components/chatinputindicator.cpp +++ b/src/components/chatinputindicator.cpp @@ -54,6 +54,12 @@ void ChatInputIndicator::AddUser(Snowflake channel_id, const UserData &user, int void ChatInputIndicator::SetActiveChannel(Snowflake id) { m_active_channel = id; + const auto channel = Abaddon::Get().GetDiscordClient().GetChannel(id); + if (channel.has_value()) { + m_active_guild = channel->GuildID; + } else { + m_active_guild = std::nullopt; + } ComputeTypingString(); } @@ -105,14 +111,14 @@ void ChatInputIndicator::ComputeTypingString() { if (typers.empty()) { SetTypingString(""); } else if (typers.size() == 1) { - SetTypingString(typers[0].Username + " is typing..."); + SetTypingString(typers[0].GetDisplayName(m_active_guild) + " is typing..."); } else if (typers.size() == 2) { - SetTypingString(typers[0].Username + " and " + typers[1].Username + " are typing..."); + SetTypingString(typers[0].GetDisplayName(m_active_guild) + " and " + typers[1].GetDisplayName(m_active_guild) + " are typing..."); } else if (typers.size() > 2 && typers.size() <= MaxUsersInIndicator) { Glib::ustring str; for (size_t i = 0; i < typers.size() - 1; i++) - str += typers[i].Username + ", "; - SetTypingString(str + "and " + typers[typers.size() - 1].Username + " are typing..."); + str += typers[i].GetDisplayName(m_active_guild) + ", "; + SetTypingString(str + "and " + typers[typers.size() - 1].GetDisplayName(m_active_guild) + " are typing..."); } else { // size() > MaxUsersInIndicator SetTypingString("Several people are typing..."); } diff --git a/src/components/chatinputindicator.hpp b/src/components/chatinputindicator.hpp index 40c966e..5688393 100644 --- a/src/components/chatinputindicator.hpp +++ b/src/components/chatinputindicator.hpp @@ -23,5 +23,6 @@ private: Glib::ustring m_custom_markup; Snowflake m_active_channel; + std::optional<Snowflake> m_active_guild; std::unordered_map<Snowflake, std::unordered_map<Snowflake, sigc::connection>> m_typers; // channel id -> [user id -> connection] }; diff --git a/src/components/chatlist.cpp b/src/components/chatlist.cpp index 93fb46f..ba18c0a 100644 --- a/src/components/chatlist.cpp +++ b/src/components/chatlist.cpp @@ -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] { diff --git a/src/components/chatlist.hpp b/src/components/chatlist.hpp index 0248d7f..9f12df9 100644 --- a/src/components/chatlist.hpp +++ b/src/components/chatlist.hpp @@ -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(); diff --git a/src/components/chatmessage.cpp b/src/components/chatmessage.cpp index 5f4c9a5..23ee36f 100644 --- a/src/components/chatmessage.cpp +++ b/src/components/chatmessage.cpp @@ -221,36 +221,36 @@ void ChatMessageItemContainer::UpdateTextComponent(Gtk::TextView *tv) { if (data->Mentions.empty()) break; const auto &adder = Abaddon::Get().GetDiscordClient().GetUser(data->Author.ID); const auto &added = data->Mentions[0]; - b->insert_markup(s, "<i><span color='#999999'><span color='#eeeeee'>" + adder->Username + "</span> added <span color='#eeeeee'>" + added.Username + "</span></span></i>"); + b->insert_markup(s, "<i><span color='#999999'><span color='#eeeeee'>" + adder->GetUsername() + "</span> added <span color='#eeeeee'>" + added.GetUsername() + "</span></span></i>"); } break; case MessageType::RECIPIENT_REMOVE: { if (data->Mentions.empty()) break; const auto &adder = Abaddon::Get().GetDiscordClient().GetUser(data->Author.ID); const auto &added = data->Mentions[0]; if (adder->ID == added.ID) - b->insert_markup(s, "<i><span color='#999999'><span color='#eeeeee'>" + adder->Username + "</span> left</span></i>"); + b->insert_markup(s, "<i><span color='#999999'><span color='#eeeeee'>" + adder->GetUsername() + "</span> left</span></i>"); else - b->insert_markup(s, "<i><span color='#999999'><span color='#eeeeee'>" + adder->Username + "</span> removed <span color='#eeeeee'>" + added.Username + "</span></span></i>"); + b->insert_markup(s, "<i><span color='#999999'><span color='#eeeeee'>" + adder->GetUsername() + "</span> removed <span color='#eeeeee'>" + added.GetUsername() + "</span></span></i>"); } break; case MessageType::CHANNEL_NAME_CHANGE: { const auto author = Abaddon::Get().GetDiscordClient().GetUser(data->Author.ID); - b->insert_markup(s, "<i><span color='#999999'>" + author->GetEscapedBoldName() + " changed the name to <b>" + Glib::Markup::escape_text(data->Content) + "</b></span></i>"); + b->insert_markup(s, "<i><span color='#999999'>" + author->GetDisplayNameEscapedBold() + " changed the name to <b>" + Glib::Markup::escape_text(data->Content) + "</b></span></i>"); } break; case MessageType::CHANNEL_ICON_CHANGE: { const auto author = Abaddon::Get().GetDiscordClient().GetUser(data->Author.ID); - b->insert_markup(s, "<i><span color='#999999'>" + author->GetEscapedBoldName() + " changed the channel icon</span></i>"); + b->insert_markup(s, "<i><span color='#999999'>" + author->GetDisplayNameEscapedBold() + " changed the channel icon</span></i>"); } break; case MessageType::USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1: case MessageType::USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2: case MessageType::USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3: { const auto author = Abaddon::Get().GetDiscordClient().GetUser(data->Author.ID); const auto guild = Abaddon::Get().GetDiscordClient().GetGuild(*data->GuildID); - b->insert_markup(s, "<i><span color='#999999'>" + author->GetEscapedBoldName() + " just boosted the server <b>" + Glib::Markup::escape_text(data->Content) + "</b> times! " + + b->insert_markup(s, "<i><span color='#999999'>" + author->GetDisplayNameEscapedBold() + " just boosted the server <b>" + Glib::Markup::escape_text(data->Content) + "</b> times! " + Glib::Markup::escape_text(guild->Name) + " has achieved <b>Level " + std::to_string(static_cast<int>(data->Type) - 8) + "!</b></span></i>"); // oo cheeky me !!! } break; case MessageType::CHANNEL_FOLLOW_ADD: { const auto author = Abaddon::Get().GetDiscordClient().GetUser(data->Author.ID); - b->insert_markup(s, "<i><span color='#999999'>" + author->GetEscapedBoldName() + " has added <b>" + Glib::Markup::escape_text(data->Content) + "</b> to this channel. Its most important updates will show up here.</span></i>"); + b->insert_markup(s, "<i><span color='#999999'>" + author->GetDisplayNameEscapedBold() + " has added <b>" + Glib::Markup::escape_text(data->Content) + "</b> to this channel. Its most important updates will show up here.</span></i>"); } break; case MessageType::CALL: { b->insert_markup(s, "<span color='#999999'><i>[started a call]</i></span>"); @@ -270,13 +270,13 @@ void ChatMessageItemContainer::UpdateTextComponent(Gtk::TextView *tv) { case MessageType::THREAD_CREATED: { const auto author = Abaddon::Get().GetDiscordClient().GetUser(data->Author.ID); if (data->MessageReference.has_value() && data->MessageReference->ChannelID.has_value()) { - auto iter = b->insert_markup(s, "<i><span color='#999999'>" + author->GetEscapedBoldName() + " started a thread: </span></i>"); + auto iter = b->insert_markup(s, "<i><span color='#999999'>" + author->GetDisplayNameEscapedBold() + " started a thread: </span></i>"); auto tag = b->create_tag(); tag->property_weight() = Pango::WEIGHT_BOLD; m_channel_tagmap[tag] = *data->MessageReference->ChannelID; b->insert_with_tag(iter, data->Content, tag); } else { - b->insert_markup(s, "<i><span color='#999999'>" + author->GetEscapedBoldName() + " started a thread: </span><b>" + Glib::Markup::escape_text(data->Content) + "</b></i>"); + b->insert_markup(s, "<i><span color='#999999'>" + author->GetDisplayNameEscapedBold() + " started a thread: </span><b>" + Glib::Markup::escape_text(data->Content) + "</b></i>"); } } break; default: break; @@ -656,7 +656,7 @@ Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data) if (role.has_value()) { const auto author = discord.GetUser(author_id); if (author.has_value()) { - return "<b><span color=\"#" + IntToCSSColor(role->Color) + "\">" + author->GetEscapedString() + "</span></b>"; + return "<b><span color=\"#" + IntToCSSColor(role->Color) + "\">" + author->GetDisplayNameEscaped(guild_id) + "</span></b>"; } } } @@ -664,7 +664,7 @@ Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data) const auto author = discord.GetUser(author_id); if (author.has_value()) { - return author->GetEscapedBoldString<false>(); + return author->GetDisplayNameEscapedBold(guild_id); } return "<b>Unknown User</b>"; @@ -674,8 +674,9 @@ Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data) std::optional<std::shared_ptr<Message>> referenced_message = data.ReferencedMessage; if (data.MessageReference.has_value() && data.MessageReference->MessageID.has_value() && !referenced_message.has_value()) { auto refd = discord.GetMessage(*data.MessageReference->MessageID); - if (refd.has_value()) + if (refd.has_value()) { referenced_message = std::make_shared<Message>(std::move(*refd)); + } } if (data.Interaction.has_value()) { @@ -685,7 +686,7 @@ Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data) Glib::Markup::escape_text(data.Interaction->Name) + "</span>"); } else if (const auto user = discord.GetUser(data.Interaction->User.ID); user.has_value()) { - lbl->set_markup(user->GetEscapedBoldString<false>()); + lbl->set_markup(user->GetDisplayNameEscapedBold()); } else { lbl->set_markup("<b>Unknown User</b>"); } @@ -1011,6 +1012,7 @@ ChatMessageHeader::ChatMessageHeader(const Message &data) add(m_main_box); set_margin_bottom(8); + set_focus_on_click(false); show_all(); @@ -1042,7 +1044,7 @@ void ChatMessageHeader::UpdateName() { else m_author.set_markup("<span weight='bold'>" + name + "</span>"); } else - m_author.set_markup("<span weight='bold'>" + user->GetEscapedName() + "</span>"); + m_author.set_markup("<span weight='bold'>" + user->GetDisplayNameEscaped() + "</span>"); } std::vector<Gtk::Widget *> ChatMessageHeader::GetChildContent() { @@ -1068,7 +1070,7 @@ Glib::ustring ChatMessageHeader::GetEscapedDisplayName(const UserData &user, con if (member.has_value() && !member->Nickname.empty()) return Glib::Markup::escape_text(member->Nickname); else - return Glib::Markup::escape_text(user.GetEscapedName()); + return Glib::Markup::escape_text(user.GetDisplayNameEscaped()); } bool ChatMessageHeader::on_author_button_press(GdkEventButton *ev) { diff --git a/src/components/chatwindow.cpp b/src/components/chatwindow.cpp index 1565d0c..aeed4ed 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 && !(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()) - m_input_indicator->SetCustomMarkup("Replying to " + author->GetEscapedBoldString<false>()); - else + if (author.has_value()) { + m_input_indicator->SetCustomMarkup("Replying to " + author->GetUsernameEscapedBold()); + } 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); diff --git a/src/components/chatwindow.hpp b/src/components/chatwindow.hpp index e1bb57a..b3c9d41 100644 --- a/src/components/chatwindow.hpp +++ b/src/components/chatwindow.hpp @@ -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); diff --git a/src/components/completer.cpp b/src/components/completer.cpp index c6a5a08..f6f0906 100644 --- a/src/components/completer.cpp +++ b/src/components/completer.cpp @@ -117,12 +117,13 @@ void Completer::CompleteMentions(const Glib::ustring &term) { if (id == me) continue; const auto author = discord.GetUser(id); if (!author.has_value()) continue; + // todo improve the predicate here if (!StringContainsCaseless(author->Username, term)) continue; if (i++ > 15) break; auto entry = CreateEntry(author->GetMention()); - entry->SetText(author->Username + "#" + author->Discriminator); + entry->SetText(author->GetUsername()); if (channel_id.IsValid()) { const auto chan = discord.GetChannel(channel_id); diff --git a/src/components/friendslist.cpp b/src/components/friendslist.cpp index d8a566f..99fbafa 100644 --- a/src/components/friendslist.cpp +++ b/src/components/friendslist.cpp @@ -117,16 +117,16 @@ void FriendsList::OnActionRemove(Snowflake id) { Glib::ustring str; switch (*discord.GetRelationship(id)) { case RelationshipType::Blocked: - str = "Are you sure you want to unblock " + user->Username + "#" + user->Discriminator + "?"; + str = "Are you sure you want to unblock " + user->GetUsername() + "?"; break; case RelationshipType::Friend: - str = "Are you sure you want to remove " + user->Username + "#" + user->Discriminator + "?"; + str = "Are you sure you want to remove " + user->GetUsername() + "?"; break; case RelationshipType::PendingIncoming: - str = "Are you sure you want to ignore " + user->Username + "#" + user->Discriminator + "?"; + str = "Are you sure you want to ignore " + user->GetUsername() + "?"; break; case RelationshipType::PendingOutgoing: - str = "Are you sure you want to cancel your request to " + user->Username + "#" + user->Discriminator + "?"; + str = "Are you sure you want to cancel your request to " + user->GetUsername() + "?"; break; default: break; @@ -244,7 +244,7 @@ bool FriendsListAddComponent::OnKeyPress(GdkEventKey *e) { FriendsListFriendRow::FriendsListFriendRow(RelationshipType type, const UserData &data) : ID(data.ID) , Type(type) - , Name(data.Username + "#" + data.Discriminator) + , Name(data.GetUsername()) , Status(Abaddon::Get().GetDiscordClient().GetUserStatus(data.ID)) , m_accept("Accept") { auto *ev = Gtk::manage(new Gtk::EventBox); @@ -265,7 +265,7 @@ FriendsListFriendRow::FriendsListFriendRow(RelationshipType type, const UserData img->SetURL(data.GetAvatarURL("png", "32")); } - namelbl->set_markup(data.GetEscapedBoldName()); + namelbl->set_markup(data.GetDisplayNameEscapedBold()); UpdatePresenceLabel(); diff --git a/src/components/memberlist.cpp b/src/components/memberlist.cpp index 19b4fb8..975b527 100644 --- a/src/components/memberlist.cpp +++ b/src/components/memberlist.cpp @@ -37,9 +37,17 @@ MemberListUserRow::MemberListUserRow(const std::optional<GuildData> &guild, cons m_label->set_single_line_mode(true); m_label->set_ellipsize(Pango::ELLIPSIZE_END); - std::string display = data.Username; - if (Abaddon::Get().GetSettings().ShowMemberListDiscriminators) - display += "#" + data.Discriminator; + // todo remove after migration complete + std::string display; + if (data.IsPomelo()) { + display = data.GetDisplayName(guild.has_value() ? guild->ID : Snowflake::Invalid); + } else { + display = data.Username; + if (Abaddon::Get().GetSettings().ShowMemberListDiscriminators) { + display += "#" + data.Discriminator; + } + } + if (guild.has_value()) { if (const auto col_id = data.GetHoistedRole(guild->ID, true); col_id.IsValid()) { auto color = Abaddon::Get().GetDiscordClient().GetRole(col_id)->Color; |