diff options
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/channels.cpp | 127 | ||||
-rw-r--r-- | src/components/channels.hpp | 18 | ||||
-rw-r--r-- | src/components/channelscellrenderer.cpp | 144 | ||||
-rw-r--r-- | src/components/channelscellrenderer.hpp | 10 |
4 files changed, 282 insertions, 17 deletions
diff --git a/src/components/channels.cpp b/src/components/channels.cpp index 148f26f..7ad2dd0 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -9,13 +9,14 @@ ChannelList::ChannelList() : Glib::ObjectBase(typeid(ChannelList)) - , Gtk::ScrolledWindow() , m_model(Gtk::TreeStore::create(m_columns)) , m_menu_guild_copy_id("_Copy ID", true) , m_menu_guild_settings("View _Settings", true) , m_menu_guild_leave("_Leave", true) + , m_menu_guild_mark_as_read("Mark as _Read", true) , m_menu_category_copy_id("_Copy ID", true) , m_menu_channel_copy_id("_Copy ID", true) + , m_menu_channel_mark_as_read("Mark as _Read", true) , m_menu_dm_copy_id("_Copy ID", true) , m_menu_dm_close("") // changes depending on if group or not , m_menu_thread_copy_id("_Copy ID", true) @@ -24,6 +25,7 @@ ChannelList::ChannelList() , m_menu_thread_unarchive("_Unarchive", true) { get_style_context()->add_class("channel-list"); + // todo: move to method const auto cb = [this](const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *column) { auto row = *m_model->get_iter(path); const auto type = row[m_columns.m_type]; @@ -40,7 +42,9 @@ ChannelList::ChannelList() } if (type == RenderType::TextChannel || type == RenderType::DM || type == RenderType::Thread) { - m_signal_action_channel_item_select.emit(static_cast<Snowflake>(row[m_columns.m_id])); + const auto id = static_cast<Snowflake>(row[m_columns.m_id]); + m_signal_action_channel_item_select.emit(id); + Abaddon::Get().GetDiscordClient().MarkChannelAsRead(id, [](...) {}); } }; m_view.signal_row_activated().connect(cb, false); @@ -77,6 +81,7 @@ ChannelList::ChannelList() column->add_attribute(renderer->property_icon(), m_columns.m_icon); column->add_attribute(renderer->property_icon_animation(), m_columns.m_icon_anim); column->add_attribute(renderer->property_name(), m_columns.m_name); + column->add_attribute(renderer->property_id(), m_columns.m_id); column->add_attribute(renderer->property_expanded(), m_columns.m_expanded); column->add_attribute(renderer->property_nsfw(), m_columns.m_nsfw); m_view.append_column(*column); @@ -90,20 +95,47 @@ ChannelList::ChannelList() m_menu_guild_leave.signal_activate().connect([this] { m_signal_action_guild_leave.emit(static_cast<Snowflake>((*m_model->get_iter(m_path_for_menu))[m_columns.m_id])); }); - m_menu_guild.append(m_menu_guild_copy_id); + m_menu_guild_mark_as_read.signal_activate().connect([this] { + Abaddon::Get().GetDiscordClient().MarkGuildAsRead(static_cast<Snowflake>((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]), [](...) {}); + }); + m_menu_guild_toggle_mute.signal_activate().connect([this] { + const auto id = static_cast<Snowflake>((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]); + auto &discord = Abaddon::Get().GetDiscordClient(); + if (discord.IsGuildMuted(id)) + discord.UnmuteGuild(id, NOOP_CALLBACK); + else + discord.MuteGuild(id, NOOP_CALLBACK); + }); + m_menu_guild.append(m_menu_guild_mark_as_read); m_menu_guild.append(m_menu_guild_settings); m_menu_guild.append(m_menu_guild_leave); + m_menu_guild.append(m_menu_guild_toggle_mute); + m_menu_guild.append(m_menu_guild_copy_id); m_menu_guild.show_all(); 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.append(m_menu_category_copy_id); m_menu_category.show_all(); m_menu_channel_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_channel_mark_as_read.signal_activate().connect([this] { + Abaddon::Get().GetDiscordClient().MarkChannelAsRead(static_cast<Snowflake>((*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<Snowflake>((*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_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(); @@ -121,8 +153,8 @@ ChannelList::ChannelList() else if (Abaddon::Get().ShowConfirm("Are you sure you want to leave this group DM?")) Abaddon::Get().GetDiscordClient().CloseDM(id); }); - m_menu_dm.append(m_menu_dm_copy_id); m_menu_dm.append(m_menu_dm_close); + m_menu_dm.append(m_menu_dm_copy_id); m_menu_dm.show_all(); m_menu_thread_copy_id.signal_activate().connect([this] { @@ -144,6 +176,8 @@ ChannelList::ChannelList() m_menu_thread.append(m_menu_thread_unarchive); m_menu_thread.show_all(); + m_menu_guild.signal_popped_up().connect(sigc::mem_fun(*this, &ChannelList::OnGuildSubmenuPopup)); + 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(); @@ -159,6 +193,19 @@ ChannelList::ChannelList() discord.signal_added_to_thread().connect(sigc::mem_fun(*this, &ChannelList::OnThreadJoined)); discord.signal_removed_from_thread().connect(sigc::mem_fun(*this, &ChannelList::OnThreadRemoved)); discord.signal_guild_update().connect(sigc::mem_fun(*this, &ChannelList::UpdateGuild)); + discord.signal_message_ack().connect(sigc::mem_fun(*this, &ChannelList::OnMessageAck)); + discord.signal_channel_muted().connect(sigc::mem_fun(*this, &ChannelList::OnChannelMute)); + discord.signal_channel_unmuted().connect(sigc::mem_fun(*this, &ChannelList::OnChannelUnmute)); + discord.signal_guild_muted().connect(sigc::mem_fun(*this, &ChannelList::OnGuildMute)); + discord.signal_guild_unmuted().connect(sigc::mem_fun(*this, &ChannelList::OnGuildUnmute)); +} + +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() { @@ -346,9 +393,35 @@ void ChannelList::DeleteThreadRow(Snowflake id) { m_model->erase(iter); } +void ChannelList::OnChannelMute(Snowflake id) { + if (auto iter = GetIteratorForChannelFromID(id)) + m_model->row_changed(m_model->get_path(iter), iter); +} + +void ChannelList::OnChannelUnmute(Snowflake id) { + if (auto iter = GetIteratorForChannelFromID(id)) + m_model->row_changed(m_model->get_path(iter), iter); +} + +void ChannelList::OnGuildMute(Snowflake id) { + if (auto iter = GetIteratorForGuildFromID(id)) + m_model->row_changed(m_model->get_path(iter), iter); +} + +void ChannelList::OnGuildUnmute(Snowflake id) { + if (auto iter = GetIteratorForGuildFromID(id)) + m_model->row_changed(m_model->get_path(iter), iter); +} + // create a temporary channel row for non-joined threads // and delete them when the active channel switches off of them if still not joined void ChannelList::SetActiveChannel(Snowflake id) { + // mark channel as read when switching off + if (m_active_channel.IsValid()) + Abaddon::Get().GetDiscordClient().MarkChannelAsRead(m_active_channel, [](...) {}); + + m_active_channel = id; + if (m_temporary_thread_row) { const auto thread_id = static_cast<Snowflake>((*m_temporary_thread_row)[m_columns.m_id]); const auto thread = Abaddon::Get().GetDiscordClient().GetChannel(thread_id); @@ -662,7 +735,7 @@ void ChannelList::UpdateCreateDMChannel(const ChannelData &dm) { std::optional<UserData> top_recipient; const auto recipients = dm.GetDMRecipients(); - if (recipients.size() > 0) + if (!recipients.empty()) top_recipient = recipients[0]; auto iter = m_model->append(header_row->children()); @@ -686,13 +759,29 @@ void ChannelList::UpdateCreateDMChannel(const ChannelData &dm) { } } +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) { + auto iter = GetIteratorForChannelFromID(msg.ChannelID); + if (iter) m_model->row_changed(m_model->get_path(iter), iter); // redraw const auto channel = Abaddon::Get().GetDiscordClient().GetChannel(msg.ChannelID); if (!channel.has_value()) return; - if (channel->Type != ChannelType::DM && channel->Type != ChannelType::GROUP_DM) return; - auto iter = GetIteratorForChannelFromID(msg.ChannelID); - if (iter) - (*iter)[m_columns.m_sort] = -msg.ID; + if (channel->Type == ChannelType::DM || channel->Type == ChannelType::GROUP_DM) { + if (iter) + (*iter)[m_columns.m_sort] = -msg.ID; + } + if (channel->GuildID.has_value()) + if ((iter = GetIteratorForGuildFromID(*channel->GuildID))) + m_model->row_changed(m_model->get_path(iter), iter); } bool ChannelList::OnButtonPressEvent(GdkEventButton *ev) { @@ -758,6 +847,26 @@ void ChannelList::MoveRow(const Gtk::TreeModel::iterator &iter, const Gtk::TreeM m_model->erase(iter); } +void ChannelList::OnGuildSubmenuPopup(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<Snowflake>((*iter)[m_columns.m_id]); + if (Abaddon::Get().GetDiscordClient().IsGuildMuted(id)) + m_menu_guild_toggle_mute.set_label("Unmute"); + else + m_menu_guild_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; + const auto id = static_cast<Snowflake>((*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 a255f01..6970729 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); @@ -33,6 +37,10 @@ protected: void UpdateCreateChannel(const ChannelData &channel); void UpdateGuild(Snowflake id); void DeleteThreadRow(Snowflake id); + void OnChannelMute(Snowflake id); + void OnChannelUnmute(Snowflake id); + void OnGuildMute(Snowflake id); + void OnGuildUnmute(Snowflake id); void OnThreadJoined(Snowflake id); void OnThreadRemoved(Snowflake id); @@ -89,6 +97,8 @@ protected: void AddPrivateChannels(); void UpdateCreateDMChannel(const ChannelData &channel); + void OnMessageAck(const MessageAckData &data); + void OnMessageCreate(const Message &msg); Gtk::TreeModel::Path m_path_for_menu; @@ -99,12 +109,16 @@ protected: Gtk::MenuItem m_menu_guild_copy_id; Gtk::MenuItem m_menu_guild_settings; Gtk::MenuItem m_menu_guild_leave; + Gtk::MenuItem m_menu_guild_mark_as_read; + Gtk::MenuItem m_menu_guild_toggle_mute; Gtk::Menu m_menu_category; Gtk::MenuItem m_menu_category_copy_id; 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; @@ -116,10 +130,14 @@ protected: Gtk::MenuItem m_menu_thread_archive; 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 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; + Snowflake m_active_channel; + // (GetIteratorForChannelFromID is rather slow) // only temporary since i dont want to worry about maintaining this map std::unordered_map<Snowflake, Gtk::TreeModel::iterator> m_tmp_channel_map; diff --git a/src/components/channelscellrenderer.cpp b/src/components/channelscellrenderer.cpp index 2526753..325d45a 100644 --- a/src/components/channelscellrenderer.cpp +++ b/src/components/channelscellrenderer.cpp @@ -1,11 +1,19 @@ -#include "channelscellrenderer.hpp" #include "abaddon.hpp" +#include "channelscellrenderer.hpp" #include <gtkmm.h> +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() , 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 +34,10 @@ Glib::PropertyProxy<RenderType> CellRendererChannels::property_type() { return m_property_type.get_proxy(); } +Glib::PropertyProxy<uint64_t> CellRendererChannels::property_id() { + return m_property_id.get_proxy(); +} + Glib::PropertyProxy<Glib::ustring> CellRendererChannels::property_name() { return m_property_name.get_proxy(); } @@ -192,7 +204,7 @@ void CellRendererChannels::render_vfunc_guild(const Cairo::RefPtr<Cairo::Context const double icon_w = pixbuf_w; const double icon_h = pixbuf_h; - const double icon_x = background_area.get_x(); + 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; @@ -233,6 +245,32 @@ void CellRendererChannels::render_vfunc_guild(const Cairo::RefPtr<Cairo::Context cr->rectangle(icon_x, icon_y, icon_w, icon_h); cr->fill(); } + + // unread + + const auto id = m_property_id.get_value(); + + auto &discord = Abaddon::Get().GetDiscordClient(); + int total_mentions; + const auto has_unread = discord.GetUnreadStateForGuild(id, total_mentions); + + 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(); + 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<Gtk::Paned *>(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 @@ -321,13 +359,51 @@ void CellRendererChannels::render_vfunc_channel(const Cairo::RefPtr<Cairo::Conte 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); + + // move to style in msys? + static Gdk::RGBA sfw_unmuted("#FFFFFF"); + const auto nsfw_color = Gdk::RGBA(Abaddon::Get().GetSettings().NSFWChannelColor); if (m_property_nsfw.get_value()) m_renderer_text.property_foreground_rgba() = nsfw_color; + else + m_renderer_text.property_foreground_rgba() = sfw_unmuted; + if (is_muted) { + auto col = m_renderer_text.property_foreground_rgba().get_value(); + col.set_red(col.get_red() * 0.5); + col.set_green(col.get_green() * 0.5); + col.set_blue(col.get_blue() * 0.5); + m_renderer_text.property_foreground_rgba() = col; + } m_renderer_text.render(cr, widget, background_area, text_cell_area, flags); - // setting property_foreground_rgba() sets this to true which makes non-nsfw cells use the property too which is bad - // so unset it + // unset foreground to default so properties dont bleed m_renderer_text.property_foreground_set() = false; + + // 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(); + } + + if (unread_state < 1) return; + auto *paned = static_cast<Gtk::Paned *>(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, unread_state, edge, cell_area); + } } // thread @@ -436,19 +512,75 @@ void CellRendererChannels::render_vfunc_dm(const Cairo::RefPtr<Cairo::Context> & 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<Cairo::Context> &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<Cairo::Context> &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 ce8da54..95ff4fe 100644 --- a/src/components/channelscellrenderer.hpp +++ b/src/components/channelscellrenderer.hpp @@ -3,6 +3,7 @@ #include <gdkmm/pixbufanimation.h> #include <glibmm/property.h> #include <map> +#include "discord/snowflake.hpp" enum class RenderType : uint8_t { Guild, @@ -20,6 +21,7 @@ public: virtual ~CellRendererChannels(); Glib::PropertyProxy<RenderType> property_type(); + Glib::PropertyProxy<uint64_t> property_id(); Glib::PropertyProxy<Glib::ustring> property_name(); Glib::PropertyProxy<Glib::RefPtr<Gdk::Pixbuf>> property_icon(); Glib::PropertyProxy<Glib::RefPtr<Gdk::PixbufAnimation>> property_icon_animation(); @@ -103,11 +105,15 @@ protected: const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags); + static void cairo_path_rounded_rect(const Cairo::RefPtr<Cairo::Context> &cr, double x, double y, double w, double h, double r); + void unread_render_mentions(const Cairo::RefPtr<Cairo::Context> &cr, Gtk::Widget &widget, int mentions, int edge, const Gdk::Rectangle &cell_area); + private: Gtk::CellRendererText m_renderer_text; - Glib::Property<RenderType> m_property_type; // all - Glib::Property<Glib::ustring> m_property_name; // all + Glib::Property<RenderType> m_property_type; // all + Glib::Property<Glib::ustring> m_property_name; // all + Glib::Property<uint64_t> m_property_id; Glib::Property<Glib::RefPtr<Gdk::Pixbuf>> m_property_pixbuf; // guild, dm Glib::Property<Glib::RefPtr<Gdk::PixbufAnimation>> m_property_pixbuf_animation; // guild Glib::Property<bool> m_property_expanded; // category |