forked from OpenGamers/abaddon
Compare commits
5 Commits
master
...
member-lis
Author | SHA1 | Date | |
---|---|---|---|
|
190118bb58 | ||
|
6926b12a50 | ||
|
fb010b5ac5 | ||
|
b0370ee489 | ||
|
00fe6642a9 |
@ -1,85 +1,32 @@
|
||||
#include "memberlist.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include "util.hpp"
|
||||
#include "lazyimage.hpp"
|
||||
#include "statusindicator.hpp"
|
||||
|
||||
constexpr static const int MaxMemberListRows = 200;
|
||||
MemberList::MemberList()
|
||||
: m_model(Gtk::TreeStore::create(m_columns)) {
|
||||
add(m_view);
|
||||
show_all_children();
|
||||
|
||||
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));
|
||||
m_view.set_activate_on_single_click(true);
|
||||
m_view.set_hexpand(true);
|
||||
m_view.set_vexpand(true);
|
||||
|
||||
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_view.set_show_expanders(false);
|
||||
m_view.set_enable_search(false);
|
||||
m_view.set_headers_visible(false);
|
||||
m_view.set_model(m_model);
|
||||
|
||||
m_status_indicator->set_margin_start(3);
|
||||
m_model->set_sort_column(m_columns.m_sort, Gtk::SORT_DESCENDING);
|
||||
|
||||
if (guild.has_value())
|
||||
m_avatar->SetURL(data.GetAvatarURL(guild->ID, "png"));
|
||||
else
|
||||
m_avatar->SetURL(data.GetAvatarURL("png"));
|
||||
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_markup(), m_columns.m_markup);
|
||||
column->add_attribute(renderer->property_icon(), m_columns.m_icon);
|
||||
m_view.append_column(*column);
|
||||
|
||||
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);
|
||||
|
||||
std::string 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);
|
||||
}
|
||||
} 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);
|
||||
|
||||
m_listbox->get_style_context()->add_class("members");
|
||||
|
||||
m_listbox->set_selection_mode(Gtk::SELECTION_NONE);
|
||||
|
||||
m_main->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
|
||||
m_main->add(*m_listbox);
|
||||
m_main->show_all();
|
||||
}
|
||||
|
||||
Gtk::Widget *MemberList::GetRoot() const {
|
||||
return m_main;
|
||||
m_view.expand_all();
|
||||
}
|
||||
|
||||
void MemberList::Clear() {
|
||||
@ -97,131 +44,110 @@ void MemberList::SetActiveChannel(Snowflake id) {
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
m_model->clear();
|
||||
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);
|
||||
if (!discord.IsStarted()) return;
|
||||
|
||||
const auto channel = discord.GetChannel(m_chan_id);
|
||||
if (!channel.has_value()) return;
|
||||
|
||||
// dm
|
||||
if (channel->IsDM()) {
|
||||
// todo eliminate for dm
|
||||
auto everyone_row = *m_model->append();
|
||||
everyone_row[m_columns.m_type] = MemberListRenderType::Role;
|
||||
everyone_row[m_columns.m_id] = Snowflake::Invalid;
|
||||
everyone_row[m_columns.m_markup] = "<b>Users</b>";
|
||||
|
||||
for (const auto &user : channel->GetDMRecipients()) {
|
||||
auto row = *m_model->append(everyone_row.children());
|
||||
row[m_columns.m_type] = MemberListRenderType::Member;
|
||||
row[m_columns.m_id] = user.ID;
|
||||
row[m_columns.m_markup] = Glib::Markup::escape_text(user.Username + "#" + user.Discriminator);
|
||||
row[m_columns.m_icon] = Abaddon::Get().GetImageManager().GetPlaceholder(16);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
std::set<Snowflake> ids;
|
||||
if (chan->IsThread()) {
|
||||
const auto x = discord.GetUsersInThread(m_chan_id);
|
||||
ids = { x.begin(), x.end() };
|
||||
} else
|
||||
ids = discord.GetUsersInGuild(m_guild_id);
|
||||
std::vector<UserData> users;
|
||||
if (channel->IsThread()) {
|
||||
// auto x = discord.GetUsersInThread(m_chan_id);
|
||||
// ids = { x.begin(), x.end() };
|
||||
} else {
|
||||
users = discord.GetUserDataInGuildBulk(m_guild_id);
|
||||
}
|
||||
|
||||
// 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, int> user_to_color;
|
||||
// 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())
|
||||
for (const auto &user : users) {
|
||||
if (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_id = discord.GetMemberHoistedRole(m_guild_id, user.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);
|
||||
// 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;
|
||||
pos_to_users[pos_role->Position].push_back(user);
|
||||
// if (col_role.has_value())
|
||||
// user_to_color[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);
|
||||
auto add_user = [this](const UserData &user, const Gtk::TreeNodeChildren &node) -> bool {
|
||||
auto row = *m_model->append(node);
|
||||
row[m_columns.m_type] = MemberListRenderType::Member;
|
||||
row[m_columns.m_id] = user.ID;
|
||||
row[m_columns.m_markup] = user.GetEscapedName() + "#" + user.Discriminator;
|
||||
row[m_columns.m_sort] = static_cast<int>(user.ID);
|
||||
row[m_columns.m_icon] = Abaddon::Get().GetImageManager().GetPlaceholder(16);
|
||||
// come on
|
||||
Gtk::TreeRowReference ref(m_model, m_model->get_path(row));
|
||||
Abaddon::Get().GetImageManager().LoadFromURL(user.GetAvatarURL(), [this, ref = std::move(ref)](const Glib::RefPtr<Gdk::Pixbuf> &pb) {
|
||||
if (ref.is_valid()) {
|
||||
auto row = *m_model->get_iter(ref.get_path());
|
||||
row[m_columns.m_icon] = pb->scale_simple(16, 16, Gdk::INTERP_BILINEAR);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
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);
|
||||
auto add_role = [this](const RoleData &role) -> Gtk::TreeRow {
|
||||
auto row = *m_model->append();
|
||||
row[m_columns.m_type] = MemberListRenderType::Role;
|
||||
row[m_columns.m_id] = role.ID;
|
||||
row[m_columns.m_markup] = "<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;
|
||||
|
||||
add_role(role.Name);
|
||||
|
||||
if (pos_to_users.find(pos) == pos_to_users.end()) continue;
|
||||
|
||||
auto &users = pos_to_users.at(pos);
|
||||
AlphabeticalSort(users.begin(), users.end(), [](const auto &e) { return e.Username; });
|
||||
|
||||
for (const auto &data : users)
|
||||
if (!add_user(data)) return;
|
||||
}
|
||||
|
||||
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::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;
|
||||
for (auto &[pos, role] : pos_to_role) {
|
||||
auto role_children = add_role(role).children();
|
||||
if (auto it = pos_to_users.find(pos); it != pos_to_users.end()) {
|
||||
for (const auto &user : it->second) {
|
||||
if (!add_user(user, role_children)) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
m_view.expand_all();
|
||||
}
|
||||
|
||||
MemberList::ModelColumns::ModelColumns() {
|
||||
add(m_type);
|
||||
add(m_id);
|
||||
add(m_markup);
|
||||
add(m_icon);
|
||||
add(m_sort);
|
||||
}
|
||||
|
@ -1,44 +1,37 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <optional>
|
||||
#include "discord/discord.hpp"
|
||||
#include <variant>
|
||||
#include <gtkmm/scrolledwindow.h>
|
||||
#include <gtkmm/treemodel.h>
|
||||
#include <gtkmm/treestore.h>
|
||||
#include <gtkmm/treeview.h>
|
||||
#include "discord/snowflake.hpp"
|
||||
#include "memberlistcellrenderer.hpp"
|
||||
|
||||
class LazyImage;
|
||||
class StatusIndicator;
|
||||
class MemberListUserRow : public Gtk::ListBoxRow {
|
||||
public:
|
||||
MemberListUserRow(const std::optional<GuildData> &guild, const UserData &data);
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
class MemberList {
|
||||
class MemberList : public Gtk::ScrolledWindow {
|
||||
public:
|
||||
MemberList();
|
||||
Gtk::Widget *GetRoot() const;
|
||||
|
||||
void UpdateMemberList();
|
||||
void Clear();
|
||||
void SetActiveChannel(Snowflake id);
|
||||
|
||||
private:
|
||||
void AttachUserMenuHandler(Gtk::ListBoxRow *row, Snowflake id);
|
||||
class ModelColumns : public Gtk::TreeModel::ColumnRecord {
|
||||
public:
|
||||
ModelColumns();
|
||||
|
||||
Gtk::ScrolledWindow *m_main;
|
||||
Gtk::ListBox *m_listbox;
|
||||
Gtk::TreeModelColumn<MemberListRenderType> m_type;
|
||||
Gtk::TreeModelColumn<uint64_t> m_id;
|
||||
Gtk::TreeModelColumn<Glib::ustring> m_markup;
|
||||
Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf>> m_icon;
|
||||
|
||||
Gtk::TreeModelColumn<int> m_sort;
|
||||
};
|
||||
|
||||
ModelColumns m_columns;
|
||||
Glib::RefPtr<Gtk::TreeStore> m_model;
|
||||
Gtk::TreeView m_view;
|
||||
|
||||
Snowflake m_guild_id;
|
||||
Snowflake m_chan_id;
|
||||
|
||||
std::unordered_map<Snowflake, Gtk::ListBoxRow *> m_id_to_row;
|
||||
};
|
||||
|
177
src/components/memberlistcellrenderer.cpp
Normal file
177
src/components/memberlistcellrenderer.cpp
Normal file
@ -0,0 +1,177 @@
|
||||
#include "memberlistcellrenderer.hpp"
|
||||
#include <gdkmm/general.h>
|
||||
|
||||
CellRendererMemberList::CellRendererMemberList()
|
||||
: Glib::ObjectBase(typeid(CellRendererMemberList))
|
||||
, m_property_type(*this, "render-type")
|
||||
, m_property_id(*this, "id")
|
||||
, m_property_markup(*this, "markup")
|
||||
, m_property_icon(*this, "pixbuf") {
|
||||
m_renderer_text.property_ellipsize() = Pango::ELLIPSIZE_END;
|
||||
m_property_markup.get_proxy().signal_changed().connect([this]() {
|
||||
m_renderer_text.property_markup() = m_property_markup;
|
||||
});
|
||||
}
|
||||
|
||||
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_markup() {
|
||||
return m_property_markup.get_proxy();
|
||||
}
|
||||
|
||||
Glib::PropertyProxy<Glib::RefPtr<Gdk::Pixbuf>> CellRendererMemberList::property_icon() {
|
||||
return m_property_icon.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::Member:
|
||||
return get_preferred_width_vfunc_member(widget, minimum_width, natural_width);
|
||||
case MemberListRenderType::Role:
|
||||
return get_preferred_width_vfunc_role(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::Member:
|
||||
return get_preferred_width_for_height_vfunc_member(widget, height, minimum_width, natural_width);
|
||||
case MemberListRenderType::Role:
|
||||
return get_preferred_width_for_height_vfunc_role(widget, height, minimum_width, natural_width);
|
||||
}
|
||||
}
|
||||
|
||||
void CellRendererMemberList::get_preferred_height_vfunc(Gtk::Widget &widget, int &minimum_height, int &natural_height) const {
|
||||
switch (m_property_type.get_value()) {
|
||||
case MemberListRenderType::Member:
|
||||
return get_preferred_height_vfunc_member(widget, minimum_height, natural_height);
|
||||
case MemberListRenderType::Role:
|
||||
return get_preferred_height_vfunc_role(widget, minimum_height, natural_height);
|
||||
}
|
||||
}
|
||||
|
||||
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::Member:
|
||||
return get_preferred_height_for_width_vfunc_member(widget, width, minimum_height, natural_height);
|
||||
case MemberListRenderType::Role:
|
||||
return get_preferred_height_for_width_vfunc_role(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::Member:
|
||||
return render_vfunc_member(cr, widget, background_area, cell_area, flags);
|
||||
case MemberListRenderType::Role:
|
||||
return render_vfunc_role(cr, widget, background_area, cell_area, flags);
|
||||
}
|
||||
}
|
||||
|
||||
void CellRendererMemberList::get_preferred_width_vfunc_role(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {
|
||||
int text_min, text_nat;
|
||||
m_renderer_text.get_preferred_width(widget, text_min, text_nat);
|
||||
int xpad, ypad;
|
||||
get_padding(xpad, ypad);
|
||||
minimum_width = text_min + xpad * 2;
|
||||
natural_width = text_nat + xpad * 2;
|
||||
}
|
||||
|
||||
void CellRendererMemberList::get_preferred_width_for_height_vfunc_role(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const {
|
||||
get_preferred_width_vfunc_role(widget, minimum_width, natural_width);
|
||||
}
|
||||
|
||||
void CellRendererMemberList::get_preferred_height_vfunc_role(Gtk::Widget &widget, int &minimum_height, int &natural_height) const {
|
||||
int text_min, text_nat;
|
||||
m_renderer_text.get_preferred_height(widget, text_min, text_nat);
|
||||
int xpad, ypad;
|
||||
get_padding(xpad, ypad);
|
||||
minimum_height = text_min + ypad * 2;
|
||||
natural_height = text_nat + ypad * 2;
|
||||
}
|
||||
|
||||
void CellRendererMemberList::get_preferred_height_for_width_vfunc_role(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const {
|
||||
get_preferred_height_vfunc_role(widget, 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) {
|
||||
Gtk::Requisition text_min, text_nat, min, nat;
|
||||
m_renderer_text.get_preferred_size(widget, text_min, text_nat);
|
||||
get_preferred_size(widget, min, nat);
|
||||
|
||||
int x = background_area.get_x() + 5;
|
||||
int y = background_area.get_y() + background_area.get_height() / 2.0 - text_nat.height / 2.0;
|
||||
int w = text_nat.width;
|
||||
int h = text_nat.height;
|
||||
|
||||
Gdk::Rectangle text_cell_area(x, y, w, h);
|
||||
m_renderer_text.render(cr, widget, background_area, text_cell_area, flags);
|
||||
}
|
||||
|
||||
void CellRendererMemberList::get_preferred_width_vfunc_member(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {
|
||||
int text_min, text_nat;
|
||||
m_renderer_text.get_preferred_width(widget, text_min, text_nat);
|
||||
int xpad, ypad;
|
||||
get_padding(xpad, ypad);
|
||||
minimum_width = text_min + xpad * 2;
|
||||
natural_width = text_nat + xpad * 2;
|
||||
}
|
||||
|
||||
void CellRendererMemberList::get_preferred_width_for_height_vfunc_member(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const {
|
||||
get_preferred_width_vfunc_role(widget, minimum_width, natural_width);
|
||||
}
|
||||
|
||||
void CellRendererMemberList::get_preferred_height_vfunc_member(Gtk::Widget &widget, int &minimum_height, int &natural_height) const {
|
||||
int text_min, text_nat;
|
||||
m_renderer_text.get_preferred_height(widget, text_min, text_nat);
|
||||
int xpad, ypad;
|
||||
get_padding(xpad, ypad);
|
||||
minimum_height = text_min + ypad * 2;
|
||||
natural_height = text_nat + ypad * 2;
|
||||
}
|
||||
|
||||
void CellRendererMemberList::get_preferred_height_for_width_vfunc_member(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const {
|
||||
get_preferred_height_vfunc_role(widget, 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) {
|
||||
Gtk::Requisition text_min, text_nat, min, nat;
|
||||
m_renderer_text.get_preferred_size(widget, text_min, text_nat);
|
||||
get_preferred_size(widget, min, nat);
|
||||
|
||||
int pixbuf_w = 0, pixbuf_h = 0;
|
||||
if (auto pixbuf = m_property_icon.get_value()) {
|
||||
pixbuf_w = pixbuf->get_width();
|
||||
pixbuf_h = pixbuf->get_height();
|
||||
}
|
||||
|
||||
const double icon_w = pixbuf_w;
|
||||
const double icon_h = pixbuf_h;
|
||||
const double icon_x = background_area.get_x() + 3.0;
|
||||
const double icon_y = background_area.get_y() + background_area.get_height() / 2.0 - icon_h / 2.0;
|
||||
|
||||
const double x = icon_x + icon_w + 5;
|
||||
const double y = background_area.get_y() + background_area.get_height() / 2.0 - text_nat.height / 2.0;
|
||||
const double w = text_nat.width;
|
||||
const double h = text_nat.height;
|
||||
|
||||
if (auto pixbuf = m_property_icon.get_value()) {
|
||||
Gdk::Cairo::set_source_pixbuf(cr, pixbuf, icon_x, icon_y);
|
||||
cr->rectangle(icon_x, icon_y, icon_w, icon_h);
|
||||
cr->fill();
|
||||
}
|
||||
|
||||
Gdk::Rectangle text_cell_area(
|
||||
static_cast<int>(x),
|
||||
static_cast<int>(y),
|
||||
static_cast<int>(w),
|
||||
static_cast<int>(h));
|
||||
|
||||
m_renderer_text.render(cr, widget, background_area, text_cell_area, flags);
|
||||
}
|
60
src/components/memberlistcellrenderer.hpp
Normal file
60
src/components/memberlistcellrenderer.hpp
Normal file
@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
#include <gdkmm/pixbuf.h>
|
||||
#include <glibmm/property.h>
|
||||
#include <gtkmm/cellrenderertext.h>
|
||||
|
||||
enum class MemberListRenderType {
|
||||
Role,
|
||||
Member,
|
||||
};
|
||||
|
||||
class CellRendererMemberList : public Gtk::CellRenderer {
|
||||
public:
|
||||
CellRendererMemberList();
|
||||
~CellRendererMemberList() = default;
|
||||
|
||||
Glib::PropertyProxy<MemberListRenderType> property_type();
|
||||
Glib::PropertyProxy<uint64_t> property_id();
|
||||
Glib::PropertyProxy<Glib::ustring> property_markup();
|
||||
Glib::PropertyProxy<Glib::RefPtr<Gdk::Pixbuf>> property_icon();
|
||||
|
||||
private:
|
||||
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;
|
||||
|
||||
// role
|
||||
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);
|
||||
|
||||
// member
|
||||
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);
|
||||
|
||||
Gtk::CellRendererText m_renderer_text;
|
||||
|
||||
Glib::Property<MemberListRenderType> m_property_type;
|
||||
Glib::Property<uint64_t> m_property_id;
|
||||
Glib::Property<Glib::ustring> m_property_markup;
|
||||
Glib::Property<Glib::RefPtr<Gdk::Pixbuf>> m_property_icon;
|
||||
};
|
@ -262,6 +262,12 @@ std::set<Snowflake> DiscordClient::GetUsersInGuild(Snowflake id) const {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<UserData> DiscordClient::GetUserDataInGuildBulk(Snowflake id) {
|
||||
const auto ids = GetUsersInGuild(id);
|
||||
std::vector<Snowflake> test;
|
||||
return m_store.GetUserDataBulk(ids.begin(), ids.end());
|
||||
}
|
||||
|
||||
std::set<Snowflake> DiscordClient::GetChannelsInGuild(Snowflake id) const {
|
||||
auto it = m_guild_to_channels.find(id);
|
||||
if (it != m_guild_to_channels.end())
|
||||
|
@ -76,6 +76,7 @@ public:
|
||||
Snowflake GetMemberHoistedRole(Snowflake guild_id, Snowflake user_id, bool with_color = false) const;
|
||||
std::optional<RoleData> GetMemberHighestRole(Snowflake guild_id, Snowflake user_id) const;
|
||||
std::set<Snowflake> GetUsersInGuild(Snowflake id) const;
|
||||
std::vector<UserData> GetUserDataInGuildBulk(Snowflake id);
|
||||
std::set<Snowflake> GetChannelsInGuild(Snowflake id) const;
|
||||
std::vector<Snowflake> GetUsersInThread(Snowflake id) const;
|
||||
std::vector<ChannelData> GetActiveThreads(Snowflake channel_id) const;
|
||||
|
@ -1033,6 +1033,22 @@ RoleData Store::GetRoleBound(std::unique_ptr<Statement> &s) {
|
||||
return r;
|
||||
}
|
||||
|
||||
UserData Store::GetUserBound(Statement *s) const {
|
||||
UserData r;
|
||||
|
||||
s->Get(0, r.ID);
|
||||
s->Get(1, r.Username);
|
||||
s->Get(2, r.Discriminator);
|
||||
s->Get(3, r.Avatar);
|
||||
s->Get(4, r.IsBot);
|
||||
s->Get(5, r.IsSystem);
|
||||
s->Get(6, r.IsMFAEnabled);
|
||||
s->Get(7, r.PremiumType);
|
||||
s->Get(8, r.PublicFlags);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
std::optional<UserData> Store::GetUser(Snowflake id) const {
|
||||
auto &s = m_stmt_get_user;
|
||||
s->Bind(1, id);
|
||||
@ -1043,17 +1059,7 @@ std::optional<UserData> Store::GetUser(Snowflake id) const {
|
||||
return {};
|
||||
}
|
||||
|
||||
UserData r;
|
||||
|
||||
r.ID = id;
|
||||
s->Get(1, r.Username);
|
||||
s->Get(2, r.Discriminator);
|
||||
s->Get(3, r.Avatar);
|
||||
s->Get(4, r.IsBot);
|
||||
s->Get(5, r.IsSystem);
|
||||
s->Get(6, r.IsMFAEnabled);
|
||||
s->Get(7, r.PremiumType);
|
||||
s->Get(8, r.PublicFlags);
|
||||
auto r = GetUserBound(s.get());
|
||||
|
||||
s->Reset();
|
||||
|
||||
|
@ -39,6 +39,36 @@ public:
|
||||
std::optional<BanData> GetBan(Snowflake guild_id, Snowflake user_id) const;
|
||||
std::vector<BanData> GetBans(Snowflake guild_id) const;
|
||||
|
||||
template<typename Iter>
|
||||
std::vector<UserData> GetUserDataBulk(Iter start, Iter end) {
|
||||
std::string query = "SELECT * FROM users WHERE id IN (";
|
||||
for (Iter it = start; it != end; it++) {
|
||||
query += "?,";
|
||||
}
|
||||
query.pop_back();
|
||||
query += ")";
|
||||
|
||||
Statement stmt(m_db, query.c_str());
|
||||
if (!stmt.OK()) {
|
||||
printf("failed to prepare GetUserDataBulk: %s\n", m_db.ErrStr());
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (Iter it = start; it != end; it++) {
|
||||
i++;
|
||||
if (stmt.Bind(i, *it) != SQLITE_OK) {
|
||||
printf("failed to bind GetUserDataBulk: %s\n", m_db.ErrStr());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<UserData> r;
|
||||
while (stmt.FetchOne()) {
|
||||
r.push_back(GetUserBound(&stmt));
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
std::vector<Message> GetLastMessages(Snowflake id, size_t num) const;
|
||||
std::vector<Message> GetMessagesBefore(Snowflake channel_id, Snowflake message_id, size_t limit) const;
|
||||
std::vector<Message> GetPinnedMessages(Snowflake channel_id) const;
|
||||
@ -240,6 +270,7 @@ private:
|
||||
|
||||
Message GetMessageBound(std::unique_ptr<Statement> &stmt) const;
|
||||
static RoleData GetRoleBound(std::unique_ptr<Statement> &stmt);
|
||||
UserData GetUserBound(Statement *stmt) const;
|
||||
|
||||
void SetMessageInteractionPair(Snowflake message_id, const MessageInteractionData &interaction);
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
#include "mainwindow.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
#include "components/memberlist.hpp" // TMP!!!!
|
||||
|
||||
MainWindow::MainWindow()
|
||||
: m_main_box(Gtk::ORIENTATION_VERTICAL)
|
||||
, m_content_box(Gtk::ORIENTATION_HORIZONTAL)
|
||||
@ -20,7 +22,6 @@ MainWindow::MainWindow()
|
||||
m_main_box.add(m_content_box);
|
||||
m_main_box.show();
|
||||
|
||||
auto *member_list = m_members.GetRoot();
|
||||
auto *chat = m_chat.GetRoot();
|
||||
|
||||
chat->set_vexpand(true);
|
||||
@ -37,8 +38,8 @@ MainWindow::MainWindow()
|
||||
m_channel_list.set_size_request(-1, -1);
|
||||
m_channel_list.show();
|
||||
|
||||
member_list->set_vexpand(true);
|
||||
member_list->show();
|
||||
m_members.set_vexpand(true);
|
||||
m_members.show();
|
||||
|
||||
m_friends.set_vexpand(true);
|
||||
m_friends.set_hexpand(true);
|
||||
@ -63,11 +64,11 @@ MainWindow::MainWindow()
|
||||
m_channel_list.UsePanedHack(m_chan_content_paned);
|
||||
|
||||
m_content_members_paned.pack1(m_content_stack);
|
||||
m_content_members_paned.pack2(*member_list);
|
||||
m_content_members_paned.pack2(m_members);
|
||||
m_content_members_paned.child_property_shrink(m_content_stack) = true;
|
||||
m_content_members_paned.child_property_resize(m_content_stack) = true;
|
||||
m_content_members_paned.child_property_shrink(*member_list) = true;
|
||||
m_content_members_paned.child_property_resize(*member_list) = true;
|
||||
m_content_members_paned.child_property_shrink(m_members) = true;
|
||||
m_content_members_paned.child_property_resize(m_members) = true;
|
||||
int w, h;
|
||||
get_default_size(w, h); // :s
|
||||
m_content_members_paned.set_position(w - m_chan_content_paned.get_position() - 150);
|
||||
@ -367,7 +368,7 @@ void MainWindow::SetupMenu() {
|
||||
});
|
||||
|
||||
m_menu_view_members.signal_activate().connect([this]() {
|
||||
m_members.GetRoot()->set_visible(m_menu_view_members.get_active());
|
||||
m_members.set_visible(m_menu_view_members.get_active());
|
||||
});
|
||||
|
||||
#ifdef WITH_LIBHANDY
|
||||
|
@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
#include "components/channels.hpp"
|
||||
#include "components/chatwindow.hpp"
|
||||
#include "components/memberlist.hpp"
|
||||
#include "components/friendslist.hpp"
|
||||
#include "components/memberlist.hpp"
|
||||
#include <gtkmm.h>
|
||||
|
||||
class MainWindow : public Gtk::Window {
|
||||
|
Loading…
Reference in New Issue
Block a user