From af567847970121765674dc8d0542b9c4a1f89ed1 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 5 Dec 2021 03:57:26 -0500 Subject: basic unread indicators for channels --- src/components/channelscellrenderer.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src/components/channelscellrenderer.cpp') diff --git a/src/components/channelscellrenderer.cpp b/src/components/channelscellrenderer.cpp index 2526753..f4cd33c 100644 --- a/src/components/channelscellrenderer.cpp +++ b/src/components/channelscellrenderer.cpp @@ -1,11 +1,13 @@ #include "channelscellrenderer.hpp" #include "abaddon.hpp" #include +#include "unreadrenderer.hpp" CellRendererChannels::CellRendererChannels() : Glib::ObjectBase(typeid(CellRendererChannels)) , Gtk::CellRenderer() , m_property_type(*this, "render-type") + , m_property_id(*this, "id") , m_property_name(*this, "name") , m_property_pixbuf(*this, "pixbuf") , m_property_pixbuf_animation(*this, "pixbuf-animation") @@ -26,6 +28,10 @@ Glib::PropertyProxy CellRendererChannels::property_type() { return m_property_type.get_proxy(); } +Glib::PropertyProxy CellRendererChannels::property_id() { + return m_property_id.get_proxy(); +} + Glib::PropertyProxy CellRendererChannels::property_name() { return m_property_name.get_proxy(); } @@ -328,6 +334,8 @@ void CellRendererChannels::render_vfunc_channel(const Cairo::RefPtr Date: Thu, 9 Dec 2021 02:54:59 -0500 Subject: rudimentary guild unread indicator --- src/components/channels.cpp | 5 +++++ src/components/channelscellrenderer.cpp | 4 +++- src/components/unreadrenderer.cpp | 30 ++++++++++++++++++++++++++---- src/components/unreadrenderer.hpp | 1 + 4 files changed, 35 insertions(+), 5 deletions(-) (limited to 'src/components/channelscellrenderer.cpp') diff --git a/src/components/channels.cpp b/src/components/channels.cpp index 4d3d5a2..d8fbde9 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -701,6 +701,11 @@ void ChannelList::OnMessageAck(const MessageAckData &data) { // trick renderer into redrawing auto iter = GetIteratorForChannelFromID(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); + } } void ChannelList::OnMessageCreate(const Message &msg) { diff --git a/src/components/channelscellrenderer.cpp b/src/components/channelscellrenderer.cpp index f4cd33c..a96668c 100644 --- a/src/components/channelscellrenderer.cpp +++ b/src/components/channelscellrenderer.cpp @@ -198,7 +198,7 @@ void CellRendererChannels::render_vfunc_guild(const Cairo::RefPtrrectangle(icon_x, icon_y, icon_w, icon_h); cr->fill(); } + + UnreadRenderer::RenderUnreadOnGuild(m_property_id.get_value(), cr, background_area, cell_area); } // category diff --git a/src/components/unreadrenderer.cpp b/src/components/unreadrenderer.cpp index 4e508fc..b53af2d 100644 --- a/src/components/unreadrenderer.cpp +++ b/src/components/unreadrenderer.cpp @@ -1,14 +1,36 @@ #include "unreadrenderer.hpp" #include "abaddon.hpp" +void UnreadRenderer::RenderUnreadOnGuild(Snowflake id, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area) { + // maybe have DiscordClient track this? + auto &discord = Abaddon::Get().GetDiscordClient(); + const auto channels = discord.GetChannelsInGuild(id); + bool has_unread = false; + for (const auto &id : channels) { + if (Abaddon::Get().GetDiscordClient().GetUnreadStateForChannel(id) >= 0) { + has_unread = true; + break; + } + } + if (!has_unread) return; + + cr->set_source_rgb(1.0, 1.0, 1.0); + 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 + h / 2 - 24 / 2, 3, 24); + cr->fill(); +} + void UnreadRenderer::RenderUnreadOnChannel(Snowflake id, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area) { const auto state = Abaddon::Get().GetDiscordClient().GetUnreadStateForChannel(id); if (state >= 0) { cr->set_source_rgb(1.0, 1.0, 1.0); - const auto x = cell_area.get_x() + 1; - const auto y = cell_area.get_y(); - const auto w = cell_area.get_width(); - const auto h = cell_area.get_height(); + 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(); } diff --git a/src/components/unreadrenderer.hpp b/src/components/unreadrenderer.hpp index e333543..30446fa 100644 --- a/src/components/unreadrenderer.hpp +++ b/src/components/unreadrenderer.hpp @@ -5,5 +5,6 @@ class UnreadRenderer { public: + static void RenderUnreadOnGuild(Snowflake id, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area); static void RenderUnreadOnChannel(Snowflake id, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area); }; -- cgit v1.2.3 From 0b0135268ec4684dfcdc0a5ae5c8da947bb17e12 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 10 Dec 2021 00:15:39 -0500 Subject: basic channel mentions count indicator --- src/components/channels.cpp | 8 ++++++ src/components/channels.hpp | 4 +++ src/components/channelscellrenderer.cpp | 4 +-- src/components/unreadrenderer.cpp | 43 +++++++++++++++++++++++++-------- src/components/unreadrenderer.hpp | 5 ++-- src/discord/discord.cpp | 7 +++--- src/discord/message.cpp | 6 +++++ src/discord/message.hpp | 2 ++ src/windows/mainwindow.cpp | 1 + 9 files changed, 63 insertions(+), 17 deletions(-) (limited to 'src/components/channelscellrenderer.cpp') diff --git a/src/components/channels.cpp b/src/components/channels.cpp index d8fbde9..c83b5f8 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -176,6 +176,14 @@ ChannelList::ChannelList() discord.signal_message_ack().connect(sigc::mem_fun(*this, &ChannelList::OnMessageAck)); } +void ChannelList::UsePanedHack(Gtk::Paned& paned) { + paned.property_position().signal_changed().connect(sigc::mem_fun(*this, &ChannelList::OnPanedPositionChanged)); +} + +void ChannelList::OnPanedPositionChanged() { + m_view.queue_draw(); +} + void ChannelList::UpdateListing() { m_updating_listing = true; diff --git a/src/components/channels.hpp b/src/components/channels.hpp index d279907..2f4d045 100644 --- a/src/components/channels.hpp +++ b/src/components/channels.hpp @@ -25,7 +25,11 @@ public: void UseExpansionState(const ExpansionStateRoot &state); ExpansionStateRoot GetExpansionState() const; + void UsePanedHack(Gtk::Paned &paned); + protected: + void OnPanedPositionChanged(); + void UpdateNewGuild(const GuildData &guild); void UpdateRemoveGuild(Snowflake id); void UpdateRemoveChannel(Snowflake id); diff --git a/src/components/channelscellrenderer.cpp b/src/components/channelscellrenderer.cpp index a96668c..f507802 100644 --- a/src/components/channelscellrenderer.cpp +++ b/src/components/channelscellrenderer.cpp @@ -240,7 +240,7 @@ void CellRendererChannels::render_vfunc_guild(const Cairo::RefPtrfill(); } - UnreadRenderer::RenderUnreadOnGuild(m_property_id.get_value(), cr, background_area, cell_area); + UnreadRenderer::RenderUnreadOnGuild(m_property_id.get_value(), widget, cr, background_area, cell_area); } // category @@ -337,7 +337,7 @@ void CellRendererChannels::render_vfunc_channel(const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area) { +void UnreadRenderer::RenderUnreadOnGuild(Snowflake id, Gtk::Widget &widget, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area) { // maybe have DiscordClient track this? auto &discord = Abaddon::Get().GetDiscordClient(); const auto channels = discord.GetChannelsInGuild(id); @@ -23,15 +23,38 @@ void UnreadRenderer::RenderUnreadOnGuild(Snowflake id, const Cairo::RefPtrfill(); } -void UnreadRenderer::RenderUnreadOnChannel(Snowflake id, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area) { +void UnreadRenderer::RenderUnreadOnChannel(Snowflake id, Gtk::Widget &widget, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area) { const auto state = Abaddon::Get().GetDiscordClient().GetUnreadStateForChannel(id); - if (state >= 0) { - cr->set_source_rgb(1.0, 1.0, 1.0); - 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(); + if (state < 0) return; + cr->set_source_rgb(1.0, 1.0, 1.0); + 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(); + + if (state < 1) return; + auto *paned = static_cast(widget.get_ancestor(Gtk::Paned::get_type())); + const auto edge = paned->get_position(); + + // surely this shouldnt be run every draw? + // https://developer-old.gnome.org/gtkmm-tutorial/3.22/sec-drawing-text.html.en + + Pango::FontDescription font; + font.set_family("sans 14"); + font.set_weight(Pango::WEIGHT_BOLD); + + auto layout = widget.create_pango_layout(std::to_string(state)); + layout->set_font_description(font); + layout->set_alignment(Pango::ALIGN_RIGHT); + + int width, height; + layout->get_pixel_size(width, height); + { + const auto x = cell_area.get_x() + std::min(edge, cell_area.get_width()) - 14 - 2; + const auto y = cell_area.get_y() + cell_area.get_height() / 2.0 - height / 2.0; + cr->move_to(x, y); + layout->show_in_cairo_context(cr); } } diff --git a/src/components/unreadrenderer.hpp b/src/components/unreadrenderer.hpp index 30446fa..1b4ddc2 100644 --- a/src/components/unreadrenderer.hpp +++ b/src/components/unreadrenderer.hpp @@ -1,10 +1,11 @@ #pragma once #include #include +#include #include "discord/snowflake.hpp" class UnreadRenderer { public: - static void RenderUnreadOnGuild(Snowflake id, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area); - static void RenderUnreadOnChannel(Snowflake id, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area); + static void RenderUnreadOnGuild(Snowflake id, Gtk::Widget &widget, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area); + static void RenderUnreadOnChannel(Snowflake id, Gtk::Widget &widget, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area); }; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index a8ceb5b..f21946f 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1467,9 +1467,10 @@ void DiscordClient::HandleGatewayMessageCreate(const GatewayMessage &msg) { if (data.GuildID.has_value()) AddUserToGuild(data.Author.ID, *data.GuildID); m_last_message_id[data.ChannelID] = data.ID; - const auto iter = m_unread.find(data.ChannelID); - if (iter == m_unread.end()) - m_unread[data.ChannelID] = 0; + m_unread[data.ChannelID]; + if (data.DoesMention(GetUserData().ID)) { + m_unread[data.ChannelID]++; + } m_signal_message_create.emit(data); } diff --git a/src/discord/message.cpp b/src/discord/message.cpp index 70c557d..93d57c2 100644 --- a/src/discord/message.cpp +++ b/src/discord/message.cpp @@ -263,3 +263,9 @@ bool Message::IsDeleted() const { bool Message::IsEdited() const { return m_edited; } + +bool Message::DoesMention(Snowflake id) const noexcept { + return std::any_of(Mentions.begin(), Mentions.end(), [id](const UserData &user) { + return user.ID == id; + }); +} diff --git a/src/discord/message.hpp b/src/discord/message.hpp index 56f4c0f..244b572 100644 --- a/src/discord/message.hpp +++ b/src/discord/message.hpp @@ -212,6 +212,8 @@ struct Message { bool IsDeleted() const; bool IsEdited() const; + bool DoesMention(Snowflake id) const noexcept; + private: bool m_deleted = false; bool m_edited = false; diff --git a/src/windows/mainwindow.cpp b/src/windows/mainwindow.cpp index 3fc0b0d..d171a03 100644 --- a/src/windows/mainwindow.cpp +++ b/src/windows/mainwindow.cpp @@ -153,6 +153,7 @@ MainWindow::MainWindow() m_chan_content_paned.set_position(200); m_chan_content_paned.show(); m_content_box.add(m_chan_content_paned); + m_channel_list.UsePanedHack(m_chan_content_paned); m_content_members_paned.pack1(m_content_stack); m_content_members_paned.pack2(*member_list); -- cgit v1.2.3 From f580535d3570de59125cc377b01188601c82b5b9 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 18 Dec 2021 01:58:29 -0500 Subject: add mute/unmute channel menu item --- src/components/channels.cpp | 24 +++++++++++++++++++-- src/components/channels.hpp | 2 ++ src/components/channelscellrenderer.cpp | 2 +- src/discord/discord.cpp | 33 +++++++++++++++++++++++++++- src/discord/discord.hpp | 2 ++ src/discord/objects.cpp | 38 +++++++++++++++++++++++++++++++-- src/discord/objects.hpp | 16 +++++++++++--- 7 files changed, 108 insertions(+), 9 deletions(-) (limited to 'src/components/channelscellrenderer.cpp') diff --git a/src/components/channels.cpp b/src/components/channels.cpp index d49afca..76fd69d 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -1,8 +1,8 @@ -#include "channels.hpp" #include "abaddon.hpp" +#include "channels.hpp" #include "imgmanager.hpp" -#include "util.hpp" #include "statusindicator.hpp" +#include "util.hpp" #include #include #include @@ -117,7 +117,16 @@ ChannelList::ChannelList() m_menu_channel_mark_as_read.signal_activate().connect([this] { Abaddon::Get().GetDiscordClient().MarkChannelAsRead(static_cast((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]), [](...) {}); }); + m_menu_channel_toggle_mute.signal_activate().connect([this] { + const auto id = static_cast((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]); + auto &discord = Abaddon::Get().GetDiscordClient(); + if (discord.IsChannelMuted(id)) + discord.UnmuteChannel(id, [](...) {}); + else + discord.MuteChannel(id, [](...) {}); + }); m_menu_channel.append(m_menu_channel_mark_as_read); + m_menu_channel.append(m_menu_channel_toggle_mute); m_menu_channel.append(m_menu_channel_copy_id); m_menu_channel.show_all(); @@ -158,6 +167,7 @@ ChannelList::ChannelList() m_menu_thread.append(m_menu_thread_unarchive); m_menu_thread.show_all(); + m_menu_channel.signal_popped_up().connect(sigc::mem_fun(*this, &ChannelList::OnChannelSubmenuPopup)); m_menu_thread.signal_popped_up().connect(sigc::mem_fun(*this, &ChannelList::OnThreadSubmenuPopup)); auto &discord = Abaddon::Get().GetDiscordClient(); @@ -805,6 +815,16 @@ void ChannelList::MoveRow(const Gtk::TreeModel::iterator &iter, const Gtk::TreeM m_model->erase(iter); } +void ChannelList::OnChannelSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y) { + const auto iter = m_model->get_iter(m_path_for_menu); + if (!iter) return; + const auto id = static_cast((*iter)[m_columns.m_id]); + if (Abaddon::Get().GetDiscordClient().IsChannelMuted(id)) + m_menu_channel_toggle_mute.set_label("Unmute"); + else + m_menu_channel_toggle_mute.set_label("Mute"); +} + void ChannelList::OnThreadSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y) { m_menu_thread_archive.set_visible(false); m_menu_thread_unarchive.set_visible(false); diff --git a/src/components/channels.hpp b/src/components/channels.hpp index 16e6360..bc7dfc2 100644 --- a/src/components/channels.hpp +++ b/src/components/channels.hpp @@ -115,6 +115,7 @@ protected: Gtk::Menu m_menu_channel; Gtk::MenuItem m_menu_channel_copy_id; Gtk::MenuItem m_menu_channel_mark_as_read; + Gtk::MenuItem m_menu_channel_toggle_mute; Gtk::Menu m_menu_dm; Gtk::MenuItem m_menu_dm_copy_id; @@ -126,6 +127,7 @@ protected: Gtk::MenuItem m_menu_thread_archive; Gtk::MenuItem m_menu_thread_unarchive; + void OnChannelSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y); void OnThreadSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y); bool m_updating_listing = false; diff --git a/src/components/channelscellrenderer.cpp b/src/components/channelscellrenderer.cpp index f507802..df9f03d 100644 --- a/src/components/channelscellrenderer.cpp +++ b/src/components/channelscellrenderer.cpp @@ -1,7 +1,7 @@ #include "channelscellrenderer.hpp" #include "abaddon.hpp" -#include #include "unreadrenderer.hpp" +#include CellRendererChannels::CellRendererChannels() : Glib::ObjectBase(typeid(CellRendererChannels)) diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 9d711c8..1872985 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1,9 +1,11 @@ +#include "abaddon.hpp" #include "discord.hpp" #include "util.hpp" -#include "abaddon.hpp" #include #include +using namespace std::string_literals; + DiscordClient::DiscordClient(bool mem_store) : m_decompress_buf(InflateChunkSize) , m_store(mem_store) { @@ -909,6 +911,35 @@ void DiscordClient::MarkGuildAsRead(Snowflake guild_id, sigc::slot callback) { + const auto channel = GetChannel(channel_id); + if (!channel.has_value()) return; + const auto guild_id_path = channel->GuildID.has_value() ? std::to_string(*channel->GuildID) : "@me"s; + nlohmann::json j; + j["channel_overrides"][std::to_string(channel_id)]["mute_config"] = MuteConfigData { std::nullopt, -1 }; + j["channel_overrides"][std::to_string(channel_id)]["muted"] = true; + m_http.MakePATCH("/users/@me/guilds/" + guild_id_path + "/settings", j.dump(), [this, callback](const http::response_type &response) { + if (CheckCode(response)) + callback(DiscordError::NONE); + else + callback(GetCodeFromResponse(response)); + }); +} + +void DiscordClient::UnmuteChannel(Snowflake channel_id, sigc::slot callback) { + const auto channel = GetChannel(channel_id); + if (!channel.has_value()) return; + const auto guild_id_path = channel->GuildID.has_value() ? std::to_string(*channel->GuildID) : "@me"s; + nlohmann::json j; + j["channel_overrides"][std::to_string(channel_id)]["muted"] = false; + m_http.MakePATCH("/users/@me/guilds/" + guild_id_path + "/settings", j.dump(), [this, callback](const http::response_type &response) { + if (CheckCode(response)) + callback(DiscordError::NONE); + else + callback(GetCodeFromResponse(response)); + }); +} + void DiscordClient::FetchPinned(Snowflake id, sigc::slot, DiscordError code)> callback) { // return from db if we know the pins have already been requested if (m_channels_pinned_requested.find(id) != m_channels_pinned_requested.end()) { diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 90919b6..9e526f9 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -140,6 +140,8 @@ public: void UnArchiveThread(Snowflake channel_id, sigc::slot callback); void MarkChannelAsRead(Snowflake channel_id, sigc::slot callback); void MarkGuildAsRead(Snowflake guild_id, sigc::slot callback); + void MuteChannel(Snowflake channel_id, sigc::slot callback); + void UnmuteChannel(Snowflake channel_id, sigc::slot callback); bool CanModifyRole(Snowflake guild_id, Snowflake role_id) const; bool CanModifyRole(Snowflake guild_id, Snowflake role_id, Snowflake user_id) const; diff --git a/src/discord/objects.cpp b/src/discord/objects.cpp index a41b664..9e3741f 100644 --- a/src/discord/objects.cpp +++ b/src/discord/objects.cpp @@ -143,6 +143,27 @@ void from_json(const nlohmann::json &j, UserGuildSettingsChannelOverride &m) { JS_D("channel_id", m.ChannelID); } +void to_json(nlohmann::json &j, const UserGuildSettingsChannelOverride &m) { + j["channel_id"] = m.ChannelID; + j["collapsed"] = m.Collapsed; + j["message_notifications"] = m.MessageNotifications; + j["mute_config"] = m.MuteConfig; + j["muted"] = m.Muted; +} + +void from_json(const nlohmann::json &j, MuteConfigData &m) { + JS_ON("end_time", m.EndTime); + JS_D("selected_time_window", m.SelectedTimeWindow); +} + +void to_json(nlohmann::json &j, const MuteConfigData &m) { + if (m.EndTime.has_value()) + j["end_time"] = *m.EndTime; + else + j["end_time"] = nullptr; + j["selected_time_window"] = m.SelectedTimeWindow; +} + void from_json(const nlohmann::json &j, UserGuildSettingsEntry &m) { JS_D("version", m.Version); JS_D("suppress_roles", m.SuppressRoles); @@ -151,13 +172,26 @@ void from_json(const nlohmann::json &j, UserGuildSettingsEntry &m) { JS_D("mobile_push", m.MobilePush); JS_D("message_notifications", m.MessageNotifications); JS_D("hide_muted_channels", m.HideMutedChannels); - JS_D("guild_id", m.GuildID); + JS_N("guild_id", m.GuildID); JS_D("channel_overrides", m.ChannelOverrides); } +void to_json(nlohmann::json &j, const UserGuildSettingsEntry &m) { + j["channel_overrides"] = m.ChannelOverrides; + j["guild_id"] = m.GuildID; + j["hide_muted_channels"] = m.HideMutedChannels; + j["message_notifications"] = m.MessageNotifications; + j["mobile_push"] = m.MobilePush; + j["mute_config"] = m.MuteConfig; + j["muted"] = m.Muted; + j["suppress_everyone"] = m.SuppressEveryone; + j["suppress_roles"] = m.SuppressRoles; + j["version"] = m.Version; +} + void from_json(const nlohmann::json &j, UserGuildSettingsData &m) { JS_D("version", m.Version); - JS_D("partial", m.IsParital); + JS_D("partial", m.IsPartial); JS_D("entries", m.Entries); } diff --git a/src/discord/objects.hpp b/src/discord/objects.hpp index 69ad422..c72361b 100644 --- a/src/discord/objects.hpp +++ b/src/discord/objects.hpp @@ -244,14 +244,23 @@ struct ReadStateData { friend void from_json(const nlohmann::json &j, ReadStateData &m); }; +struct MuteConfigData { + std::optional EndTime; // nullopt is encoded as null + int SelectedTimeWindow; + + friend void from_json(const nlohmann::json &j, MuteConfigData &m); + friend void to_json(nlohmann::json &j, const MuteConfigData &m); +}; + struct UserGuildSettingsChannelOverride { bool Muted; - // MuteConfig + MuteConfigData MuteConfig; int MessageNotifications; bool Collapsed; Snowflake ChannelID; friend void from_json(const nlohmann::json &j, UserGuildSettingsChannelOverride &m); + friend void to_json(nlohmann::json &j, const UserGuildSettingsChannelOverride &m); }; struct UserGuildSettingsEntry { @@ -259,7 +268,7 @@ struct UserGuildSettingsEntry { bool SuppressRoles; bool SuppressEveryone; bool Muted; - // MuteConfig + MuteConfigData MuteConfig; bool MobilePush; int MessageNotifications; bool HideMutedChannels; @@ -267,11 +276,12 @@ struct UserGuildSettingsEntry { std::vector ChannelOverrides; friend void from_json(const nlohmann::json &j, UserGuildSettingsEntry &m); + friend void to_json(nlohmann::json &j, const UserGuildSettingsEntry &m); }; struct UserGuildSettingsData { int Version; - bool IsParital; + bool IsPartial; std::vector Entries; friend void from_json(const nlohmann::json &j, UserGuildSettingsData &m); -- cgit v1.2.3 From e9867173c9fc7a38e5d6688fcaf582aaa8bf3ca5 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 18 Dec 2021 02:06:16 -0500 Subject: inline unread rendering --- src/components/channelscellrenderer.cpp | 104 ++++++++++++++++++++++++++++++-- src/components/channelscellrenderer.hpp | 3 + src/components/unreadrenderer.cpp | 100 ------------------------------ src/components/unreadrenderer.hpp | 11 ---- 4 files changed, 103 insertions(+), 115 deletions(-) delete mode 100644 src/components/unreadrenderer.cpp delete mode 100644 src/components/unreadrenderer.hpp (limited to 'src/components/channelscellrenderer.cpp') diff --git a/src/components/channelscellrenderer.cpp b/src/components/channelscellrenderer.cpp index df9f03d..c04c5b8 100644 --- a/src/components/channelscellrenderer.cpp +++ b/src/components/channelscellrenderer.cpp @@ -1,8 +1,14 @@ -#include "channelscellrenderer.hpp" #include "abaddon.hpp" -#include "unreadrenderer.hpp" +#include "channelscellrenderer.hpp" #include +constexpr static int MentionsRightPad = 7; +#ifndef M_PI +constexpr static double M_PI = 3.14159265358979; +#endif +constexpr static double M_PI_H = M_PI / 2.0; +constexpr static double M_PI_3_2 = M_PI * 3.0 / 2.0; + CellRendererChannels::CellRendererChannels() : Glib::ObjectBase(typeid(CellRendererChannels)) , Gtk::CellRenderer() @@ -240,7 +246,40 @@ void CellRendererChannels::render_vfunc_guild(const Cairo::RefPtrfill(); } - UnreadRenderer::RenderUnreadOnGuild(m_property_id.get_value(), widget, cr, background_area, cell_area); + // unread + + const auto id = m_property_id.get_value(); + + int total_mentions = 0; + auto &discord = Abaddon::Get().GetDiscordClient(); + const auto channels = discord.GetChannelsInGuild(id); + bool has_unread = false; + for (const auto &id : channels) { + const int state = Abaddon::Get().GetDiscordClient().GetUnreadStateForChannel(id); + if (state >= 0) { + has_unread = true; + total_mentions += state; + } + } + if (!has_unread) return; + + if (!discord.IsGuildMuted(id)) { + cr->set_source_rgb(1.0, 1.0, 1.0); + 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 + h / 2 - 24 / 2, 3, 24); + cr->fill(); + } + + if (total_mentions < 1) return; + auto *paned = static_cast(widget.get_ancestor(Gtk::Paned::get_type())); + if (paned != nullptr) { + const auto edge = std::min(paned->get_position(), background_area.get_width()); + + unread_render_mentions(cr, widget, total_mentions, edge, background_area); + } } // category @@ -337,7 +376,30 @@ void CellRendererChannels::render_vfunc_channel(const Cairo::RefPtrset_source_rgb(1.0, 1.0, 1.0); + 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(); + } + + if (state < 1) return; + auto *paned = static_cast(widget.get_ancestor(Gtk::Paned::get_type())); + if (paned != nullptr) { + const auto edge = std::min(paned->get_position(), cell_area.get_width()); + + unread_render_mentions(cr, widget, state, edge, cell_area); + } } // thread @@ -462,3 +524,37 @@ void CellRendererChannels::render_vfunc_dm(const Cairo::RefPtr & cr->rectangle(icon_x, icon_y, icon_w, icon_h); cr->fill(); } + +void CellRendererChannels::cairo_path_rounded_rect(const Cairo::RefPtr &cr, double x, double y, double w, double h, double r) { + const double degrees = M_PI / 180.0; + + cr->begin_new_sub_path(); + cr->arc(x + w - r, y + r, r, -M_PI_H, 0); + cr->arc(x + w - r, y + h - r, r, 0, M_PI_H); + cr->arc(x + r, y + h - r, r, M_PI_H, M_PI); + cr->arc(x + r, y + r, r, M_PI, M_PI_3_2); + cr->close_path(); +} + +void CellRendererChannels::unread_render_mentions(const Cairo::RefPtr &cr, Gtk::Widget &widget, int mentions, int edge, const Gdk::Rectangle &cell_area) { + Pango::FontDescription font; + font.set_family("sans 14"); + //font.set_weight(Pango::WEIGHT_BOLD); + + auto layout = widget.create_pango_layout(std::to_string(mentions)); + layout->set_font_description(font); + layout->set_alignment(Pango::ALIGN_RIGHT); + + int width, height; + layout->get_pixel_size(width, height); + { + const auto x = cell_area.get_x() + edge - width - MentionsRightPad; + const auto y = cell_area.get_y() + cell_area.get_height() / 2.0 - height / 2.0 - 1; + cairo_path_rounded_rect(cr, x - 4, y + 2, width + 8, height, 5); + cr->set_source_rgb(184.0 / 255.0, 37.0 / 255.0, 37.0 / 255.0); + cr->fill(); + cr->set_source_rgb(1.0, 1.0, 1.0); + cr->move_to(x, y); + layout->show_in_cairo_context(cr); + } +} diff --git a/src/components/channelscellrenderer.hpp b/src/components/channelscellrenderer.hpp index 4a9d428..95ff4fe 100644 --- a/src/components/channelscellrenderer.hpp +++ b/src/components/channelscellrenderer.hpp @@ -105,6 +105,9 @@ protected: const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags); + static void cairo_path_rounded_rect(const Cairo::RefPtr &cr, double x, double y, double w, double h, double r); + void unread_render_mentions(const Cairo::RefPtr &cr, Gtk::Widget &widget, int mentions, int edge, const Gdk::Rectangle &cell_area); + private: Gtk::CellRendererText m_renderer_text; diff --git a/src/components/unreadrenderer.cpp b/src/components/unreadrenderer.cpp deleted file mode 100644 index c49b8c3..0000000 --- a/src/components/unreadrenderer.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include "unreadrenderer.hpp" -#include "abaddon.hpp" - -constexpr static int MentionsRightPad = 7; -#ifndef M_PI -constexpr static double M_PI = 3.14159265358979; -#endif -constexpr static double M_PI_H = M_PI / 2.0; -constexpr static double M_PI_3_2 = M_PI * 3.0 / 2.0; - -void CairoPathRoundedRect(const Cairo::RefPtr &cr, double x, double y, double w, double h, double r) { - const double degrees = M_PI / 180.0; - - cr->begin_new_sub_path(); - cr->arc(x + w - r, y + r, r, -M_PI_H, 0); - cr->arc(x + w - r, y + h - r, r, 0, M_PI_H); - cr->arc(x + r, y + h - r, r, M_PI_H, M_PI); - cr->arc(x + r, y + r, r, M_PI, M_PI_3_2); - cr->close_path(); -} - -void RenderMentionsCount(const Cairo::RefPtr &cr, Gtk::Widget &widget, int mentions, int edge, const Gdk::Rectangle &cell_area) { - Pango::FontDescription font; - font.set_family("sans 14"); - //font.set_weight(Pango::WEIGHT_BOLD); - - auto layout = widget.create_pango_layout(std::to_string(mentions)); - layout->set_font_description(font); - layout->set_alignment(Pango::ALIGN_RIGHT); - - int width, height; - layout->get_pixel_size(width, height); - { - const auto x = cell_area.get_x() + edge - width - MentionsRightPad; - const auto y = cell_area.get_y() + cell_area.get_height() / 2.0 - height / 2.0 - 1; - CairoPathRoundedRect(cr, x - 4, y + 2, width + 8, height, 5); - cr->set_source_rgb(184.0 / 255.0, 37.0 / 255.0, 37.0 / 255.0); - cr->fill(); - cr->set_source_rgb(1.0, 1.0, 1.0); - cr->move_to(x, y); - layout->show_in_cairo_context(cr); - } -} - -void UnreadRenderer::RenderUnreadOnGuild(Snowflake id, Gtk::Widget &widget, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area) { - // maybe have DiscordClient track this? - int total_mentions = 0; - auto &discord = Abaddon::Get().GetDiscordClient(); - const auto channels = discord.GetChannelsInGuild(id); - bool has_unread = false; - for (const auto &id : channels) { - const int state = Abaddon::Get().GetDiscordClient().GetUnreadStateForChannel(id); - if (state >= 0) { - has_unread = true; - total_mentions += state; - } - } - if (!has_unread) return; - - if (!discord.IsGuildMuted(id)) { - cr->set_source_rgb(1.0, 1.0, 1.0); - 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 + h / 2 - 24 / 2, 3, 24); - cr->fill(); - } - - if (total_mentions < 1) return; - auto *paned = static_cast(widget.get_ancestor(Gtk::Paned::get_type())); - if (paned != nullptr) { - const auto edge = std::min(paned->get_position(), background_area.get_width()); - - RenderMentionsCount(cr, widget, total_mentions, edge, background_area); - } -} - -void UnreadRenderer::RenderUnreadOnChannel(Snowflake id, Gtk::Widget &widget, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area) { - const auto state = Abaddon::Get().GetDiscordClient().GetUnreadStateForChannel(id); - if (state < 0) return; - - if (!Abaddon::Get().GetDiscordClient().IsChannelMuted(id)) { - cr->set_source_rgb(1.0, 1.0, 1.0); - 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(); - } - - if (state < 1) return; - auto *paned = static_cast(widget.get_ancestor(Gtk::Paned::get_type())); - if (paned != nullptr) { - const auto edge = std::min(paned->get_position(), cell_area.get_width()); - - RenderMentionsCount(cr, widget, state, edge, cell_area); - } -} diff --git a/src/components/unreadrenderer.hpp b/src/components/unreadrenderer.hpp deleted file mode 100644 index 1b4ddc2..0000000 --- a/src/components/unreadrenderer.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once -#include -#include -#include -#include "discord/snowflake.hpp" - -class UnreadRenderer { -public: - static void RenderUnreadOnGuild(Snowflake id, Gtk::Widget &widget, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area); - static void RenderUnreadOnChannel(Snowflake id, Gtk::Widget &widget, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area); -}; -- cgit v1.2.3 From c43d49ed549b4638b3113224c98149b9aee1d355 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 18 Dec 2021 02:17:43 -0500 Subject: grey out muted channels in list --- src/components/channelscellrenderer.cpp | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) (limited to 'src/components/channelscellrenderer.cpp') diff --git a/src/components/channelscellrenderer.cpp b/src/components/channelscellrenderer.cpp index c04c5b8..0626575 100644 --- a/src/components/channelscellrenderer.cpp +++ b/src/components/channelscellrenderer.cpp @@ -368,22 +368,34 @@ void CellRendererChannels::render_vfunc_channel(const Cairo::RefPtrset_source_rgb(1.0, 1.0, 1.0); const auto x = background_area.get_x(); const auto y = background_area.get_y(); @@ -393,12 +405,12 @@ void CellRendererChannels::render_vfunc_channel(const Cairo::RefPtrfill(); } - if (state < 1) return; + if (unread_state < 1) return; auto *paned = static_cast(widget.get_ancestor(Gtk::Paned::get_type())); if (paned != nullptr) { const auto edge = std::min(paned->get_position(), cell_area.get_width()); - unread_render_mentions(cr, widget, state, edge, cell_area); + unread_render_mentions(cr, widget, unread_state, edge, cell_area); } } -- cgit v1.2.3 From 67062d6ed820e9774bb99d1bbc75c8757d65ee70 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 18 Dec 2021 03:24:44 -0500 Subject: unread indicator for dm channels --- src/components/channelscellrenderer.cpp | 31 +++++++++++++++++++++++++++---- src/discord/discord.cpp | 3 +++ 2 files changed, 30 insertions(+), 4 deletions(-) (limited to 'src/components/channelscellrenderer.cpp') diff --git a/src/components/channelscellrenderer.cpp b/src/components/channelscellrenderer.cpp index 0626575..788afdf 100644 --- a/src/components/channelscellrenderer.cpp +++ b/src/components/channelscellrenderer.cpp @@ -370,6 +370,7 @@ void CellRendererChannels::render_vfunc_channel(const Cairo::RefPtrset_source_rgb(1.0, 1.0, 1.0); const auto x = background_area.get_x(); const auto y = background_area.get_y(); @@ -520,21 +521,43 @@ void CellRendererChannels::render_vfunc_dm(const Cairo::RefPtr & const double icon_w = pixbuf->get_width(); const double icon_h = pixbuf->get_height(); - const double icon_x = background_area.get_x() + 2; + const double icon_x = background_area.get_x() + 3; const double icon_y = background_area.get_y() + background_area.get_height() / 2.0 - icon_h / 2.0; - const double text_x = icon_x + icon_w + 5.0; + const double text_x = icon_x + icon_w + 6.0; const double text_y = background_area.get_y() + background_area.get_height() / 2.0 - text_natural.height / 2.0; const double text_w = text_natural.width; const double text_h = text_natural.height; Gdk::Rectangle text_cell_area(text_x, text_y, text_w, text_h); + auto &discord = Abaddon::Get().GetDiscordClient(); + const auto id = m_property_id.get_value(); + const bool is_muted = discord.IsChannelMuted(id); + + if (is_muted) + m_renderer_text.property_foreground() = "#7f7f7f"; m_renderer_text.render(cr, widget, background_area, text_cell_area, flags); + m_renderer_text.property_foreground_set() = false; Gdk::Cairo::set_source_pixbuf(cr, m_property_pixbuf.get_value(), icon_x, icon_y); cr->rectangle(icon_x, icon_y, icon_w, icon_h); cr->fill(); + + // unread + + const auto unread_state = discord.GetUnreadStateForChannel(id); + if (unread_state < 0) return; + + if (!is_muted) { + cr->set_source_rgb(1.0, 1.0, 1.0); + 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::cairo_path_rounded_rect(const Cairo::RefPtr &cr, double x, double y, double w, double h, double r) { diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 1872985..94099f6 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -2240,6 +2240,9 @@ void DiscordClient::HandleReadyReadState(const ReadyEventData &data) { for (const auto &channel : *guild.Channels) if (channel.Type == ChannelType::GUILD_TEXT || channel.Type == ChannelType::GUILD_NEWS && channel.LastMessageID.has_value()) m_last_message_id[channel.ID] = *channel.LastMessageID; + for (const auto &channel : data.PrivateChannels) + if (channel.LastMessageID.has_value()) + m_last_message_id[channel.ID] = *channel.LastMessageID; for (const auto &entry : data.ReadState.Entries) { const auto it = m_last_message_id.find(entry.ID); -- cgit v1.2.3 From 207c00422861c91c553654a37c52e21f72c76cc2 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 25 Dec 2021 03:07:11 -0500 Subject: take muted channels into account for unread guild indicator --- src/components/channelscellrenderer.cpp | 15 +++------------ src/discord/discord.cpp | 16 +++++++++++++++- src/discord/discord.hpp | 1 + 3 files changed, 19 insertions(+), 13 deletions(-) (limited to 'src/components/channelscellrenderer.cpp') diff --git a/src/components/channelscellrenderer.cpp b/src/components/channelscellrenderer.cpp index 788afdf..325d45a 100644 --- a/src/components/channelscellrenderer.cpp +++ b/src/components/channelscellrenderer.cpp @@ -250,20 +250,11 @@ void CellRendererChannels::render_vfunc_guild(const Cairo::RefPtr= 0) { - has_unread = true; - total_mentions += state; - } - } - if (!has_unread) return; + int total_mentions; + const auto has_unread = discord.GetUnreadStateForGuild(id, total_mentions); - if (!discord.IsGuildMuted(id)) { + if (has_unread && !discord.IsGuildMuted(id)) { cr->set_source_rgb(1.0, 1.0, 1.0); const auto x = background_area.get_x(); const auto y = background_area.get_y(); diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 5443b87..eac6428 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1156,10 +1156,24 @@ bool DiscordClient::IsGuildMuted(Snowflake id) const noexcept { int DiscordClient::GetUnreadStateForChannel(Snowflake id) const noexcept { const auto iter = m_unread.find(id); - if (iter == m_unread.end()) return -1; // todo: no magic number + if (iter == m_unread.end()) return -1; // todo: no magic number (who am i kidding ill never change this) return iter->second; } +bool DiscordClient::GetUnreadStateForGuild(Snowflake id, int &total_mentions) const noexcept { + total_mentions = 0; + bool has_any_unread = false; + const auto channels = GetChannelsInGuild(id); + for (const auto channel_id : channels) { + const auto channel_unread = GetUnreadStateForChannel(channel_id); + if (!has_any_unread && channel_unread > -1 && !IsChannelMuted(channel_id)) + has_any_unread = true; + if (channel_unread > -1) + total_mentions += channel_unread; + } + return has_any_unread; +} + PresenceStatus DiscordClient::GetUserStatus(Snowflake id) const { auto it = m_user_to_status.find(id); if (it != m_user_to_status.end()) diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 5569123..4374396 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -190,6 +190,7 @@ public: bool IsChannelMuted(Snowflake id) const noexcept; bool IsGuildMuted(Snowflake id) const noexcept; int GetUnreadStateForChannel(Snowflake id) const noexcept; + bool GetUnreadStateForGuild(Snowflake id, int &total_mentions) const noexcept; PresenceStatus GetUserStatus(Snowflake id) const; -- cgit v1.2.3 From 40106ddeb11a755864e446920b74739f1ec21c57 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 5 Jan 2022 03:52:20 -0500 Subject: handle mutable categories --- src/components/channels.cpp | 21 ++++++++++++++++++++- src/components/channels.hpp | 2 ++ src/components/channelscellrenderer.cpp | 4 ++++ src/discord/channel.cpp | 8 ++++++++ src/discord/channel.hpp | 2 ++ src/discord/discord.cpp | 33 +++++++++++++++++++++++++++++++-- src/discord/discord.hpp | 2 ++ src/discord/store.cpp | 23 +++++++++++++++++++++++ src/discord/store.hpp | 2 ++ 9 files changed, 94 insertions(+), 3 deletions(-) (limited to 'src/components/channelscellrenderer.cpp') diff --git a/src/components/channels.cpp b/src/components/channels.cpp index 7ad2dd0..c4ba2c0 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -116,7 +116,15 @@ ChannelList::ChannelList() m_menu_category_copy_id.signal_activate().connect([this] { Gtk::Clipboard::get()->set_text(std::to_string((*m_model->get_iter(m_path_for_menu))[m_columns.m_id])); }); - + m_menu_category_toggle_mute.signal_activate().connect([this] { + const auto id = static_cast((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]); + auto &discord = Abaddon::Get().GetDiscordClient(); + if (discord.IsChannelMuted(id)) + discord.UnmuteChannel(id, NOOP_CALLBACK); + else + discord.MuteChannel(id, NOOP_CALLBACK); + }); + m_menu_category.append(m_menu_category_toggle_mute); m_menu_category.append(m_menu_category_copy_id); m_menu_category.show_all(); @@ -177,6 +185,7 @@ ChannelList::ChannelList() m_menu_thread.show_all(); m_menu_guild.signal_popped_up().connect(sigc::mem_fun(*this, &ChannelList::OnGuildSubmenuPopup)); + m_menu_category.signal_popped_up().connect(sigc::mem_fun(*this, &ChannelList::OnCategorySubmenuPopup)); m_menu_channel.signal_popped_up().connect(sigc::mem_fun(*this, &ChannelList::OnChannelSubmenuPopup)); m_menu_thread.signal_popped_up().connect(sigc::mem_fun(*this, &ChannelList::OnThreadSubmenuPopup)); @@ -857,6 +866,16 @@ void ChannelList::OnGuildSubmenuPopup(const Gdk::Rectangle *flipped_rect, const m_menu_guild_toggle_mute.set_label("Mute"); } +void ChannelList::OnCategorySubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y) { + const auto iter = m_model->get_iter(m_path_for_menu); + if (!iter) return; + const auto id = static_cast((*iter)[m_columns.m_id]); + if (Abaddon::Get().GetDiscordClient().IsChannelMuted(id)) + m_menu_category_toggle_mute.set_label("Unmute"); + else + m_menu_category_toggle_mute.set_label("Mute"); +} + void ChannelList::OnChannelSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y) { const auto iter = m_model->get_iter(m_path_for_menu); if (!iter) return; diff --git a/src/components/channels.hpp b/src/components/channels.hpp index 6970729..ba75be8 100644 --- a/src/components/channels.hpp +++ b/src/components/channels.hpp @@ -114,6 +114,7 @@ protected: Gtk::Menu m_menu_category; Gtk::MenuItem m_menu_category_copy_id; + Gtk::MenuItem m_menu_category_toggle_mute; Gtk::Menu m_menu_channel; Gtk::MenuItem m_menu_channel_copy_id; @@ -131,6 +132,7 @@ protected: Gtk::MenuItem m_menu_thread_unarchive; void OnGuildSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y); + void OnCategorySubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y); void OnChannelSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y); void OnThreadSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y); diff --git a/src/components/channelscellrenderer.cpp b/src/components/channelscellrenderer.cpp index 325d45a..b998442 100644 --- a/src/components/channelscellrenderer.cpp +++ b/src/components/channelscellrenderer.cpp @@ -327,7 +327,11 @@ void CellRendererChannels::render_vfunc_category(const Cairo::RefPtr ChannelData::GetChildIDs() const { + return Abaddon::Get().GetDiscordClient().GetChildChannelIDs(ID); +} + std::optional ChannelData::GetOverwrite(Snowflake id) const { return Abaddon::Get().GetDiscordClient().GetPermissionOverwrite(ID, id); } diff --git a/src/discord/channel.hpp b/src/discord/channel.hpp index fd76d3a..195a09a 100644 --- a/src/discord/channel.hpp +++ b/src/discord/channel.hpp @@ -88,6 +88,8 @@ struct ChannelData { bool IsDM() const noexcept; bool IsThread() const noexcept; bool IsJoinedThread() const; + bool IsCategory() const noexcept; + std::vector GetChildIDs() const; std::optional GetOverwrite(Snowflake id) const; std::vector GetDMRecipients() const; }; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 6819375..529da0b 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -307,6 +307,10 @@ void DiscordClient::GetArchivedPrivateThreads(Snowflake channel_id, sigc::slot DiscordClient::GetChildChannelIDs(Snowflake parent_id) const { + return m_store.GetChannelIDsWithParentID(parent_id); +} + bool DiscordClient::IsThreadJoined(Snowflake thread_id) const { return std::find(m_joined_threads.begin(), m_joined_threads.end(), thread_id) != m_joined_threads.end(); } @@ -1184,10 +1188,15 @@ bool DiscordClient::GetUnreadStateForGuild(Snowflake id, int &total_mentions) co const auto channels = GetChannelsInGuild(id); for (const auto channel_id : channels) { const auto channel_unread = GetUnreadStateForChannel(channel_id); - if (!has_any_unread && channel_unread > -1 && !IsChannelMuted(channel_id)) - has_any_unread = true; if (channel_unread > -1) total_mentions += channel_unread; + + // channels under muted categories wont contribute to unread state + if (const auto iter = m_channel_muted_parent.find(channel_id); iter != m_channel_muted_parent.end()) + continue; + + if (!has_any_unread && channel_unread > -1 && !IsChannelMuted(channel_id)) + has_any_unread = true; } return has_any_unread; } @@ -1974,11 +1983,19 @@ void DiscordClient::HandleGatewayUserGuildSettingsUpdate(const GatewayMessage &m if (now_muted) { m_muted_channels.insert(channel_id); if (!was_muted) { + if (const auto chan = GetChannel(channel_id); chan.has_value() && chan->IsCategory()) + for (const auto child_id : chan->GetChildIDs()) + m_channel_muted_parent.insert(child_id); + m_signal_channel_muted.emit(channel_id); } } else { m_muted_channels.erase(channel_id); if (was_muted) { + if (const auto chan = GetChannel(channel_id); chan.has_value() && chan->IsCategory()) + for (const auto child_id : chan->GetChildIDs()) + m_channel_muted_parent.erase(child_id); + m_signal_channel_unmuted.emit(channel_id); } } @@ -2357,6 +2374,14 @@ void DiscordClient::HandleReadyReadState(const ReadyEventData &data) { } void DiscordClient::HandleReadyGuildSettings(const ReadyEventData &data) { + // i dont like this implementation for muted categories but its rather simple and doesnt use a horriiible amount of ram + + std::unordered_map> category_children; + for (const auto &guild : data.Guilds) + for (const auto &channel : *guild.Channels) + if (channel.ParentID.has_value() && !channel.IsThread()) + category_children[*channel.ParentID].push_back(channel.ID); + const auto now = Snowflake::FromNow(); for (const auto &entry : data.GuildSettings.Entries) { // even if muted is true a guild/channel can be unmuted if the current time passes mute_config.end_time @@ -2371,6 +2396,10 @@ void DiscordClient::HandleReadyGuildSettings(const ReadyEventData &data) { } for (const auto &override : entry.ChannelOverrides) { if (override.Muted) { + if (const auto iter = category_children.find(override.ChannelID); iter != category_children.end()) + for (const auto child : iter->second) + m_channel_muted_parent.insert(child); + if (override.MuteConfig.EndTime.has_value()) { const auto end = Snowflake::FromISO8601(*override.MuteConfig.EndTime); if (end.IsValid() && end > now) diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 44f02bb..69cfcf7 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -82,6 +82,7 @@ public: std::vector GetActiveThreads(Snowflake channel_id) const; void GetArchivedPublicThreads(Snowflake channel_id, sigc::slot callback); void GetArchivedPrivateThreads(Snowflake channel_id, sigc::slot callback); + std::vector GetChildChannelIDs(Snowflake parent_id) const; bool IsThreadJoined(Snowflake thread_id) const; bool HasGuildPermission(Snowflake user_id, Snowflake guild_id, Permission perm) const; @@ -290,6 +291,7 @@ private: std::unordered_set m_muted_guilds; std::unordered_set m_muted_channels; std::unordered_map m_unread; + std::unordered_set m_channel_muted_parent; UserData m_user_data; UserSettings m_user_settings; diff --git a/src/discord/store.cpp b/src/discord/store.cpp index 5e4e3b3..aa97178 100644 --- a/src/discord/store.cpp +++ b/src/discord/store.cpp @@ -571,6 +571,21 @@ std::vector Store::GetActiveThreads(Snowflake channel_id) const { return ret; } +std::vector Store::GetChannelIDsWithParentID(Snowflake channel_id) const { + auto &s = m_stmt_get_chan_ids_parent; + + s->Bind(1, channel_id); + + std::vector ret; + while (s->FetchOne()) { + Snowflake x; + s->Get(0, x); + ret.push_back(x); + } + + return ret; +} + void Store::AddReaction(const MessageReactionAddObject &data, bool byself) { auto &s = m_stmt_add_reaction; @@ -2120,6 +2135,14 @@ bool Store::CreateStatements() { return false; } + m_stmt_get_chan_ids_parent = std::make_unique(m_db, R"( + SELECT id FROM channels WHERE parent_id = ? + )"); + if (!m_stmt_get_chan_ids_parent->OK()) { + fprintf(stderr, "failed to prepare get channel ids for parent statement: %s\n", m_db.ErrStr()); + return false; + } + return true; } diff --git a/src/discord/store.hpp b/src/discord/store.hpp index 715f280..4320807 100644 --- a/src/discord/store.hpp +++ b/src/discord/store.hpp @@ -43,6 +43,7 @@ public: std::vector GetMessagesBefore(Snowflake channel_id, Snowflake message_id, size_t limit) const; std::vector GetPinnedMessages(Snowflake channel_id) const; std::vector GetActiveThreads(Snowflake channel_id) const; // public + std::vector GetChannelIDsWithParentID(Snowflake channel_id) const; void AddReaction(const MessageReactionAddObject &data, bool byself); void RemoveReaction(const MessageReactionRemoveObject &data, bool byself); @@ -300,5 +301,6 @@ private: STMT(add_reaction); STMT(sub_reaction); STMT(get_reactions); + STMT(get_chan_ids_parent); #undef STMT }; -- cgit v1.2.3 From 604f2ffe3dc8978aebd6aa819b73374aa32d2f0e Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 8 Jan 2022 20:03:12 -0500 Subject: show count of unread dms in header --- src/components/channels.cpp | 1 + src/components/channelscellrenderer.cpp | 7 +++++++ src/discord/discord.cpp | 8 ++++++++ src/discord/discord.hpp | 1 + 4 files changed, 17 insertions(+) (limited to 'src/components/channelscellrenderer.cpp') diff --git a/src/components/channels.cpp b/src/components/channels.cpp index c4ba2c0..28eb288 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -770,6 +770,7 @@ void ChannelList::UpdateCreateDMChannel(const ChannelData &dm) { void ChannelList::OnMessageAck(const MessageAckData &data) { // trick renderer into redrawing + m_model->row_changed(Gtk::TreeModel::Path("0"), m_model->get_iter("0")); // 0 is always path for dm header auto iter = GetIteratorForChannelFromID(data.ChannelID); if (iter) m_model->row_changed(m_model->get_path(iter), iter); auto channel = Abaddon::Get().GetDiscordClient().GetChannel(data.ChannelID); diff --git a/src/components/channelscellrenderer.cpp b/src/components/channelscellrenderer.cpp index b998442..4578020 100644 --- a/src/components/channelscellrenderer.cpp +++ b/src/components/channelscellrenderer.cpp @@ -465,6 +465,13 @@ void CellRendererChannels::render_vfunc_dmheader(const Cairo::RefPtr(widget.get_ancestor(Gtk::Paned::get_type())); + if (paned != nullptr) { + const auto edge = std::min(paned->get_position(), background_area.get_width()); + if (const auto unread = Abaddon::Get().GetDiscordClient().GetUnreadDMsCount(); unread > 0) + unread_render_mentions(cr, widget, unread, edge, background_area); + } } // dm (basically the same thing as guild) diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index dce5124..5b3cdb5 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1202,6 +1202,14 @@ bool DiscordClient::GetUnreadStateForGuild(Snowflake id, int &total_mentions) co return has_any_unread; } +int DiscordClient::GetUnreadDMsCount() const { + const auto channels = GetPrivateChannels(); + int count = 0; + for (const auto channel_id : channels) + if (GetUnreadStateForChannel(channel_id) > -1) count++; + return count; +} + PresenceStatus DiscordClient::GetUserStatus(Snowflake id) const { auto it = m_user_to_status.find(id); if (it != m_user_to_status.end()) diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 42c24fd..1a6aa14 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -194,6 +194,7 @@ public: bool IsGuildMuted(Snowflake id) const noexcept; int GetUnreadStateForChannel(Snowflake id) const noexcept; bool GetUnreadStateForGuild(Snowflake id, int &total_mentions) const noexcept; + int GetUnreadDMsCount() const; PresenceStatus GetUserStatus(Snowflake id) const; -- cgit v1.2.3