From eb0feef51166eeabd58993c484e85e0739285aa1 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 10 Nov 2020 01:38:44 -0500 Subject: [PATCH] use textviews in channel list + parse emojis --- components/channels.cpp | 98 +++++++++++++++++++++++++++++++++----- components/channels.hpp | 15 ++++-- components/chatmessage.cpp | 33 +------------ components/chatwindow.cpp | 2 +- css/main.css | 4 ++ discord/guild.cpp | 14 ++++-- emojis.cpp | 37 ++++++++++++++ emojis.hpp | 1 + 8 files changed, 151 insertions(+), 53 deletions(-) diff --git a/components/channels.cpp b/components/channels.cpp index adcfe13..8516f94 100644 --- a/components/channels.cpp +++ b/components/channels.cpp @@ -9,6 +9,24 @@ void ChannelListRow::Collapse() {} void ChannelListRow::Expand() {} +void ChannelListRow::MakeReadOnly(Gtk::TextView *tv) { + tv->set_can_focus(false); + tv->set_editable(false); + tv->signal_realize().connect([tv]() { + auto window = tv->get_window(Gtk::TEXT_WINDOW_TEXT); + auto display = window->get_display(); + auto cursor = Gdk::Cursor::create(display, "default"); // textview uses "text" which looks out of place + window->set_cursor(cursor); + }); + // stupid hack to prevent selection + auto buf = tv->get_buffer(); + buf->property_has_selection().signal_changed().connect([tv, buf]() { + Gtk::TextBuffer::iterator a, b; + buf->get_bounds(a, b); + buf->select_range(a, a); + }); +} + ChannelListRowDMHeader::ChannelListRowDMHeader() { m_ev = Gtk::manage(new Gtk::EventBox); m_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL)); @@ -31,7 +49,8 @@ ChannelListRowDMChannel::ChannelListRowDMChannel(const Channel *data) { ID = data->ID; m_ev = Gtk::manage(new Gtk::EventBox); m_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL)); - m_lbl = Gtk::manage(new Gtk::Label); + m_lbl = Gtk::manage(new Gtk::TextView); + MakeReadOnly(m_lbl); get_style_context()->add_class("channel-row"); m_lbl->get_style_context()->add_class("channel-row-label"); @@ -50,10 +69,12 @@ ChannelListRowDMChannel::ChannelListRowDMChannel(const Channel *data) { } } + auto buf = m_lbl->get_buffer(); if (data->Type == ChannelType::DM) - m_lbl->set_text(data->Recipients[0].Username); + buf->set_text(data->Recipients[0].Username); else if (data->Type == ChannelType::GROUP_DM) - m_lbl->set_text(std::to_string(data->Recipients.size()) + " users"); + buf->set_text(std::to_string(data->Recipients.size()) + " users"); + Abaddon::Get().GetEmojis().ReplaceEmojis(buf, ChannelEmojiSize); m_box->set_halign(Gtk::ALIGN_START); if (m_icon != nullptr) @@ -73,7 +94,8 @@ ChannelListRowGuild::ChannelListRowGuild(const Guild *data) { ID = data->ID; m_ev = Gtk::manage(new Gtk::EventBox); m_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL)); - m_lbl = Gtk::manage(new Gtk::Label); + m_lbl = Gtk::manage(new Gtk::TextView); + MakeReadOnly(m_lbl); if (data->HasIcon()) { auto buf = Abaddon::Get().GetImageManager().GetFromURLIfCached(data->GetIconURL("png", "32")); @@ -91,7 +113,11 @@ ChannelListRowGuild::ChannelListRowGuild(const Guild *data) { get_style_context()->add_class("channel-row-guild"); m_lbl->get_style_context()->add_class("channel-row-label"); - m_lbl->set_markup("" + Glib::Markup::escape_text(data->Name) + ""); + auto buf = m_lbl->get_buffer(); + Gtk::TextBuffer::iterator start, end; + buf->get_bounds(start, end); + buf->insert_markup(start, "" + Glib::Markup::escape_text(data->Name) + ""); + Abaddon::Get().GetEmojis().ReplaceEmojis(buf, ChannelEmojiSize); m_box->set_halign(Gtk::ALIGN_START); m_box->pack_start(*m_icon); m_box->pack_start(*m_lbl); @@ -108,14 +134,17 @@ ChannelListRowCategory::ChannelListRowCategory(const Channel *data) { ID = data->ID; m_ev = Gtk::manage(new Gtk::EventBox); m_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL)); - m_lbl = Gtk::manage(new Gtk::Label); + m_lbl = Gtk::manage(new Gtk::TextView); + MakeReadOnly(m_lbl); m_arrow = Gtk::manage(new Gtk::Arrow(Gtk::ARROW_DOWN, Gtk::SHADOW_NONE)); get_style_context()->add_class("channel-row"); get_style_context()->add_class("channel-row-category"); m_lbl->get_style_context()->add_class("channel-row-label"); - m_lbl->set_text(data->Name); + auto buf = m_lbl->get_buffer(); + buf->set_text(data->Name); + Abaddon::Get().GetEmojis().ReplaceEmojis(buf, ChannelEmojiSize); m_box->set_halign(Gtk::ALIGN_START); m_box->pack_start(*m_arrow); m_box->pack_start(*m_lbl); @@ -136,13 +165,16 @@ ChannelListRowChannel::ChannelListRowChannel(const Channel *data) { ID = data->ID; m_ev = Gtk::manage(new Gtk::EventBox); m_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL)); - m_lbl = Gtk::manage(new Gtk::Label); + m_lbl = Gtk::manage(new Gtk::TextView); + MakeReadOnly(m_lbl); get_style_context()->add_class("channel-row"); get_style_context()->add_class("channel-row-channel"); m_lbl->get_style_context()->add_class("channel-row-label"); - m_lbl->set_text("#" + data->Name); + auto buf = m_lbl->get_buffer(); + buf->set_text("#" + data->Name); + Abaddon::Get().GetEmojis().ReplaceEmojis(buf, ChannelEmojiSize); m_box->set_halign(Gtk::ALIGN_START); m_box->pack_start(*m_lbl); m_ev->add(*m_box); @@ -189,6 +221,18 @@ ChannelList::ChannelList() { m_main->show_all(); m_update_dispatcher.connect(sigc::mem_fun(*this, &ChannelList::UpdateListingInternal)); + + // maybe will regret doing it this way + auto &discord = Abaddon::Get().GetDiscordClient(); + discord.signal_message_create().connect(sigc::track_obj([this, &discord](Snowflake message_id) { + const auto *message = discord.GetMessage(message_id); + const auto *channel = discord.GetChannel(message->ChannelID); + if (channel == nullptr) return; + if (channel->Type == ChannelType::DM || channel->Type == ChannelType::GROUP_DM) + CheckBumpDM(message->ChannelID); + // clang-format off + }, this)); + // clang-format on } Gtk::Widget *ChannelList::GetRoot() const { @@ -462,6 +506,7 @@ void ChannelList::DeleteRow(ChannelListRow *row) { m_id_to_row.erase(row->ID); delete row; } + void ChannelList::on_row_activated(Gtk::ListBoxRow *tmprow) { auto row = dynamic_cast(tmprow); if (row == nullptr) return; @@ -556,7 +601,14 @@ void ChannelList::InsertGuildAt(Snowflake id, int pos) { } void ChannelList::AddPrivateChannels() { - auto dms = Abaddon::Get().GetDiscordClient().GetPrivateChannels(); + auto dms_ = Abaddon::Get().GetDiscordClient().GetPrivateChannels(); + std::vector dms; + const auto &discord = Abaddon::Get().GetDiscordClient(); + for (const auto &x : dms_) + dms.push_back(discord.GetChannel(x)); + std::sort(dms.begin(), dms.end(), [&](const Channel *a, const Channel *b) -> bool { + return a->LastMessageID > b->LastMessageID; + }); m_dm_header_row = Gtk::manage(new ChannelListRowDMHeader); m_dm_header_row->show_all(); @@ -564,7 +616,9 @@ void ChannelList::AddPrivateChannels() { m_list->add(*m_dm_header_row); for (const auto &dm : dms) { - auto *dm_row = Gtk::manage(new ChannelListRowDMChannel(Abaddon::Get().GetDiscordClient().GetChannel(dm))); + auto *dm_row = Gtk::manage(new ChannelListRowDMChannel(dm)); + dm_row->Parent = m_dm_header_row; + m_dm_id_to_row[dm->ID] = dm_row; dm_row->IsUserCollapsed = false; m_list->add(*dm_row); m_dm_header_row->Children.insert(dm_row); @@ -660,6 +714,28 @@ void ChannelList::AttachChannelMenuHandler(Gtk::ListBoxRow *row) { }); } +void ChannelList::CheckBumpDM(Snowflake channel_id) { + auto it = m_dm_id_to_row.find(channel_id); + if (it == m_dm_id_to_row.end()) return; + auto *row = it->second; + const auto index = row->get_index(); + if (index == 1) return; // 1 is top of dm list + const bool selected = row->is_selected(); + row->Parent->Children.erase(row); + delete row; + auto *dm_row = Gtk::manage(new ChannelListRowDMChannel(Abaddon::Get().GetDiscordClient().GetChannel(channel_id))); + dm_row->Parent = m_dm_header_row; + m_dm_header_row->Children.insert(dm_row); + m_dm_id_to_row[channel_id] = dm_row; + dm_row->IsUserCollapsed = false; + m_list->insert(*dm_row, 1); + m_dm_header_row->Children.insert(dm_row); + if (selected) + m_list->select_row(*dm_row); + if (m_dm_header_row->is_visible() && !m_dm_header_row->IsUserCollapsed) + dm_row->show(); +} + ChannelList::type_signal_action_channel_item_select ChannelList::signal_action_channel_item_select() { return m_signal_action_channel_item_select; } diff --git a/components/channels.hpp b/components/channels.hpp index 0326841..485cb5f 100644 --- a/components/channels.hpp +++ b/components/channels.hpp @@ -8,6 +8,8 @@ #include #include "../discord/discord.hpp" +static const constexpr int ChannelEmojiSize = 16; + class ChannelListRow : public Gtk::ListBoxRow { public: bool IsUserCollapsed; @@ -17,6 +19,8 @@ public: virtual void Collapse(); virtual void Expand(); + + static void MakeReadOnly(Gtk::TextView *tv); }; class ChannelListRowDMHeader : public ChannelListRow { @@ -38,7 +42,7 @@ protected: Gtk::EventBox *m_ev; Gtk::Box *m_box; - Gtk::Label *m_lbl; + Gtk::TextView *m_lbl; Gtk::Image *m_icon = nullptr; }; @@ -53,7 +57,7 @@ protected: Gtk::EventBox *m_ev; Gtk::Box *m_box; - Gtk::Label *m_lbl; + Gtk::TextView *m_lbl; Gtk::Image *m_icon; }; @@ -67,7 +71,7 @@ public: protected: Gtk::EventBox *m_ev; Gtk::Box *m_box; - Gtk::Label *m_lbl; + Gtk::TextView *m_lbl; Gtk::Arrow *m_arrow; }; @@ -78,7 +82,7 @@ public: protected: Gtk::EventBox *m_ev; Gtk::Box *m_box; - Gtk::Label *m_lbl; + Gtk::TextView *m_lbl; }; class ChannelList { @@ -133,6 +137,7 @@ protected: // i would use one map but in really old guilds there can be a channel w/ same id as the guild so this hacky shit has to do std::unordered_map m_guild_id_to_row; std::unordered_map m_id_to_row; + std::unordered_map m_dm_id_to_row; void InsertGuildAt(Snowflake id, int pos); @@ -141,6 +146,8 @@ protected: void AttachGuildMenuHandler(Gtk::ListBoxRow *row); void AttachChannelMenuHandler(Gtk::ListBoxRow *row); + void CheckBumpDM(Snowflake channel_id); + public: typedef sigc::signal type_signal_action_channel_item_select; typedef sigc::signal type_signal_action_guild_move_up; diff --git a/components/chatmessage.cpp b/components/chatmessage.cpp index f528e37..a36b67f 100644 --- a/components/chatmessage.cpp +++ b/components/chatmessage.cpp @@ -451,38 +451,7 @@ void ChatMessageItemContainer::HandleUserMentions(Gtk::TextView *tv) { } void ChatMessageItemContainer::HandleStockEmojis(Gtk::TextView *tv) { - auto buf = tv->get_buffer(); - auto text = GetText(buf); - - auto &emojis = Abaddon::Get().GetEmojis(); - int searchpos; - for (const auto &pattern : emojis.GetPatterns()) { - searchpos = 0; - Glib::RefPtr pixbuf; - while (true) { - size_t r = text.find(pattern, searchpos); - if (r == Glib::ustring::npos) break; - if (!pixbuf) { - pixbuf = emojis.GetPixBuf(pattern); - if (pixbuf) - pixbuf = pixbuf->scale_simple(24, 24, Gdk::INTERP_BILINEAR); - else - break; - } - searchpos = r + pattern.size(); - - const auto start_it = buf->get_iter_at_offset(r); - const auto end_it = buf->get_iter_at_offset(r + pattern.size()); - - auto it = buf->erase(start_it, end_it); - buf->insert_pixbuf(it, pixbuf); - - int alen = text.size(); - text = GetText(buf); - int blen = text.size(); - searchpos -= (alen - blen); - } - } + Abaddon::Get().GetEmojis().ReplaceEmojis(tv->get_buffer()); } void ChatMessageItemContainer::HandleCustomEmojis(Gtk::TextView *tv) { diff --git a/components/chatwindow.cpp b/components/chatwindow.cpp index d447177..6d7edca 100644 --- a/components/chatwindow.cpp +++ b/components/chatwindow.cpp @@ -49,7 +49,7 @@ ChatWindow::ChatWindow() { m_input->set_halign(Gtk::ALIGN_FILL); m_input->set_wrap_mode(Gtk::WRAP_WORD_CHAR); - m_input_scroll->set_max_content_height(170); + m_input_scroll->set_max_content_height(200); m_input_scroll->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC); m_input_scroll->add(*m_input); diff --git a/css/main.css b/css/main.css index 77e16bf..99b4ff8 100644 --- a/css/main.css +++ b/css/main.css @@ -21,7 +21,11 @@ .channel-row-label { padding: 5px; +} + +.channel-row-label, .channel-row-label text { color: #cfd8dc; + background: rgba(0, 0, 0, 0); } .channel-row:focus { diff --git a/discord/guild.cpp b/discord/guild.cpp index 94268d8..054a2c3 100644 --- a/discord/guild.cpp +++ b/discord/guild.cpp @@ -14,10 +14,10 @@ void from_json(const nlohmann::json &j, Guild &m) { JS_ON("discovery_splash", m.DiscoverySplash); JS_O("owner", m.IsOwner); JS_D("owner_id", m.OwnerID); - std::string tmp; + std::optional tmp; JS_O("permissions", tmp); - if (tmp != "") - m.Permissions = std::stoull(tmp); + if (tmp.has_value()) + m.Permissions = std::stoull(*tmp); JS_D("region", m.VoiceRegion); JS_N("afk_channel_id", m.AFKChannelID); JS_D("afk_timeout", m.AFKTimeout); @@ -54,8 +54,12 @@ void from_json(const nlohmann::json &j, Guild &m) { JS_D("preferred_locale", m.PreferredLocale); JS_N("public_updates_channel_id", m.PublicUpdatesChannelID); JS_O("max_video_channel_users", m.MaxVideoChannelUsers); - JS_O("approximate_member_count", m.ApproximateMemberCount); - JS_O("approximate_presence_count", m.ApproximatePresenceCount); + JS_O("approximate_member_count", tmp); + if (tmp.has_value()) + m.ApproximateMemberCount = std::stoull(*tmp); + JS_O("approximate_presence_count", tmp); + if (tmp.has_value()) + m.ApproximatePresenceCount = std::stoull(*tmp); } void Guild::update_from_json(const nlohmann::json &j) { diff --git a/emojis.cpp b/emojis.cpp index 5392ee8..6e9a293 100644 --- a/emojis.cpp +++ b/emojis.cpp @@ -72,6 +72,43 @@ Glib::ustring EmojiResource::HexToPattern(Glib::ustring hex) { } return ret; } +void EmojiResource::ReplaceEmojis(Glib::RefPtr buf, int size) { + auto get_text = [&]() -> auto { + Gtk::TextBuffer::iterator a, b; + buf->get_bounds(a, b); + return buf->get_slice(a, b, true); + }; + auto text = get_text(); + + int searchpos; + for (const auto &pattern : m_patterns) { + searchpos = 0; + Glib::RefPtr pixbuf; + while (true) { + size_t r = text.find(pattern, searchpos); + if (r == Glib::ustring::npos) break; + if (!pixbuf) { + pixbuf = GetPixBuf(pattern); + if (pixbuf) + pixbuf = pixbuf->scale_simple(size, size, Gdk::INTERP_BILINEAR); + else + break; + } + searchpos = r + pattern.size(); + + const auto start_it = buf->get_iter_at_offset(r); + const auto end_it = buf->get_iter_at_offset(r + pattern.size()); + + auto it = buf->erase(start_it, end_it); + buf->insert_pixbuf(it, pixbuf); + + int alen = text.size(); + text = get_text(); + int blen = text.size(); + searchpos -= (alen - blen); + } + } +} const std::vector &EmojiResource::GetPatterns() const { return m_patterns; diff --git a/emojis.hpp b/emojis.hpp index b3c2925..b84feb7 100644 --- a/emojis.hpp +++ b/emojis.hpp @@ -15,6 +15,7 @@ public: static Glib::ustring PatternToHex(const Glib::ustring &pattern); static Glib::ustring HexToPattern(Glib::ustring hex); const std::vector &GetPatterns() const; + void ReplaceEmojis(Glib::RefPtr buf, int size = 24); private: std::unordered_map> m_index; // pattern -> [pos, len]