summaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/cellrenderermemberlist.cpp140
-rw-r--r--src/components/cellrenderermemberlist.hpp65
-rw-r--r--src/components/memberlist.cpp365
-rw-r--r--src/components/memberlist.hpp65
4 files changed, 424 insertions, 211 deletions
diff --git a/src/components/cellrenderermemberlist.cpp b/src/components/cellrenderermemberlist.cpp
new file mode 100644
index 0000000..66b223e
--- /dev/null
+++ b/src/components/cellrenderermemberlist.cpp
@@ -0,0 +1,140 @@
+#include "cellrenderermemberlist.hpp"
+
+CellRendererMemberList::CellRendererMemberList()
+ : Glib::ObjectBase(typeid(CellRendererMemberList))
+ , m_property_type(*this, "render-type")
+ , m_property_id(*this, "id")
+ , m_property_name(*this, "name")
+ , m_property_pixbuf(*this, "pixbuf")
+ , m_property_color(*this, "color") {
+ property_mode() = Gtk::CELL_RENDERER_MODE_ACTIVATABLE;
+ property_xpad() = 2;
+ property_ypad() = 2;
+ m_property_name.get_proxy().signal_changed().connect([this]() {
+ m_renderer_text.property_markup() = m_property_name;
+ });
+}
+
+Glib::PropertyProxy<MemberListRenderType> CellRendererMemberList::property_type() {
+ return m_property_type.get_proxy();
+}
+
+Glib::PropertyProxy<uint64_t> CellRendererMemberList::property_id() {
+ return m_property_id.get_proxy();
+}
+
+Glib::PropertyProxy<Glib::ustring> CellRendererMemberList::property_name() {
+ return m_property_name.get_proxy();
+}
+
+Glib::PropertyProxy<Glib::RefPtr<Gdk::Pixbuf>> CellRendererMemberList::property_pixbuf() {
+ return m_property_pixbuf.get_proxy();
+}
+
+Glib::PropertyProxy<Gdk::RGBA> CellRendererMemberList::property_color() {
+ return m_property_color.get_proxy();
+}
+
+void CellRendererMemberList::get_preferred_width_vfunc(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {
+ switch (m_property_type.get_value()) {
+ case MemberListRenderType::Role:
+ return get_preferred_width_vfunc_role(widget, minimum_width, natural_width);
+ case MemberListRenderType::Member:
+ return get_preferred_width_vfunc_member(widget, minimum_width, natural_width);
+ }
+}
+
+void CellRendererMemberList::get_preferred_width_for_height_vfunc(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const {
+ switch (m_property_type.get_value()) {
+ case MemberListRenderType::Role:
+ return get_preferred_width_for_height_vfunc_role(widget, height, minimum_width, natural_width);
+ case MemberListRenderType::Member:
+ return get_preferred_width_for_height_vfunc_member(widget, height, minimum_width, natural_width);
+ }
+}
+
+void CellRendererMemberList::get_preferred_height_vfunc(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {
+ switch (m_property_type.get_value()) {
+ case MemberListRenderType::Role:
+ return get_preferred_height_vfunc_role(widget, minimum_width, natural_width);
+ case MemberListRenderType::Member:
+ return get_preferred_height_vfunc_member(widget, minimum_width, natural_width);
+ }
+}
+
+void CellRendererMemberList::get_preferred_height_for_width_vfunc(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const {
+ switch (m_property_type.get_value()) {
+ case MemberListRenderType::Role:
+ return get_preferred_height_for_width_vfunc_role(widget, width, minimum_height, natural_height);
+ case MemberListRenderType::Member:
+ return get_preferred_height_for_width_vfunc_member(widget, width, minimum_height, natural_height);
+ }
+}
+
+void CellRendererMemberList::render_vfunc(const Cairo::RefPtr<Cairo::Context> &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags) {
+ switch (m_property_type.get_value()) {
+ case MemberListRenderType::Role:
+ return render_vfunc_role(cr, widget, background_area, cell_area, flags);
+ case MemberListRenderType::Member:
+ return render_vfunc_member(cr, widget, background_area, cell_area, flags);
+ }
+}
+
+void CellRendererMemberList::get_preferred_width_vfunc_role(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {
+ m_renderer_text.get_preferred_width(widget, minimum_width, natural_width);
+}
+
+void CellRendererMemberList::get_preferred_width_for_height_vfunc_role(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const {
+ m_renderer_text.get_preferred_width_for_height(widget, height, minimum_width, natural_width);
+}
+
+void CellRendererMemberList::get_preferred_height_vfunc_role(Gtk::Widget &widget, int &minimum_height, int &natural_height) const {
+ m_renderer_text.get_preferred_height(widget, minimum_height, natural_height);
+}
+
+void CellRendererMemberList::get_preferred_height_for_width_vfunc_role(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const {
+ m_renderer_text.get_preferred_height_for_width(widget, width, minimum_height, natural_height);
+}
+
+void CellRendererMemberList::render_vfunc_role(const Cairo::RefPtr<Cairo::Context> &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags) {
+ m_renderer_text.render(cr, widget, background_area, cell_area, flags);
+}
+
+void CellRendererMemberList::get_preferred_width_vfunc_member(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {
+ m_renderer_text.get_preferred_width(widget, minimum_width, natural_width);
+}
+
+void CellRendererMemberList::get_preferred_width_for_height_vfunc_member(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const {
+ m_renderer_text.get_preferred_width_for_height(widget, height, minimum_width, natural_width);
+}
+
+void CellRendererMemberList::get_preferred_height_vfunc_member(Gtk::Widget &widget, int &minimum_height, int &natural_height) const {
+ m_renderer_text.get_preferred_height(widget, minimum_height, natural_height);
+}
+
+void CellRendererMemberList::get_preferred_height_for_width_vfunc_member(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const {
+ m_renderer_text.get_preferred_height_for_width(widget, width, minimum_height, natural_height);
+}
+
+void CellRendererMemberList::render_vfunc_member(const Cairo::RefPtr<Cairo::Context> &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags) {
+ Gdk::Rectangle text_cell_area = cell_area;
+ text_cell_area.set_x(22);
+ const auto color = m_property_color.get_value();
+ if (color.get_alpha_u() > 0) {
+ m_renderer_text.property_foreground_rgba().set_value(color);
+ }
+ m_renderer_text.render(cr, widget, background_area, text_cell_area, flags);
+ m_renderer_text.property_foreground_set().set_value(false);
+
+ const double icon_x = background_area.get_x() + 6.0;
+ const double icon_y = background_area.get_y() + background_area.get_height() / 2.0 - 8.0;
+ Gdk::Cairo::set_source_pixbuf(cr, m_property_pixbuf.get_value(), icon_x, icon_y);
+ cr->rectangle(icon_x, icon_y, 16.0, 16.0);
+ cr->fill();
+
+ m_signal_render.emit(m_property_id.get_value());
+}
+
+CellRendererMemberList::type_signal_render CellRendererMemberList::signal_render() {
+ return m_signal_render;
+}
diff --git a/src/components/cellrenderermemberlist.hpp b/src/components/cellrenderermemberlist.hpp
new file mode 100644
index 0000000..7a49ccf
--- /dev/null
+++ b/src/components/cellrenderermemberlist.hpp
@@ -0,0 +1,65 @@
+#pragma once
+#include <gtkmm/cellrenderer.h>
+
+enum class MemberListRenderType : uint8_t {
+ Role,
+ Member,
+};
+
+class CellRendererMemberList : public Gtk::CellRenderer {
+public:
+ CellRendererMemberList();
+ ~CellRendererMemberList() override = default;
+
+ Glib::PropertyProxy<MemberListRenderType> property_type();
+ Glib::PropertyProxy<uint64_t> property_id();
+ Glib::PropertyProxy<Glib::ustring> property_name();
+ Glib::PropertyProxy<Glib::RefPtr<Gdk::Pixbuf>> property_pixbuf();
+ Glib::PropertyProxy<Gdk::RGBA> property_color();
+
+protected:
+ void get_preferred_width_vfunc(Gtk::Widget &widget, int &minimum_width, int &natural_width) const override;
+ void get_preferred_width_for_height_vfunc(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const override;
+ void get_preferred_height_vfunc(Gtk::Widget &widget, int &minimum_height, int &natural_height) const override;
+ void get_preferred_height_for_width_vfunc(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const override;
+ void render_vfunc(const Cairo::RefPtr<Cairo::Context> &cr,
+ Gtk::Widget &widget,
+ const Gdk::Rectangle &background_area,
+ const Gdk::Rectangle &cell_area,
+ Gtk::CellRendererState flags) override;
+
+ void get_preferred_width_vfunc_role(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
+ void get_preferred_width_for_height_vfunc_role(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
+ void get_preferred_height_vfunc_role(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
+ void get_preferred_height_for_width_vfunc_role(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
+ void render_vfunc_role(const Cairo::RefPtr<Cairo::Context> &cr,
+ Gtk::Widget &widget,
+ const Gdk::Rectangle &background_area,
+ const Gdk::Rectangle &cell_area,
+ Gtk::CellRendererState flags);
+
+ void get_preferred_width_vfunc_member(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
+ void get_preferred_width_for_height_vfunc_member(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
+ void get_preferred_height_vfunc_member(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
+ void get_preferred_height_for_width_vfunc_member(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
+ void render_vfunc_member(const Cairo::RefPtr<Cairo::Context> &cr,
+ Gtk::Widget &widget,
+ const Gdk::Rectangle &background_area,
+ const Gdk::Rectangle &cell_area,
+ Gtk::CellRendererState flags);
+
+private:
+ Gtk::CellRendererText m_renderer_text;
+
+ Glib::Property<MemberListRenderType> m_property_type;
+ Glib::Property<uint64_t> m_property_id;
+ Glib::Property<Glib::ustring> m_property_name;
+ Glib::Property<Glib::RefPtr<Gdk::Pixbuf>> m_property_pixbuf;
+ Glib::Property<Gdk::RGBA> m_property_color;
+
+ using type_signal_render = sigc::signal<void(uint64_t)>;
+ type_signal_render m_signal_render;
+
+public:
+ type_signal_render signal_render();
+};
diff --git a/src/components/memberlist.cpp b/src/components/memberlist.cpp
index 975b527..0796e85 100644
--- a/src/components/memberlist.cpp
+++ b/src/components/memberlist.cpp
@@ -1,233 +1,228 @@
#include "memberlist.hpp"
-#include "lazyimage.hpp"
-#include "statusindicator.hpp"
-
-constexpr static const int MaxMemberListRows = 200;
-
-MemberListUserRow::MemberListUserRow(const std::optional<GuildData> &guild, const UserData &data) {
- ID = data.ID;
- m_ev = Gtk::manage(new Gtk::EventBox);
- m_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
- m_label = Gtk::manage(new Gtk::Label);
- m_avatar = Gtk::manage(new LazyImage(16, 16));
- m_status_indicator = Gtk::manage(new StatusIndicator(ID));
-
- if (Abaddon::Get().GetSettings().ShowOwnerCrown && guild.has_value() && guild->OwnerID == data.ID) {
- try {
- const static auto crown_path = Abaddon::GetResPath("/crown.png");
- auto pixbuf = Gdk::Pixbuf::create_from_file(crown_path, 12, 12);
- m_crown = Gtk::manage(new Gtk::Image(pixbuf));
- m_crown->set_valign(Gtk::ALIGN_CENTER);
- m_crown->set_margin_end(8);
- } catch (...) {}
- }
-
- m_status_indicator->set_margin_start(3);
-
- if (guild.has_value())
- m_avatar->SetURL(data.GetAvatarURL(guild->ID, "png"));
- else
- m_avatar->SetURL(data.GetAvatarURL("png"));
-
- get_style_context()->add_class("members-row");
- get_style_context()->add_class("members-row-member");
- m_label->get_style_context()->add_class("members-row-label");
- m_avatar->get_style_context()->add_class("members-row-avatar");
-
- m_label->set_single_line_mode(true);
- m_label->set_ellipsize(Pango::ELLIPSIZE_END);
-
- // 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;
- m_label->set_use_markup(true);
- m_label->set_markup("<span color='#" + IntToCSSColor(color) + "'>" + Glib::Markup::escape_text(display) + "</span>");
- } else {
- m_label->set_text(display);
+MemberList::MemberList()
+ : m_model(Gtk::TreeStore::create(m_columns))
+ , m_menu_role_copy_id("_Copy ID", true) {
+ m_main.get_style_context()->add_class("member-list");
+
+ m_view.set_hexpand(true);
+ m_view.set_vexpand(true);
+
+ m_view.set_show_expanders(false);
+ m_view.set_enable_search(false);
+ m_view.set_headers_visible(false);
+ m_view.get_selection()->set_mode(Gtk::SELECTION_NONE);
+ m_view.set_model(m_model);
+ m_view.signal_button_press_event().connect(sigc::mem_fun(*this, &MemberList::OnButtonPressEvent), false);
+
+ m_main.add(m_view);
+ m_main.show_all_children();
+
+ auto *column = Gtk::make_managed<Gtk::TreeView::Column>("display");
+ auto *renderer = Gtk::make_managed<CellRendererMemberList>();
+ column->pack_start(*renderer);
+ column->add_attribute(renderer->property_type(), m_columns.m_type);
+ column->add_attribute(renderer->property_id(), m_columns.m_id);
+ column->add_attribute(renderer->property_name(), m_columns.m_name);
+ column->add_attribute(renderer->property_pixbuf(), m_columns.m_pixbuf);
+ column->add_attribute(renderer->property_color(), m_columns.m_color);
+ m_view.append_column(*column);
+
+ m_model->set_sort_column(m_columns.m_sort, Gtk::SORT_ASCENDING);
+ m_model->set_sort_func(m_columns.m_sort, [this](const Gtk::TreeModel::iterator &a, const Gtk::TreeModel::iterator &b) -> int {
+ if ((*a)[m_columns.m_type] == MemberListRenderType::Role) {
+ return (*b)[m_columns.m_sort] - (*a)[m_columns.m_sort];
+ } else if ((*a)[m_columns.m_type] == MemberListRenderType::Member) {
+ return static_cast<Glib::ustring>((*a)[m_columns.m_name]).compare((*b)[m_columns.m_name]);
}
- } else {
- m_label->set_text(display);
- }
-
- m_label->set_halign(Gtk::ALIGN_START);
- m_box->add(*m_avatar);
- m_box->add(*m_status_indicator);
- m_box->add(*m_label);
- if (m_crown != nullptr)
- m_box->add(*m_crown);
- m_ev->add(*m_box);
- add(*m_ev);
- show_all();
-}
-
-MemberList::MemberList() {
- m_main = Gtk::manage(new Gtk::ScrolledWindow);
- m_listbox = Gtk::manage(new Gtk::ListBox);
+ return 0;
+ });
- m_listbox->get_style_context()->add_class("members");
+ renderer->signal_render().connect(sigc::mem_fun(*this, &MemberList::OnCellRender));
- m_listbox->set_selection_mode(Gtk::SELECTION_NONE);
+ // Menu stuff
- m_main->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
- m_main->add(*m_listbox);
- m_main->show_all();
-}
+ m_menu_role.append(m_menu_role_copy_id);
+ m_menu_role.show_all();
-Gtk::Widget *MemberList::GetRoot() const {
- return m_main;
-}
-
-void MemberList::Clear() {
- SetActiveChannel(Snowflake::Invalid);
- UpdateMemberList();
+ m_menu_role_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]));
+ });
}
-void MemberList::SetActiveChannel(Snowflake id) {
- m_chan_id = id;
- m_guild_id = Snowflake::Invalid;
- if (m_chan_id.IsValid()) {
- const auto chan = Abaddon::Get().GetDiscordClient().GetChannel(id);
- if (chan.has_value() && chan->GuildID.has_value()) m_guild_id = *chan->GuildID;
- }
+Gtk::Widget *MemberList::GetRoot() {
+ return &m_main;
}
void MemberList::UpdateMemberList() {
- m_id_to_row.clear();
-
- auto children = m_listbox->get_children();
- auto it = children.begin();
- while (it != children.end()) {
- delete *it;
- it++;
- }
-
- if (!Abaddon::Get().GetDiscordClient().IsStarted()) return;
- if (!m_chan_id.IsValid()) return;
+ Clear();
+ if (!m_active_channel.IsValid()) return;
auto &discord = Abaddon::Get().GetDiscordClient();
- const auto chan = discord.GetChannel(m_chan_id);
- if (!chan.has_value()) return;
- if (chan->Type == ChannelType::DM || chan->Type == ChannelType::GROUP_DM) {
- int num_rows = 0;
- for (const auto &user : chan->GetDMRecipients()) {
- if (num_rows++ > MaxMemberListRows) break;
- auto *row = Gtk::manage(new MemberListUserRow(std::nullopt, user));
- m_id_to_row[user.ID] = row;
- AttachUserMenuHandler(row, user.ID);
- m_listbox->add(*row);
- }
+ const auto channel = discord.GetChannel(m_active_channel);
+ if (!channel.has_value()) {
+ spdlog::get("ui")->warn("attempted to update member list with unfetchable channel");
return;
}
+ if (channel->IsDM()) {
+ for (const auto &user : channel->GetDMRecipients()) {
+ auto row = *m_model->append();
+ row[m_columns.m_type] = MemberListRenderType::Member;
+ row[m_columns.m_id] = user.ID;
+ row[m_columns.m_name] = user.GetDisplayNameEscaped();
+ }
+ }
+
+ const auto guild = discord.GetGuild(m_active_guild);
+ if (!guild.has_value()) return;
+
std::set<Snowflake> ids;
- if (chan->IsThread()) {
- const auto x = discord.GetUsersInThread(m_chan_id);
+ if (channel->IsThread()) {
+ const auto x = discord.GetUsersInThread(m_active_channel);
ids = { x.begin(), x.end() };
- } else
- ids = discord.GetUsersInGuild(m_guild_id);
+ } else {
+ ids = discord.GetUsersInGuild(m_active_guild);
+ }
- // process all the shit first so its in proper order
- std::map<int, RoleData> pos_to_role;
- std::map<int, std::vector<UserData>> pos_to_users;
+ std::unordered_map<Snowflake, std::vector<UserData>> role_to_users;
std::unordered_map<Snowflake, int> user_to_color;
std::vector<Snowflake> roleless_users;
- for (const auto &id : ids) {
- auto user = discord.GetUser(id);
- if (!user.has_value() || user->IsDeleted())
- continue;
+ for (const auto user_id : ids) {
+ auto user = discord.GetUser(user_id);
+ if (!user.has_value() || user->IsDeleted()) continue;
- auto pos_role_id = discord.GetMemberHoistedRole(m_guild_id, id); // role for positioning
- auto col_role_id = discord.GetMemberHoistedRole(m_guild_id, id, true); // role for color
- auto pos_role = discord.GetRole(pos_role_id);
- auto col_role = discord.GetRole(col_role_id);
+ const auto pos_role_id = discord.GetMemberHoistedRole(m_active_guild, user_id);
+ const auto col_role_id = discord.GetMemberHoistedRole(m_active_guild, user_id, true);
+ const auto pos_role = discord.GetRole(pos_role_id);
+ const auto col_role = discord.GetRole(col_role_id);
if (!pos_role.has_value()) {
- roleless_users.push_back(id);
+ roleless_users.push_back(user_id);
continue;
}
- pos_to_role[pos_role->Position] = *pos_role;
- pos_to_users[pos_role->Position].push_back(std::move(*user));
- if (col_role.has_value())
- user_to_color[id] = col_role->Color;
+ role_to_users[pos_role->ID].push_back(std::move(*user));
+ if (col_role.has_value()) user_to_color[user_id] = col_role->Color;
}
- int num_rows = 0;
- const auto guild = discord.GetGuild(m_guild_id);
- if (!guild.has_value()) return;
- auto add_user = [this, &num_rows, guild](const UserData &data) -> bool {
- if (num_rows++ > MaxMemberListRows) return false;
- auto *row = Gtk::manage(new MemberListUserRow(*guild, data));
- m_id_to_row[data.ID] = row;
- AttachUserMenuHandler(row, data.ID);
- m_listbox->add(*row);
- return true;
+ const auto add_user = [this, &guild, &user_to_color](const UserData &user, const Gtk::TreeRow &parent) {
+ auto test = m_model->append(parent->children());
+ auto row = *test;
+ row[m_columns.m_type] = MemberListRenderType::Member;
+ row[m_columns.m_id] = user.ID;
+ row[m_columns.m_name] = user.GetDisplayNameEscaped();
+ row[m_columns.m_pixbuf] = Abaddon::Get().GetImageManager().GetPlaceholder(16);
+ row[m_columns.m_av_requested] = false;
+ if (const auto iter = user_to_color.find(user.ID); iter != user_to_color.end()) {
+ row[m_columns.m_color] = IntToRGBA(iter->second);
+ } else {
+ const static auto transparent = Gdk::RGBA("rgba(0,0,0,0)");
+ row[m_columns.m_color] = transparent;
+ }
+ m_pending_avatars[user.ID] = test;
+ return test;
};
- auto add_role = [this](const std::string &name) {
- auto *role_row = Gtk::manage(new Gtk::ListBoxRow);
- auto *role_lbl = Gtk::manage(new Gtk::Label);
-
- role_row->get_style_context()->add_class("members-row");
- role_row->get_style_context()->add_class("members-row-role");
- role_lbl->get_style_context()->add_class("members-row-label");
-
- role_lbl->set_single_line_mode(true);
- role_lbl->set_ellipsize(Pango::ELLIPSIZE_END);
- role_lbl->set_use_markup(true);
- role_lbl->set_markup("<b>" + Glib::Markup::escape_text(name) + "</b>");
- role_lbl->set_halign(Gtk::ALIGN_START);
- role_row->add(*role_lbl);
- role_row->show_all();
- m_listbox->add(*role_row);
+ const auto add_role = [this](const RoleData &role) {
+ auto row = *m_model->append();
+ row[m_columns.m_type] = MemberListRenderType::Role;
+ row[m_columns.m_id] = role.ID;
+ row[m_columns.m_name] = "<b>" + role.GetEscapedName() + "</b>";
+ row[m_columns.m_sort] = role.Position;
+ return row;
};
- for (auto it = pos_to_role.crbegin(); it != pos_to_role.crend(); it++) {
- auto pos = it->first;
- const auto &role = it->second;
+ for (const auto &[role_id, users] : role_to_users) {
+ const auto role = discord.GetRole(role_id);
+ if (!role.has_value()) continue;
+
+ auto role_row = add_role(*role);
+ for (const auto &user : users) add_user(user, role_row);
+ }
+
+ auto everyone_role = *m_model->append();
+ everyone_role[m_columns.m_type] = MemberListRenderType::Role;
+ everyone_role[m_columns.m_id] = m_active_guild; // yes thats how the role works
+ everyone_role[m_columns.m_name] = "<b>@everyone</b>";
+ everyone_role[m_columns.m_sort] = 0;
- add_role(role.Name);
+ for (const auto id : roleless_users) {
+ const auto user = discord.GetUser(id);
+ if (user.has_value()) add_user(*user, everyone_role);
+ }
- if (pos_to_users.find(pos) == pos_to_users.end()) continue;
+ m_view.expand_all();
+}
- auto &users = pos_to_users.at(pos);
- AlphabeticalSort(users.begin(), users.end(), [](const auto &e) { return e.Username; });
+void MemberList::Clear() {
+ m_model->clear();
+ m_pending_avatars.clear();
+}
- for (const auto &data : users)
- if (!add_user(data)) return;
+void MemberList::SetActiveChannel(Snowflake id) {
+ m_active_channel = id;
+ m_active_guild = Snowflake::Invalid;
+ if (m_active_channel.IsValid()) {
+ const auto channel = Abaddon::Get().GetDiscordClient().GetChannel(m_active_channel);
+ if (channel.has_value() && channel->GuildID.has_value()) m_active_guild = *channel->GuildID;
}
+}
- if (chan->Type == ChannelType::DM || chan->Type == ChannelType::GROUP_DM)
- add_role("Users");
- else
- add_role("@everyone");
- for (const auto &id : roleless_users) {
- const auto user = discord.GetUser(id);
- if (user.has_value())
- if (!add_user(*user)) return;
+void MemberList::OnCellRender(uint64_t id) {
+ Snowflake real_id = id;
+ if (const auto iter = m_pending_avatars.find(real_id); iter != m_pending_avatars.end()) {
+ auto row = iter->second;
+ m_pending_avatars.erase(iter);
+ if (!row) return;
+ if ((*row)[m_columns.m_av_requested]) return;
+ (*row)[m_columns.m_av_requested] = true;
+ const auto user = Abaddon::Get().GetDiscordClient().GetUser(real_id);
+ if (!user.has_value()) return;
+ const auto cb = [this, row](const Glib::RefPtr<Gdk::Pixbuf> &pb) {
+ // for some reason row::operator bool() returns true when m_model->iter_is_valid returns false
+ // idk why since other code already does essentially the same thing im doing here
+ // iter_is_valid is "slow" according to gtk but the only other workaround i can think of would be worse
+ if (row && m_model->iter_is_valid(row)) {
+ (*row)[m_columns.m_pixbuf] = pb->scale_simple(16, 16, Gdk::INTERP_BILINEAR);
+ }
+ };
+ Abaddon::Get().GetImageManager().LoadFromURL(user->GetAvatarURL("png", "16"), cb);
}
}
-void MemberList::AttachUserMenuHandler(Gtk::ListBoxRow *row, Snowflake id) {
- row->signal_button_press_event().connect([this, id](GdkEventButton *e) -> bool {
- if (e->type == GDK_BUTTON_PRESS && e->button == GDK_BUTTON_SECONDARY) {
- Abaddon::Get().ShowUserMenu(reinterpret_cast<const GdkEvent *>(e), id, m_guild_id);
- return true;
+bool MemberList::OnButtonPressEvent(GdkEventButton *ev) {
+ if (ev->button == GDK_BUTTON_SECONDARY && ev->type == GDK_BUTTON_PRESS) {
+ if (m_view.get_path_at_pos(static_cast<int>(ev->x), static_cast<int>(ev->y), m_path_for_menu)) {
+ switch ((*m_model->get_iter(m_path_for_menu))[m_columns.m_type]) {
+ case MemberListRenderType::Role:
+ OnRoleSubmenuPopup();
+ m_menu_role.popup_at_pointer(reinterpret_cast<GdkEvent *>(ev));
+ break;
+ case MemberListRenderType::Member:
+ Abaddon::Get().ShowUserMenu(
+ reinterpret_cast<GdkEvent *>(ev),
+ static_cast<Snowflake>((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]),
+ m_active_guild);
+ break;
+ }
}
+ return true;
+ }
+ return false;
+}
- return false;
- });
+void MemberList::OnRoleSubmenuPopup() {
+}
+
+MemberList::ModelColumns::ModelColumns() {
+ add(m_type);
+ add(m_id);
+ add(m_name);
+ add(m_pixbuf);
+ add(m_av_requested);
+ add(m_color);
+ add(m_sort);
}
diff --git a/src/components/memberlist.hpp b/src/components/memberlist.hpp
index 7d5da10..658114e 100644
--- a/src/components/memberlist.hpp
+++ b/src/components/memberlist.hpp
@@ -1,43 +1,56 @@
#pragma once
-#include <mutex>
-#include <unordered_map>
-#include <optional>
-#include "discord/discord.hpp"
+#include <gdkmm/pixbuf.h>
+#include <gtkmm/treemodel.h>
+#include <gtkmm/treestore.h>
+#include <gtkmm/treeview.h>
-class LazyImage;
-class StatusIndicator;
-class MemberListUserRow : public Gtk::ListBoxRow {
-public:
- MemberListUserRow(const std::optional<GuildData> &guild, const UserData &data);
+#include <unordered_map>
- Snowflake ID;
-
-private:
- Gtk::EventBox *m_ev;
- Gtk::Box *m_box;
- LazyImage *m_avatar;
- StatusIndicator *m_status_indicator;
- Gtk::Label *m_label;
- Gtk::Image *m_crown = nullptr;
-};
+#include "cellrenderermemberlist.hpp"
+#include "discord/snowflake.hpp"
class MemberList {
public:
MemberList();
- Gtk::Widget *GetRoot() const;
+ Gtk::Widget *GetRoot();
void UpdateMemberList();
void Clear();
void SetActiveChannel(Snowflake id);
private:
- void AttachUserMenuHandler(Gtk::ListBoxRow *row, Snowflake id);
+ void OnCellRender(uint64_t id);
+ bool OnButtonPressEvent(GdkEventButton *ev);
+
+ void OnRoleSubmenuPopup();
+
+ class ModelColumns : public Gtk::TreeModel::ColumnRecord {
+ public:
+ ModelColumns();
+
+ Gtk::TreeModelColumn<MemberListRenderType> m_type;
+ Gtk::TreeModelColumn<uint64_t> m_id;
+ Gtk::TreeModelColumn<Glib::ustring> m_name;
+ Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf>> m_pixbuf;
+ Gtk::TreeModelColumn<Gdk::RGBA> m_color;
+ Gtk::TreeModelColumn<int> m_sort;
+
+ Gtk::TreeModelColumn<bool> m_av_requested;
+ };
+
+ ModelColumns m_columns;
+ Glib::RefPtr<Gtk::TreeStore> m_model;
+ Gtk::TreeView m_view;
+
+ Gtk::TreePath m_path_for_menu;
+
+ Gtk::ScrolledWindow m_main;
- Gtk::ScrolledWindow *m_main;
- Gtk::ListBox *m_listbox;
+ Snowflake m_active_channel;
+ Snowflake m_active_guild;
- Snowflake m_guild_id;
- Snowflake m_chan_id;
+ Gtk::Menu m_menu_role;
+ Gtk::MenuItem m_menu_role_copy_id;
- std::unordered_map<Snowflake, Gtk::ListBoxRow *> m_id_to_row;
+ std::unordered_map<Snowflake, Gtk::TreeIter> m_pending_avatars;
};