mirror of
https://github.com/celisej567/abaddon.git
synced 2026-09-19 20:11:59 +03:00
Restructure source and resource files (#46)
importantly, res is now res/res and css is now res/css
This commit is contained in:
committed by
GitHub
parent
d88079000a
commit
a51a54bc59
636
src/windows/guildsettings/auditlogpane.cpp
Normal file
636
src/windows/guildsettings/auditlogpane.cpp
Normal file
@@ -0,0 +1,636 @@
|
||||
#include "auditlogpane.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
GuildSettingsAuditLogPane::GuildSettingsAuditLogPane(Snowflake id)
|
||||
: GuildID(id) {
|
||||
signal_map().connect(sigc::mem_fun(*this, &GuildSettingsAuditLogPane::OnMap));
|
||||
set_name("guild-audit-log-pane");
|
||||
set_hexpand(true);
|
||||
set_vexpand(true);
|
||||
|
||||
m_list.set_selection_mode(Gtk::SELECTION_NONE);
|
||||
m_list.show();
|
||||
add(m_list);
|
||||
}
|
||||
|
||||
void GuildSettingsAuditLogPane::OnMap() {
|
||||
if (m_requested) return;
|
||||
m_requested = true;
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto self_id = discord.GetUserData().ID;
|
||||
|
||||
if (discord.HasGuildPermission(self_id, GuildID, Permission::VIEW_AUDIT_LOG))
|
||||
discord.FetchAuditLog(GuildID, sigc::mem_fun(*this, &GuildSettingsAuditLogPane::OnAuditLogFetch));
|
||||
}
|
||||
|
||||
void GuildSettingsAuditLogPane::OnAuditLogFetch(const AuditLogData &data) {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
auto guild = *discord.GetGuild(GuildID);
|
||||
for (const auto &entry : data.Entries) {
|
||||
if (entry.TargetID == "") continue;
|
||||
|
||||
auto expander = Gtk::manage(new Gtk::Expander);
|
||||
auto label = Gtk::manage(new Gtk::Label);
|
||||
label->set_ellipsize(Pango::ELLIPSIZE_END);
|
||||
|
||||
Glib::ustring user_markup = "<b>Unknown User</b>";
|
||||
if (entry.UserID.has_value()) {
|
||||
if (auto user = discord.GetUser(*entry.UserID); user.has_value())
|
||||
user_markup = discord.GetUser(*entry.UserID)->GetEscapedBoldString<false>();
|
||||
}
|
||||
|
||||
// spaghetti moment
|
||||
Glib::ustring markup;
|
||||
std::vector<Glib::ustring> extra_markup;
|
||||
switch (entry.Type) {
|
||||
case AuditLogActionType::GUILD_UPDATE: {
|
||||
markup =
|
||||
user_markup +
|
||||
" made changes to <b>" +
|
||||
Glib::Markup::escape_text(guild.Name) +
|
||||
"</b>";
|
||||
|
||||
if (entry.Changes.has_value())
|
||||
for (const auto &change : *entry.Changes) {
|
||||
if (change.Key == "icon_hash") {
|
||||
extra_markup.push_back("Set the server icon");
|
||||
} else if (change.Key == "name") {
|
||||
auto new_name = change.NewValue;
|
||||
if (new_name.has_value())
|
||||
extra_markup.push_back("Set the server name to <b>" +
|
||||
Glib::Markup::escape_text(new_name->get<std::string>()) +
|
||||
"</b>");
|
||||
else
|
||||
extra_markup.push_back("Set the server name");
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case AuditLogActionType::CHANNEL_CREATE: {
|
||||
const auto type = *entry.GetNewFromKey<ChannelType>("type");
|
||||
markup = user_markup +
|
||||
" created a " + (type == ChannelType::GUILD_VOICE ? "voice" : "text") +
|
||||
" channel <b>#" +
|
||||
Glib::Markup::escape_text(*entry.GetNewFromKey<std::string>("name")) +
|
||||
"</b>";
|
||||
if (entry.Changes.has_value())
|
||||
for (const auto &change : *entry.Changes) {
|
||||
if (change.Key == "name" && change.NewValue.has_value())
|
||||
extra_markup.push_back("Set the name to <b>" +
|
||||
Glib::Markup::escape_text(change.NewValue->get<std::string>()) +
|
||||
"</b>");
|
||||
else if (change.Key == "nsfw" && change.NewValue.has_value())
|
||||
extra_markup.push_back((*change.NewValue ? "Marked" : "Unmarked") +
|
||||
" the channel as NSFW"s);
|
||||
}
|
||||
|
||||
} break;
|
||||
case AuditLogActionType::CHANNEL_UPDATE: {
|
||||
const auto target_channel = discord.GetChannel(entry.TargetID);
|
||||
if (target_channel.has_value()) {
|
||||
markup = user_markup +
|
||||
" made changes to <b>#" +
|
||||
Glib::Markup::escape_text(*target_channel->Name) +
|
||||
"</b>";
|
||||
} else {
|
||||
markup = user_markup +
|
||||
" made changes to <b><#" +
|
||||
entry.TargetID +
|
||||
"></b>";
|
||||
}
|
||||
if (entry.Changes.has_value())
|
||||
for (const auto &change : *entry.Changes) {
|
||||
if (change.Key == "name" && change.NewValue.has_value()) {
|
||||
if (change.OldValue.has_value())
|
||||
extra_markup.push_back("Changed the name from <b>" +
|
||||
Glib::Markup::escape_text(change.OldValue->get<std::string>()) +
|
||||
"</b> to <b>" +
|
||||
Glib::Markup::escape_text(change.NewValue->get<std::string>()) +
|
||||
"</b>");
|
||||
else
|
||||
extra_markup.push_back("Changed the name to <b>" +
|
||||
Glib::Markup::escape_text(change.NewValue->get<std::string>()) +
|
||||
"</b>");
|
||||
} else if (change.Key == "topic") {
|
||||
if (change.NewValue.has_value())
|
||||
extra_markup.push_back("Changed the topic to <b>" +
|
||||
Glib::Markup::escape_text(change.NewValue->get<std::string>()) +
|
||||
"</b>");
|
||||
else
|
||||
extra_markup.push_back("Cleared the topic");
|
||||
} else if (change.Key == "nsfw" && change.NewValue.has_value()) {
|
||||
extra_markup.push_back((*change.NewValue ? "Marked" : "Unmarked") + " the channel as NSFW"s);
|
||||
} else if (change.Key == "rate_limit_per_user" && change.NewValue.has_value()) {
|
||||
const int secs = change.NewValue->get<int>();
|
||||
if (secs == 0)
|
||||
extra_markup.push_back("Disabled slowmode");
|
||||
else
|
||||
extra_markup.push_back("Set slowmode to <b>" +
|
||||
std::to_string(secs) + " seconds</b>");
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case AuditLogActionType::CHANNEL_DELETE: {
|
||||
markup = user_markup +
|
||||
" removed <b>#" +
|
||||
Glib::Markup::escape_text(*entry.GetOldFromKey<std::string>("name")) +
|
||||
"</b>";
|
||||
} break;
|
||||
case AuditLogActionType::CHANNEL_OVERWRITE_CREATE: {
|
||||
const auto channel = discord.GetChannel(entry.TargetID);
|
||||
if (channel.has_value()) {
|
||||
markup = user_markup +
|
||||
" created channel overrides for <b>#" +
|
||||
Glib::Markup::escape_text(*channel->Name) + "</b>";
|
||||
} else {
|
||||
markup = user_markup +
|
||||
" created channel overrides for <b><#" +
|
||||
entry.TargetID + "></b>";
|
||||
}
|
||||
} break;
|
||||
case AuditLogActionType::CHANNEL_OVERWRITE_UPDATE: {
|
||||
const auto channel = discord.GetChannel(entry.TargetID);
|
||||
if (channel.has_value()) {
|
||||
markup = user_markup +
|
||||
" updated channel overrides for <b>#" +
|
||||
Glib::Markup::escape_text(*channel->Name) + "</b>";
|
||||
} else {
|
||||
markup = user_markup +
|
||||
" updated channel overrides for <b><#" +
|
||||
entry.TargetID + "></b>";
|
||||
}
|
||||
} break;
|
||||
case AuditLogActionType::CHANNEL_OVERWRITE_DELETE: {
|
||||
const auto channel = discord.GetChannel(entry.TargetID);
|
||||
if (channel.has_value()) {
|
||||
markup = user_markup +
|
||||
" removed channel overrides for <b>#" +
|
||||
Glib::Markup::escape_text(*channel->Name) + "</b>";
|
||||
} else {
|
||||
markup = user_markup +
|
||||
" removed channel overrides for <b><#" +
|
||||
entry.TargetID + "></b>";
|
||||
}
|
||||
} break;
|
||||
case AuditLogActionType::MEMBER_KICK: {
|
||||
const auto target_user = discord.GetUser(entry.TargetID);
|
||||
markup = user_markup +
|
||||
" kicked <b>" +
|
||||
target_user->GetEscapedString() +
|
||||
"</b>";
|
||||
} break;
|
||||
case AuditLogActionType::MEMBER_PRUNE: {
|
||||
markup = user_markup +
|
||||
" pruned <b>" +
|
||||
*entry.Options->MembersRemoved +
|
||||
"</b> members";
|
||||
extra_markup.push_back("For <b>" +
|
||||
*entry.Options->DeleteMemberDays +
|
||||
" days</b> of inactivity");
|
||||
} break;
|
||||
case AuditLogActionType::MEMBER_BAN_ADD: {
|
||||
const auto target_user = discord.GetUser(entry.TargetID);
|
||||
markup = user_markup +
|
||||
" banned <b>" +
|
||||
target_user->GetEscapedString() +
|
||||
"</b>";
|
||||
} break;
|
||||
case AuditLogActionType::MEMBER_BAN_REMOVE: {
|
||||
const auto target_user = discord.GetUser(entry.TargetID);
|
||||
markup = user_markup +
|
||||
" removed the ban for <b>" +
|
||||
target_user->GetEscapedString() +
|
||||
"</b>";
|
||||
} break;
|
||||
case AuditLogActionType::MEMBER_UPDATE: {
|
||||
const auto target_user = discord.GetUser(entry.TargetID);
|
||||
markup = user_markup +
|
||||
" updated <b>" +
|
||||
target_user->GetEscapedString() +
|
||||
"</b>";
|
||||
if (entry.Changes.has_value())
|
||||
for (const auto &change : *entry.Changes) {
|
||||
if (change.Key == "deaf" && change.NewValue.has_value())
|
||||
extra_markup.push_back(
|
||||
(change.NewValue->get<bool>() ? "<b>Deafened</b>"s : "<b>Undeafened</b>"s) +
|
||||
" them");
|
||||
else if (change.Key == "mute" && change.NewValue.has_value())
|
||||
extra_markup.push_back(
|
||||
(change.NewValue->get<bool>() ? "<b>Muted</b>"s : "<b>Unmuted</b>"s) +
|
||||
" them");
|
||||
else if (change.Key == "nick" && change.NewValue.has_value())
|
||||
extra_markup.push_back("Set their nickname to <b>" +
|
||||
Glib::Markup::escape_text(change.NewValue->get<std::string>()) +
|
||||
"</b>");
|
||||
}
|
||||
} break;
|
||||
case AuditLogActionType::MEMBER_ROLE_UPDATE: {
|
||||
const auto target_user = discord.GetUser(entry.TargetID);
|
||||
markup = user_markup +
|
||||
" updated roles for <b>" +
|
||||
target_user->GetEscapedString() + "</b>";
|
||||
if (entry.Changes.has_value())
|
||||
for (const auto &change : *entry.Changes) {
|
||||
if (change.Key == "$remove" && change.NewValue.has_value()) {
|
||||
extra_markup.push_back("<b>Removed</b> a role <b>" +
|
||||
Glib::Markup::escape_text(change.NewValue.value()[0].at("name").get<std::string>()) +
|
||||
"</b>");
|
||||
} else if (change.Key == "$add" && change.NewValue.has_value()) {
|
||||
extra_markup.push_back("<b>Added</b> a role <b>" +
|
||||
Glib::Markup::escape_text(change.NewValue.value()[0].at("name").get<std::string>()) +
|
||||
"</b>");
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case AuditLogActionType::MEMBER_MOVE: {
|
||||
const auto channel = discord.GetChannel(*entry.Options->ChannelID);
|
||||
markup = user_markup +
|
||||
" moved <b>" +
|
||||
*entry.Options->Count +
|
||||
" user" +
|
||||
(*entry.Options->Count == "1" ? ""s : "s"s) +
|
||||
"</b> to <b>" +
|
||||
Glib::Markup::escape_text(*channel->Name) +
|
||||
"</b>";
|
||||
} break;
|
||||
case AuditLogActionType::MEMBER_DISCONNECT: {
|
||||
markup = user_markup +
|
||||
" disconnected <b>" +
|
||||
*entry.Options->Count +
|
||||
"</b> users from voice";
|
||||
} break;
|
||||
case AuditLogActionType::BOT_ADD: {
|
||||
const auto target_user = discord.GetUser(entry.TargetID);
|
||||
markup = user_markup +
|
||||
" added <b>" +
|
||||
target_user->GetEscapedString() +
|
||||
"</b> to the server";
|
||||
} break;
|
||||
case AuditLogActionType::ROLE_CREATE: {
|
||||
markup = user_markup +
|
||||
" created the role <b>" +
|
||||
*entry.GetNewFromKey<std::string>("name") +
|
||||
"</b>";
|
||||
} break;
|
||||
case AuditLogActionType::ROLE_UPDATE: {
|
||||
const auto role = discord.GetRole(entry.TargetID);
|
||||
markup = user_markup +
|
||||
" updated the role <b>" +
|
||||
(role.has_value() ? Glib::Markup::escape_text(role->Name) : Glib::ustring(entry.TargetID)) +
|
||||
"</b>";
|
||||
if (entry.Changes.has_value())
|
||||
for (const auto &change : *entry.Changes) {
|
||||
if (change.Key == "name" && change.NewValue.has_value()) {
|
||||
extra_markup.push_back("Changed the name to <b>" +
|
||||
Glib::Markup::escape_text(change.NewValue->get<std::string>()) +
|
||||
"</b>");
|
||||
} else if (change.Key == "color" && change.NewValue.has_value()) {
|
||||
const auto col = change.NewValue->get<int>();
|
||||
if (col == 0)
|
||||
extra_markup.push_back("Removed the color");
|
||||
else
|
||||
extra_markup.push_back("Set the color to <b>" +
|
||||
IntToCSSColor(col) +
|
||||
"</b>");
|
||||
} else if (change.Key == "permissions") {
|
||||
extra_markup.push_back("Updated the permissions");
|
||||
} else if (change.Key == "mentionable" && change.NewValue.has_value()) {
|
||||
extra_markup.push_back(change.NewValue->get<bool>() ? "Mentionable" : "Not mentionable");
|
||||
} else if (change.Key == "hoist" && change.NewValue.has_value()) {
|
||||
extra_markup.push_back(change.NewValue->get<bool>() ? "Not hoisted" : "Hoisted");
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case AuditLogActionType::ROLE_DELETE: {
|
||||
markup = user_markup +
|
||||
" deleted the role <b>" +
|
||||
*entry.GetOldFromKey<std::string>("name") +
|
||||
"</b>";
|
||||
} break;
|
||||
case AuditLogActionType::INVITE_CREATE: {
|
||||
const auto code = *entry.GetNewFromKey<std::string>("code");
|
||||
markup = user_markup +
|
||||
" created an invite <b>" + code + "</b>";
|
||||
if (entry.Changes.has_value())
|
||||
for (const auto &change : *entry.Changes) {
|
||||
if (change.Key == "channel_id" && change.NewValue.has_value()) {
|
||||
const auto channel = discord.GetChannel(change.NewValue->get<Snowflake>());
|
||||
if (!channel.has_value()) continue;
|
||||
extra_markup.push_back("For channel <b>#" +
|
||||
Glib::Markup::escape_text(*channel->Name) +
|
||||
"</b>");
|
||||
} else if (change.Key == "max_uses" && change.NewValue.has_value()) {
|
||||
const auto uses = change.NewValue->get<int>();
|
||||
if (uses == 0)
|
||||
extra_markup.push_back("Which has <b>unlimited</b> uses");
|
||||
else
|
||||
extra_markup.push_back("Which has <b>" + std::to_string(uses) + "</b> uses");
|
||||
} else if (change.Key == "temporary" && change.NewValue.has_value()) {
|
||||
extra_markup.push_back("With temporary <b>"s +
|
||||
(change.NewValue->get<bool>() ? "on" : "off") +
|
||||
"</b>");
|
||||
} // no max_age cuz fuck time
|
||||
}
|
||||
} break;
|
||||
case AuditLogActionType::INVITE_DELETE: {
|
||||
markup = user_markup +
|
||||
" deleted an invite <b>" +
|
||||
*entry.GetOldFromKey<std::string>("code") +
|
||||
"</b>";
|
||||
} break;
|
||||
case AuditLogActionType::WEBHOOK_CREATE: {
|
||||
markup = user_markup +
|
||||
" created the webhook <b>" +
|
||||
Glib::Markup::escape_text(*entry.GetNewFromKey<std::string>("name")) +
|
||||
"</b>";
|
||||
for (const auto &change : *entry.Changes) {
|
||||
if (change.Key == "channel_id" && change.NewValue.has_value()) {
|
||||
const auto channel = discord.GetChannel(change.NewValue->get<Snowflake>());
|
||||
if (channel.has_value()) {
|
||||
extra_markup.push_back("With channel <b>#" +
|
||||
Glib::Markup::escape_text(*channel->Name) +
|
||||
"</b>");
|
||||
}
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case AuditLogActionType::WEBHOOK_UPDATE: {
|
||||
const WebhookData *webhookptr = nullptr;
|
||||
for (const auto &webhook : data.Webhooks) {
|
||||
if (webhook.ID == entry.TargetID)
|
||||
webhookptr = &webhook;
|
||||
}
|
||||
if (webhookptr != nullptr) {
|
||||
markup = user_markup +
|
||||
" updated the webhook <b>" +
|
||||
Glib::Markup::escape_text(webhookptr->Name) +
|
||||
"</b>";
|
||||
} else {
|
||||
markup = user_markup +
|
||||
" updated a webhook";
|
||||
}
|
||||
if (entry.Changes.has_value())
|
||||
for (const auto &change : *entry.Changes) {
|
||||
if (change.Key == "name" && change.NewValue.has_value()) {
|
||||
extra_markup.push_back("Changed the name to <b>" +
|
||||
Glib::Markup::escape_text(change.NewValue->get<std::string>()) +
|
||||
"</b>");
|
||||
} else if (change.Key == "avatar_hash") {
|
||||
extra_markup.push_back("Changed the avatar");
|
||||
} else if (change.Key == "channel_id" && change.NewValue.has_value()) {
|
||||
const auto channel = discord.GetChannel(change.NewValue->get<Snowflake>());
|
||||
if (channel.has_value()) {
|
||||
extra_markup.push_back("Changed the channel to <b>#" +
|
||||
Glib::Markup::escape_text(*channel->Name) +
|
||||
"</b>");
|
||||
} else {
|
||||
extra_markup.push_back("Changed the channel");
|
||||
}
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case AuditLogActionType::WEBHOOK_DELETE: {
|
||||
markup = user_markup +
|
||||
" deleted the webhook <b>" +
|
||||
Glib::Markup::escape_text(*entry.GetOldFromKey<std::string>("name")) +
|
||||
"</b>";
|
||||
} break;
|
||||
case AuditLogActionType::EMOJI_CREATE: {
|
||||
markup = user_markup +
|
||||
" created the emoji <b>" +
|
||||
Glib::Markup::escape_text(*entry.GetNewFromKey<std::string>("name")) +
|
||||
"</b>";
|
||||
} break;
|
||||
case AuditLogActionType::EMOJI_UPDATE: {
|
||||
markup = user_markup +
|
||||
" updated the emoji <b>" +
|
||||
Glib::Markup::escape_text(*entry.GetOldFromKey<std::string>("name")) +
|
||||
"</b>";
|
||||
extra_markup.push_back("Changed the name from <b>" +
|
||||
Glib::Markup::escape_text(*entry.GetOldFromKey<std::string>("name")) +
|
||||
"</b> to <b>" +
|
||||
Glib::Markup::escape_text(*entry.GetNewFromKey<std::string>("name")) +
|
||||
"</b>");
|
||||
} break;
|
||||
case AuditLogActionType::EMOJI_DELETE: {
|
||||
markup = user_markup +
|
||||
" deleted the emoji <b>" +
|
||||
Glib::Markup::escape_text(*entry.GetOldFromKey<std::string>("name")) +
|
||||
"</b>";
|
||||
} break;
|
||||
case AuditLogActionType::MESSAGE_DELETE: {
|
||||
const auto channel = discord.GetChannel(*entry.Options->ChannelID);
|
||||
const auto count = *entry.Options->Count;
|
||||
if (channel.has_value()) {
|
||||
markup = user_markup +
|
||||
" deleted <b>" + count + "</b> messages in <b>#" +
|
||||
Glib::Markup::escape_text(*channel->Name) +
|
||||
"</b>";
|
||||
} else {
|
||||
markup = user_markup +
|
||||
" deleted <b>" + count + "</b> messages";
|
||||
}
|
||||
} break;
|
||||
case AuditLogActionType::MESSAGE_BULK_DELETE: {
|
||||
const auto channel = discord.GetChannel(entry.TargetID);
|
||||
if (channel.has_value()) {
|
||||
markup = user_markup +
|
||||
" deleted <b>" +
|
||||
*entry.Options->Count +
|
||||
"</b> messages in <b>#" +
|
||||
Glib::Markup::escape_text(*channel->Name) +
|
||||
"</b>";
|
||||
} else {
|
||||
markup = user_markup +
|
||||
" deleted <b>" +
|
||||
*entry.Options->Count +
|
||||
"</b> messages";
|
||||
}
|
||||
} break;
|
||||
case AuditLogActionType::MESSAGE_PIN: {
|
||||
const auto target_user = discord.GetUser(entry.TargetID);
|
||||
markup = user_markup +
|
||||
" pinned a message by <b>" +
|
||||
target_user->GetEscapedString() +
|
||||
"</b>";
|
||||
} break;
|
||||
case AuditLogActionType::MESSAGE_UNPIN: {
|
||||
const auto target_user = discord.GetUser(entry.TargetID);
|
||||
markup = user_markup +
|
||||
" unpinned a message by <b>" +
|
||||
target_user->GetEscapedString() +
|
||||
"</b>";
|
||||
} break;
|
||||
case AuditLogActionType::STAGE_INSTANCE_CREATE: {
|
||||
const auto channel = discord.GetChannel(*entry.Options->ChannelID);
|
||||
if (channel.has_value()) {
|
||||
markup = user_markup +
|
||||
" started the stage for <b>" +
|
||||
Glib::Markup::escape_text(*channel->Name) +
|
||||
"</b>";
|
||||
} else {
|
||||
markup = user_markup +
|
||||
" started the stage for <b>" +
|
||||
std::to_string(*entry.Options->ChannelID) +
|
||||
"</b>";
|
||||
}
|
||||
|
||||
if (entry.Changes.has_value()) {
|
||||
for (const auto &change : *entry.Changes) {
|
||||
if (change.Key == "topic" && change.NewValue.has_value()) {
|
||||
extra_markup.push_back(
|
||||
"Set the topic to <b>" +
|
||||
Glib::Markup::escape_text(change.NewValue->get<std::string>()) +
|
||||
"</b>");
|
||||
} else if (change.Key == "privacy_level" && change.NewValue.has_value()) {
|
||||
Glib::ustring str = Glib::Markup::escape_text(GetStagePrivacyDisplayString(change.NewValue->get<StagePrivacy>()));
|
||||
extra_markup.push_back(
|
||||
"Set the privacy level to <b>" +
|
||||
str +
|
||||
"</b>");
|
||||
}
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case AuditLogActionType::STAGE_INSTANCE_UPDATE: {
|
||||
const auto channel = discord.GetChannel(*entry.Options->ChannelID);
|
||||
if (channel.has_value()) {
|
||||
markup = user_markup +
|
||||
" updated the stage for <b>" +
|
||||
Glib::Markup::escape_text(*channel->Name) +
|
||||
"</b>";
|
||||
} else {
|
||||
markup = user_markup +
|
||||
" updated the stage for <b>" +
|
||||
std::to_string(*entry.Options->ChannelID) +
|
||||
"</b>";
|
||||
}
|
||||
|
||||
if (entry.Changes.has_value()) {
|
||||
for (const auto &change : *entry.Changes) {
|
||||
if (change.Key == "topic" && change.NewValue.has_value()) {
|
||||
extra_markup.push_back(
|
||||
"Set the topic to <b>" +
|
||||
Glib::Markup::escape_text(change.NewValue->get<std::string>()) +
|
||||
"</b>");
|
||||
} else if (change.Key == "privacy_level" && change.NewValue.has_value()) {
|
||||
Glib::ustring str = Glib::Markup::escape_text(GetStagePrivacyDisplayString(change.NewValue->get<StagePrivacy>()));
|
||||
extra_markup.push_back(
|
||||
"Set the privacy level to <b>" +
|
||||
str +
|
||||
"</b>");
|
||||
}
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case AuditLogActionType::STAGE_INSTANCE_DELETE: {
|
||||
const auto channel = discord.GetChannel(*entry.Options->ChannelID);
|
||||
if (channel.has_value()) {
|
||||
markup = user_markup +
|
||||
" ended the stage for <b>" +
|
||||
Glib::Markup::escape_text(*channel->Name) +
|
||||
"</b>";
|
||||
} else {
|
||||
markup = user_markup +
|
||||
" ended the stage for <b>" +
|
||||
std::to_string(*entry.Options->ChannelID) +
|
||||
"</b>";
|
||||
}
|
||||
} break;
|
||||
case AuditLogActionType::THREAD_CREATE: {
|
||||
const auto channel = discord.GetChannel(entry.TargetID);
|
||||
markup = user_markup +
|
||||
" created a thread <b>" +
|
||||
(channel.has_value()
|
||||
? Glib::Markup::escape_text(*channel->Name)
|
||||
: Glib::ustring(*entry.GetNewFromKey<std::string>("name"))) +
|
||||
"</b>";
|
||||
if (entry.Changes.has_value()) {
|
||||
for (const auto &change : *entry.Changes) {
|
||||
if (change.Key == "name")
|
||||
extra_markup.push_back("Set the name to <b>" + Glib::Markup::escape_text(change.NewValue->get<std::string>()) + "</b>");
|
||||
else if (change.Key == "archived")
|
||||
extra_markup.push_back(change.NewValue->get<bool>() ? "Archived the thread" : "Unarchived the thread");
|
||||
else if (change.Key == "auto_archive_duration")
|
||||
extra_markup.push_back("Set auto archive duration to <b>"s + std::to_string(change.NewValue->get<int>()) + " minutes</b>"s);
|
||||
else if (change.Key == "rate_limit_per_user" && change.NewValue.has_value()) {
|
||||
const int secs = change.NewValue->get<int>();
|
||||
if (secs == 0)
|
||||
extra_markup.push_back("Disabled slowmode");
|
||||
else
|
||||
extra_markup.push_back("Set slowmode to <b>" +
|
||||
std::to_string(secs) + " seconds</b>");
|
||||
} else if (change.Key == "locked")
|
||||
extra_markup.push_back(change.NewValue->get<bool>() ? "Locked the thread, restricting it to only be unarchived by moderators" : "Unlocked the thread, allowing it to be unarchived by non-moderators");
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case AuditLogActionType::THREAD_UPDATE: {
|
||||
const auto channel = discord.GetChannel(entry.TargetID);
|
||||
markup = user_markup +
|
||||
" made changes to the thread <b>" +
|
||||
(channel.has_value()
|
||||
? Glib::Markup::escape_text(*channel->Name)
|
||||
: Glib::ustring(entry.TargetID)) +
|
||||
"</b>";
|
||||
for (const auto &change : *entry.Changes) {
|
||||
if (change.Key == "name")
|
||||
extra_markup.push_back(
|
||||
"Changed the name from <b>" +
|
||||
Glib::Markup::escape_text(change.OldValue->get<std::string>()) +
|
||||
"</b> to <b>" +
|
||||
Glib::Markup::escape_text(change.NewValue->get<std::string>()) +
|
||||
"</b>");
|
||||
else if (change.Key == "auto_archive_duration")
|
||||
extra_markup.push_back("Set auto archive duration to <b>"s + std::to_string(change.NewValue->get<int>()) + " minutes</b>"s);
|
||||
else if (change.Key == "rate_limit_per_user" && change.NewValue.has_value()) {
|
||||
const int secs = change.NewValue->get<int>();
|
||||
if (secs == 0)
|
||||
extra_markup.push_back("Disabled slowmode");
|
||||
else
|
||||
extra_markup.push_back("Set slowmode to <b>" +
|
||||
std::to_string(secs) +
|
||||
" seconds</b>");
|
||||
} else if (change.Key == "locked")
|
||||
extra_markup.push_back(change.NewValue->get<bool>() ? "Locked the thread, restricting it to only be unarchived by moderators" : "Unlocked the thread, allowing it to be unarchived by non-moderators");
|
||||
else if (change.Key == "archived")
|
||||
extra_markup.push_back(change.NewValue->get<bool>() ? "Archived the thread" : "Unarchived the thread");
|
||||
}
|
||||
} break;
|
||||
case AuditLogActionType::THREAD_DELETE: {
|
||||
markup = user_markup +
|
||||
" deleted the thread <b>" + Glib::Markup::escape_text(*entry.GetOldFromKey<std::string>("name")) + "</b>";
|
||||
} break;
|
||||
default:
|
||||
markup = "<i>Unknown action</i>";
|
||||
break;
|
||||
}
|
||||
|
||||
label->set_markup(markup);
|
||||
expander->set_label_widget(*label);
|
||||
|
||||
if (entry.Reason.has_value()) {
|
||||
extra_markup.push_back("With reason <b>" +
|
||||
Glib::Markup::escape_text(*entry.Reason) +
|
||||
"</b>");
|
||||
}
|
||||
|
||||
expander->set_expanded(true);
|
||||
|
||||
auto contents = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
|
||||
for (const auto &extra : extra_markup) {
|
||||
auto extra_label = Gtk::manage(new Gtk::Label);
|
||||
extra_label->set_markup(extra);
|
||||
extra_label->set_halign(Gtk::ALIGN_START);
|
||||
extra_label->set_margin_start(25);
|
||||
extra_label->set_ellipsize(Pango::ELLIPSIZE_END);
|
||||
contents->add(*extra_label);
|
||||
}
|
||||
expander->add(*contents);
|
||||
expander->set_margin_bottom(5);
|
||||
expander->show_all();
|
||||
m_list.add(*expander);
|
||||
}
|
||||
}
|
||||
19
src/windows/guildsettings/auditlogpane.hpp
Normal file
19
src/windows/guildsettings/auditlogpane.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/objects.hpp"
|
||||
|
||||
class GuildSettingsAuditLogPane : public Gtk::ScrolledWindow {
|
||||
public:
|
||||
GuildSettingsAuditLogPane(Snowflake id);
|
||||
|
||||
private:
|
||||
void OnMap();
|
||||
|
||||
bool m_requested = false;
|
||||
|
||||
Gtk::ListBox m_list;
|
||||
|
||||
void OnAuditLogFetch(const AuditLogData &data);
|
||||
|
||||
Snowflake GuildID;
|
||||
};
|
||||
161
src/windows/guildsettings/banspane.cpp
Normal file
161
src/windows/guildsettings/banspane.cpp
Normal file
@@ -0,0 +1,161 @@
|
||||
#include "banspane.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
// gtk_list_store_set_value: assertion 'column >= 0 && column < priv->n_columns' failed
|
||||
// dont care to figure out why this happens cuz it doesnt seem to break anything
|
||||
|
||||
GuildSettingsBansPane::GuildSettingsBansPane(Snowflake id)
|
||||
: Gtk::Box(Gtk::ORIENTATION_VERTICAL)
|
||||
, GuildID(id)
|
||||
, m_model(Gtk::ListStore::create(m_columns))
|
||||
, m_menu_unban("Unban")
|
||||
, m_menu_copy_id("Copy ID") {
|
||||
signal_map().connect(sigc::mem_fun(*this, &GuildSettingsBansPane::OnMap));
|
||||
set_name("guild-bans-pane");
|
||||
set_hexpand(true);
|
||||
set_vexpand(true);
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
|
||||
discord.signal_guild_ban_add().connect(sigc::mem_fun(*this, &GuildSettingsBansPane::OnBanAdd));
|
||||
discord.signal_guild_ban_remove().connect(sigc::mem_fun(*this, &GuildSettingsBansPane::OnBanRemove));
|
||||
|
||||
const auto self_id = discord.GetUserData().ID;
|
||||
const auto can_ban = discord.HasGuildPermission(self_id, GuildID, Permission::BAN_MEMBERS);
|
||||
|
||||
if (!can_ban) {
|
||||
for (const auto &ban : discord.GetBansInGuild(id))
|
||||
OnGuildBanFetch(ban);
|
||||
|
||||
m_no_perms_note = Gtk::manage(new Gtk::Label("You do not have permission to see bans. However, bans made while you are connected will appear here"));
|
||||
m_no_perms_note->set_single_line_mode(true);
|
||||
m_no_perms_note->set_ellipsize(Pango::ELLIPSIZE_END);
|
||||
m_no_perms_note->set_halign(Gtk::ALIGN_START);
|
||||
add(*m_no_perms_note);
|
||||
}
|
||||
|
||||
m_menu_unban.signal_activate().connect(sigc::mem_fun(*this, &GuildSettingsBansPane::OnMenuUnban));
|
||||
m_menu_copy_id.signal_activate().connect(sigc::mem_fun(*this, &GuildSettingsBansPane::OnMenuCopyID));
|
||||
m_menu_unban.show();
|
||||
m_menu_copy_id.show();
|
||||
m_menu.append(m_menu_unban);
|
||||
m_menu.append(m_menu_copy_id);
|
||||
|
||||
m_view.signal_button_press_event().connect(sigc::mem_fun(*this, &GuildSettingsBansPane::OnTreeButtonPress), false);
|
||||
m_view.show();
|
||||
|
||||
m_scroll.set_propagate_natural_height(true);
|
||||
m_scroll.add(m_view);
|
||||
add(m_scroll);
|
||||
show_all_children();
|
||||
|
||||
m_view.set_enable_search(false);
|
||||
m_view.set_model(m_model);
|
||||
m_view.append_column("User", m_columns.m_col_user);
|
||||
m_view.append_column("Reason", m_columns.m_col_reason);
|
||||
}
|
||||
|
||||
void GuildSettingsBansPane::OnMap() {
|
||||
if (m_requested) return;
|
||||
m_requested = true;
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
|
||||
const auto self_id = discord.GetUserData().ID;
|
||||
const auto can_ban = discord.HasGuildPermission(self_id, GuildID, Permission::BAN_MEMBERS);
|
||||
|
||||
if (can_ban)
|
||||
discord.FetchGuildBans(GuildID, sigc::mem_fun(*this, &GuildSettingsBansPane::OnGuildBansFetch));
|
||||
}
|
||||
|
||||
void GuildSettingsBansPane::OnGuildBanFetch(const BanData &ban) {
|
||||
const auto user = Abaddon::Get().GetDiscordClient().GetUser(ban.User.ID);
|
||||
auto row = *m_model->append();
|
||||
row[m_columns.m_col_id] = ban.User.ID;
|
||||
if (user.has_value())
|
||||
row[m_columns.m_col_user] = user->Username + "#" + user->Discriminator;
|
||||
else
|
||||
row[m_columns.m_col_user] = "<@" + std::to_string(ban.User.ID) + ">";
|
||||
|
||||
row[m_columns.m_col_reason] = ban.Reason;
|
||||
}
|
||||
|
||||
void GuildSettingsBansPane::OnGuildBansFetch(const std::vector<BanData> &bans) {
|
||||
for (const auto &ban : bans) {
|
||||
const auto user = Abaddon::Get().GetDiscordClient().GetUser(ban.User.ID);
|
||||
auto row = *m_model->append();
|
||||
row[m_columns.m_col_id] = user->ID;
|
||||
row[m_columns.m_col_user] = user->Username + "#" + user->Discriminator;
|
||||
row[m_columns.m_col_reason] = ban.Reason;
|
||||
}
|
||||
}
|
||||
|
||||
void GuildSettingsBansPane::OnMenuUnban() {
|
||||
auto selected_row = *m_view.get_selection()->get_selected();
|
||||
if (selected_row) {
|
||||
Snowflake id = selected_row[m_columns.m_col_id];
|
||||
auto cb = [this](DiscordError code) {
|
||||
if (code != DiscordError::NONE) {
|
||||
Gtk::MessageDialog dlg("Failed to unban user", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
dlg.run();
|
||||
}
|
||||
};
|
||||
Abaddon::Get().GetDiscordClient().UnbanUser(GuildID, id, sigc::track_obj(cb, *this));
|
||||
}
|
||||
}
|
||||
|
||||
void GuildSettingsBansPane::OnMenuCopyID() {
|
||||
auto selected_row = *m_view.get_selection()->get_selected();
|
||||
if (selected_row)
|
||||
Gtk::Clipboard::get()->set_text(std::to_string(static_cast<Snowflake>(selected_row[m_columns.m_col_id])));
|
||||
}
|
||||
|
||||
bool GuildSettingsBansPane::OnTreeButtonPress(GdkEventButton *event) {
|
||||
if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_SECONDARY) {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto self_id = discord.GetUserData().ID;
|
||||
const auto can_ban = discord.HasGuildPermission(self_id, GuildID, Permission::BAN_MEMBERS);
|
||||
m_menu_unban.set_sensitive(can_ban);
|
||||
auto selection = m_view.get_selection();
|
||||
Gtk::TreeModel::Path path;
|
||||
if (m_view.get_path_at_pos(event->x, event->y, path)) {
|
||||
m_view.get_selection()->select(path);
|
||||
m_menu.popup_at_pointer(reinterpret_cast<GdkEvent *>(event));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void GuildSettingsBansPane::OnBanRemove(Snowflake guild_id, Snowflake user_id) {
|
||||
if (guild_id != GuildID) return;
|
||||
for (auto &child : m_model->children()) {
|
||||
if (static_cast<Snowflake>(child[m_columns.m_col_id]) == user_id) {
|
||||
m_model->erase(child);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GuildSettingsBansPane::OnBanAdd(Snowflake guild_id, Snowflake user_id) {
|
||||
if (guild_id != GuildID) return;
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
if (discord.HasGuildPermission(discord.GetUserData().ID, guild_id, Permission::BAN_MEMBERS)) {
|
||||
discord.FetchGuildBan(guild_id, user_id, sigc::mem_fun(*this, &GuildSettingsBansPane::OnGuildBanFetch));
|
||||
} else {
|
||||
auto user = *discord.GetUser(user_id);
|
||||
auto row = *m_model->append();
|
||||
row[m_columns.m_col_id] = user_id;
|
||||
row[m_columns.m_col_user] = user.Username + "#" + user.Discriminator;
|
||||
row[m_columns.m_col_reason] = "";
|
||||
}
|
||||
}
|
||||
|
||||
GuildSettingsBansPane::ModelColumns::ModelColumns() {
|
||||
add(m_col_id);
|
||||
add(m_col_user);
|
||||
add(m_col_reason);
|
||||
}
|
||||
45
src/windows/guildsettings/banspane.hpp
Normal file
45
src/windows/guildsettings/banspane.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/snowflake.hpp"
|
||||
#include "discord/ban.hpp"
|
||||
|
||||
class GuildSettingsBansPane : public Gtk::Box {
|
||||
public:
|
||||
GuildSettingsBansPane(Snowflake id);
|
||||
|
||||
private:
|
||||
void OnMap();
|
||||
|
||||
bool m_requested = false;
|
||||
|
||||
void OnGuildBanFetch(const BanData &ban);
|
||||
void OnGuildBansFetch(const std::vector<BanData> &bans);
|
||||
void OnMenuUnban();
|
||||
void OnMenuCopyID();
|
||||
bool OnTreeButtonPress(GdkEventButton *event);
|
||||
void OnBanRemove(Snowflake guild_id, Snowflake user_id);
|
||||
void OnBanAdd(Snowflake guild_id, Snowflake user_id);
|
||||
|
||||
Gtk::Label *m_no_perms_note = nullptr;
|
||||
|
||||
Gtk::ScrolledWindow m_scroll;
|
||||
Gtk::TreeView m_view;
|
||||
|
||||
Snowflake GuildID;
|
||||
|
||||
class ModelColumns : public Gtk::TreeModel::ColumnRecord {
|
||||
public:
|
||||
ModelColumns();
|
||||
|
||||
Gtk::TreeModelColumn<Glib::ustring> m_col_user;
|
||||
Gtk::TreeModelColumn<Glib::ustring> m_col_reason;
|
||||
Gtk::TreeModelColumn<Snowflake> m_col_id;
|
||||
};
|
||||
|
||||
ModelColumns m_columns;
|
||||
Glib::RefPtr<Gtk::ListStore> m_model;
|
||||
|
||||
Gtk::Menu m_menu;
|
||||
Gtk::MenuItem m_menu_unban;
|
||||
Gtk::MenuItem m_menu_copy_id;
|
||||
};
|
||||
257
src/windows/guildsettings/emojispane.cpp
Normal file
257
src/windows/guildsettings/emojispane.cpp
Normal file
@@ -0,0 +1,257 @@
|
||||
#include "emojispane.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include "components/cellrendererpixbufanimation.hpp"
|
||||
|
||||
GuildSettingsEmojisPane::GuildSettingsEmojisPane(Snowflake guild_id)
|
||||
: Gtk::Box(Gtk::ORIENTATION_VERTICAL)
|
||||
, GuildID(guild_id)
|
||||
, m_model(Gtk::ListStore::create(m_columns))
|
||||
, m_filter(Gtk::TreeModelFilter::create(m_model))
|
||||
, m_menu_delete("Delete")
|
||||
, m_menu_copy_id("Copy ID")
|
||||
, m_menu_copy_emoji_url("Copy Emoji URL")
|
||||
, m_menu_show_emoji("Open in Browser") {
|
||||
signal_map().connect(sigc::mem_fun(*this, &GuildSettingsEmojisPane::OnMap));
|
||||
set_name("guild-emojis-pane");
|
||||
|
||||
m_view_scroll.set_hexpand(true);
|
||||
m_view_scroll.set_vexpand(true);
|
||||
|
||||
m_view.signal_button_press_event().connect(sigc::mem_fun(*this, &GuildSettingsEmojisPane::OnTreeButtonPress), false);
|
||||
|
||||
m_menu_copy_id.signal_activate().connect(sigc::mem_fun(*this, &GuildSettingsEmojisPane::OnMenuCopyID));
|
||||
m_menu_delete.signal_activate().connect(sigc::mem_fun(*this, &GuildSettingsEmojisPane::OnMenuDelete));
|
||||
m_menu_copy_emoji_url.signal_activate().connect(sigc::mem_fun(*this, &GuildSettingsEmojisPane::OnMenuCopyEmojiURL));
|
||||
m_menu_show_emoji.signal_activate().connect(sigc::mem_fun(*this, &GuildSettingsEmojisPane::OnMenuShowEmoji));
|
||||
|
||||
m_menu.append(m_menu_delete);
|
||||
m_menu.append(m_menu_copy_id);
|
||||
m_menu.append(m_menu_copy_emoji_url);
|
||||
m_menu.append(m_menu_show_emoji);
|
||||
m_menu.show_all();
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
|
||||
discord.signal_guild_emojis_update().connect(sigc::hide<0>(sigc::mem_fun(*this, &GuildSettingsEmojisPane::OnFetchEmojis)));
|
||||
|
||||
const auto self_id = discord.GetUserData().ID;
|
||||
const bool can_manage = discord.HasGuildPermission(self_id, GuildID, Permission::MANAGE_EMOJIS);
|
||||
m_menu_delete.set_sensitive(can_manage);
|
||||
|
||||
m_search.set_placeholder_text("Filter");
|
||||
m_search.signal_changed().connect([this]() {
|
||||
m_filter->refilter();
|
||||
});
|
||||
|
||||
m_view_scroll.add(m_view);
|
||||
add(m_search);
|
||||
add(m_view_scroll);
|
||||
m_search.show();
|
||||
m_view.show();
|
||||
m_view_scroll.show();
|
||||
|
||||
m_filter->set_visible_func([this](const Gtk::TreeModel::const_iterator &iter) -> bool {
|
||||
const auto text = m_search.get_text();
|
||||
if (text == "") return true;
|
||||
return StringContainsCaseless((*iter)[m_columns.m_col_name], text);
|
||||
});
|
||||
m_view.set_enable_search(false);
|
||||
m_view.set_model(m_filter);
|
||||
|
||||
auto *column = Gtk::manage(new Gtk::TreeView::Column("Emoji"));
|
||||
auto *renderer = Gtk::manage(new CellRendererPixbufAnimation);
|
||||
column->pack_start(*renderer);
|
||||
column->add_attribute(renderer->property_pixbuf(), m_columns.m_col_pixbuf);
|
||||
column->add_attribute(renderer->property_pixbuf_animation(), m_columns.m_col_pixbuf_animation);
|
||||
m_view.append_column(*column);
|
||||
|
||||
if (can_manage) {
|
||||
auto *column = Gtk::manage(new Gtk::TreeView::Column("Name"));
|
||||
auto *renderer = Gtk::manage(new Gtk::CellRendererText);
|
||||
column->pack_start(*renderer);
|
||||
column->add_attribute(renderer->property_text(), m_columns.m_col_name);
|
||||
renderer->property_editable() = true;
|
||||
renderer->signal_edited().connect([this, renderer, column](const Glib::ustring &path, const Glib::ustring &text) {
|
||||
std::string new_str;
|
||||
int size = 0;
|
||||
for (const auto ch : text) {
|
||||
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_')
|
||||
new_str += ch;
|
||||
else if (ch == ' ')
|
||||
new_str += '_';
|
||||
if (++size == 32) break;
|
||||
}
|
||||
if (auto row = *m_model->get_iter(path)) {
|
||||
row[m_columns.m_col_name] = new_str;
|
||||
OnEditName(row[m_columns.m_col_id], new_str);
|
||||
}
|
||||
});
|
||||
m_view.append_column(*column);
|
||||
} else
|
||||
m_view.append_column("Name", m_columns.m_col_name);
|
||||
if (can_manage)
|
||||
m_view.append_column("Creator", m_columns.m_col_creator);
|
||||
m_view.append_column("Is Animated?", m_columns.m_col_animated);
|
||||
|
||||
for (const auto column : m_view.get_columns())
|
||||
column->set_resizable(true);
|
||||
}
|
||||
|
||||
void GuildSettingsEmojisPane::OnMap() {
|
||||
m_view.grab_focus();
|
||||
|
||||
if (m_requested) return;
|
||||
m_requested = true;
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto self_id = discord.GetUserData().ID;
|
||||
const bool can_manage = discord.HasGuildPermission(self_id, GuildID, Permission::MANAGE_EMOJIS);
|
||||
m_menu_delete.set_sensitive(can_manage);
|
||||
|
||||
discord.FetchGuildEmojis(GuildID, sigc::mem_fun(*this, &GuildSettingsEmojisPane::OnFetchEmojis));
|
||||
}
|
||||
|
||||
void GuildSettingsEmojisPane::AddEmojiRow(const EmojiData &emoji) {
|
||||
auto &img = Abaddon::Get().GetImageManager();
|
||||
|
||||
auto row = *m_model->append();
|
||||
|
||||
row[m_columns.m_col_id] = emoji.ID;
|
||||
row[m_columns.m_col_pixbuf] = img.GetPlaceholder(32);
|
||||
row[m_columns.m_col_name] = emoji.Name;
|
||||
if (emoji.Creator.has_value())
|
||||
row[m_columns.m_col_creator] = emoji.Creator->Username + "#" + emoji.Creator->Discriminator;
|
||||
if (emoji.IsAnimated.has_value())
|
||||
row[m_columns.m_col_animated] = *emoji.IsAnimated ? "Yes" : "No";
|
||||
else
|
||||
row[m_columns.m_col_animated] = "No";
|
||||
if (emoji.IsAvailable.has_value())
|
||||
row[m_columns.m_col_available] = *emoji.IsAvailable ? "Yes" : "No";
|
||||
else
|
||||
row[m_columns.m_col_available] = "Yes";
|
||||
|
||||
static bool show_animations = Abaddon::Get().GetSettings().GetShowAnimations();
|
||||
if (show_animations && emoji.IsAnimated.has_value() && *emoji.IsAnimated) {
|
||||
const auto cb = [this, id = emoji.ID](const Glib::RefPtr<Gdk::PixbufAnimation> &pb) {
|
||||
for (auto &row : m_model->children()) {
|
||||
if (static_cast<Snowflake>(row[m_columns.m_col_id]) == id) {
|
||||
row[m_columns.m_col_pixbuf_animation] = pb;
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
img.LoadAnimationFromURL(emoji.GetURL("gif"), 32, 32, sigc::track_obj(cb, *this));
|
||||
} else {
|
||||
const auto cb = [this, id = emoji.ID](const Glib::RefPtr<Gdk::Pixbuf> &pb) {
|
||||
for (auto &row : m_model->children()) {
|
||||
if (static_cast<Snowflake>(row[m_columns.m_col_id]) == id) {
|
||||
row[m_columns.m_col_pixbuf] = pb->scale_simple(32, 32, Gdk::INTERP_BILINEAR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
img.LoadFromURL(emoji.GetURL(), sigc::track_obj(cb, *this));
|
||||
}
|
||||
}
|
||||
|
||||
void GuildSettingsEmojisPane::OnFetchEmojis(std::vector<EmojiData> emojis) {
|
||||
m_model->clear();
|
||||
|
||||
// put animated emojis at the end then sort alphabetically
|
||||
std::sort(emojis.begin(), emojis.end(), [&](const EmojiData &a, const EmojiData &b) {
|
||||
const bool a_is_animated = a.IsAnimated.has_value() && *a.IsAnimated;
|
||||
const bool b_is_animated = b.IsAnimated.has_value() && *b.IsAnimated;
|
||||
if (a_is_animated == b_is_animated)
|
||||
return a.Name < b.Name;
|
||||
else if (a_is_animated && !b_is_animated)
|
||||
return false;
|
||||
else if (!a_is_animated && b_is_animated)
|
||||
return true;
|
||||
return false; // this wont happen please be quiet compiler
|
||||
});
|
||||
|
||||
for (const auto &emoji : emojis)
|
||||
AddEmojiRow(emoji);
|
||||
}
|
||||
|
||||
void GuildSettingsEmojisPane::OnEditName(Snowflake id, const std::string &name) {
|
||||
const auto cb = [this](DiscordError code) {
|
||||
if (code != DiscordError::NONE) {
|
||||
Gtk::MessageDialog dlg("Failed to set emoji name", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
dlg.run();
|
||||
}
|
||||
};
|
||||
Abaddon::Get().GetDiscordClient().ModifyEmojiName(GuildID, id, name, cb);
|
||||
}
|
||||
|
||||
void GuildSettingsEmojisPane::OnMenuCopyID() {
|
||||
if (auto selected_row = *m_view.get_selection()->get_selected()) {
|
||||
const auto id = static_cast<Snowflake>(selected_row[m_columns.m_col_id]);
|
||||
Gtk::Clipboard::get()->set_text(std::to_string(id));
|
||||
}
|
||||
}
|
||||
|
||||
void GuildSettingsEmojisPane::OnMenuDelete() {
|
||||
if (auto selected_row = *m_view.get_selection()->get_selected()) {
|
||||
const auto name = static_cast<Glib::ustring>(selected_row[m_columns.m_col_name]);
|
||||
const auto id = static_cast<Snowflake>(selected_row[m_columns.m_col_id]);
|
||||
if (auto *window = dynamic_cast<Gtk::Window *>(get_toplevel()))
|
||||
if (Abaddon::Get().ShowConfirm("Are you sure you want to delete " + name + "?", window)) {
|
||||
const auto cb = [this](DiscordError code) {
|
||||
if (code != DiscordError::NONE) {
|
||||
Gtk::MessageDialog dlg("Failed to delete emoji", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
dlg.run();
|
||||
}
|
||||
};
|
||||
Abaddon::Get().GetDiscordClient().DeleteEmoji(GuildID, id, cb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GuildSettingsEmojisPane::OnMenuCopyEmojiURL() {
|
||||
if (auto selected_row = *m_view.get_selection()->get_selected()) {
|
||||
const auto id = static_cast<Snowflake>(selected_row[m_columns.m_col_id]);
|
||||
const bool is_animated = static_cast<Glib::ustring>(selected_row[m_columns.m_col_animated]) == "Yes";
|
||||
Gtk::Clipboard::get()->set_text(EmojiData::URLFromID(id, is_animated ? "gif" : "png", "256"));
|
||||
}
|
||||
}
|
||||
|
||||
void GuildSettingsEmojisPane::OnMenuShowEmoji() {
|
||||
if (auto selected_row = *m_view.get_selection()->get_selected()) {
|
||||
const auto id = static_cast<Snowflake>(selected_row[m_columns.m_col_id]);
|
||||
const bool is_animated = static_cast<Glib::ustring>(selected_row[m_columns.m_col_animated]) == "Yes";
|
||||
LaunchBrowser(EmojiData::URLFromID(id, is_animated ? "gif" : "png", "256"));
|
||||
}
|
||||
}
|
||||
|
||||
bool GuildSettingsEmojisPane::OnTreeButtonPress(GdkEventButton *event) {
|
||||
if (event->button == GDK_BUTTON_SECONDARY) {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto self_id = discord.GetUserData().ID;
|
||||
const bool can_manage = discord.HasGuildPermission(self_id, GuildID, Permission::MANAGE_EMOJIS);
|
||||
m_menu_delete.set_sensitive(can_manage);
|
||||
|
||||
auto selection = m_view.get_selection();
|
||||
Gtk::TreeModel::Path path;
|
||||
if (m_view.get_path_at_pos(event->x, event->y, path)) {
|
||||
m_view.get_selection()->select(path);
|
||||
m_menu.popup_at_pointer(reinterpret_cast<GdkEvent *>(event));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
GuildSettingsEmojisPane::ModelColumns::ModelColumns() {
|
||||
add(m_col_id);
|
||||
add(m_col_pixbuf);
|
||||
add(m_col_pixbuf_animation);
|
||||
add(m_col_name);
|
||||
add(m_col_creator);
|
||||
add(m_col_animated);
|
||||
add(m_col_available);
|
||||
}
|
||||
53
src/windows/guildsettings/emojispane.hpp
Normal file
53
src/windows/guildsettings/emojispane.hpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/emoji.hpp"
|
||||
|
||||
class GuildSettingsEmojisPane : public Gtk::Box {
|
||||
public:
|
||||
GuildSettingsEmojisPane(Snowflake guild_id);
|
||||
|
||||
private:
|
||||
void OnMap();
|
||||
|
||||
bool m_requested = false;
|
||||
|
||||
void AddEmojiRow(const EmojiData &emoji);
|
||||
|
||||
void OnFetchEmojis(std::vector<EmojiData> emojis);
|
||||
|
||||
void OnEditName(Snowflake id, const std::string &name);
|
||||
void OnMenuCopyID();
|
||||
void OnMenuDelete();
|
||||
void OnMenuCopyEmojiURL();
|
||||
void OnMenuShowEmoji();
|
||||
bool OnTreeButtonPress(GdkEventButton *event);
|
||||
|
||||
Snowflake GuildID;
|
||||
|
||||
Gtk::Entry m_search;
|
||||
Gtk::ScrolledWindow m_view_scroll;
|
||||
Gtk::TreeView m_view;
|
||||
|
||||
class ModelColumns : public Gtk::TreeModel::ColumnRecord {
|
||||
public:
|
||||
ModelColumns();
|
||||
|
||||
Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf>> m_col_pixbuf;
|
||||
Gtk::TreeModelColumn<Glib::RefPtr<Gdk::PixbufAnimation>> m_col_pixbuf_animation;
|
||||
Gtk::TreeModelColumn<Glib::ustring> m_col_name;
|
||||
Gtk::TreeModelColumn<Glib::ustring> m_col_creator;
|
||||
Gtk::TreeModelColumn<Glib::ustring> m_col_animated;
|
||||
Gtk::TreeModelColumn<Glib::ustring> m_col_available;
|
||||
Gtk::TreeModelColumn<Snowflake> m_col_id;
|
||||
};
|
||||
|
||||
ModelColumns m_columns;
|
||||
Glib::RefPtr<Gtk::ListStore> m_model;
|
||||
Glib::RefPtr<Gtk::TreeModelFilter> m_filter;
|
||||
|
||||
Gtk::Menu m_menu;
|
||||
Gtk::MenuItem m_menu_delete;
|
||||
Gtk::MenuItem m_menu_copy_id;
|
||||
Gtk::MenuItem m_menu_copy_emoji_url;
|
||||
Gtk::MenuItem m_menu_show_emoji;
|
||||
};
|
||||
220
src/windows/guildsettings/infopane.cpp
Normal file
220
src/windows/guildsettings/infopane.cpp
Normal file
@@ -0,0 +1,220 @@
|
||||
#include "infopane.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include <filesystem>
|
||||
|
||||
GuildSettingsInfoPane::GuildSettingsInfoPane(Snowflake id)
|
||||
: m_guild_icon_label("Guild icon")
|
||||
, m_guild_name_label("Guild name")
|
||||
, GuildID(id) {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto guild = *discord.GetGuild(id);
|
||||
const auto self_id = discord.GetUserData().ID;
|
||||
const auto can_modify = discord.HasGuildPermission(self_id, id, Permission::MANAGE_GUILD);
|
||||
|
||||
set_name("guild-info-pane");
|
||||
|
||||
m_guild_name.set_sensitive(can_modify);
|
||||
m_guild_name.set_text(guild.Name);
|
||||
m_guild_name.signal_focus_out_event().connect([this](GdkEventFocus *e) -> bool {
|
||||
UpdateGuildName();
|
||||
return false;
|
||||
});
|
||||
m_guild_name.signal_key_press_event().connect([this](GdkEventKey *e) -> bool {
|
||||
if (e->keyval == GDK_KEY_Return)
|
||||
UpdateGuildName();
|
||||
return false;
|
||||
// clang-format off
|
||||
}, false);
|
||||
// clang-format on
|
||||
m_guild_name.set_tooltip_text("Press enter or lose focus to submit");
|
||||
m_guild_name.show();
|
||||
m_guild_name_label.show();
|
||||
|
||||
auto guild_update_cb = [this](Snowflake id) {
|
||||
if (id != GuildID) return;
|
||||
const auto guild = *Abaddon::Get().GetDiscordClient().GetGuild(id);
|
||||
FetchGuildIcon(guild);
|
||||
};
|
||||
discord.signal_guild_update().connect(sigc::track_obj(guild_update_cb, *this));
|
||||
FetchGuildIcon(guild);
|
||||
|
||||
AddPointerCursor(m_guild_icon_ev);
|
||||
|
||||
m_guild_icon.set_margin_bottom(10);
|
||||
if (can_modify) {
|
||||
m_guild_icon_ev.set_tooltip_text("Click to choose a file, right click to paste");
|
||||
|
||||
m_guild_icon_ev.signal_button_press_event().connect([this](GdkEventButton *event) -> bool {
|
||||
if (event->type == GDK_BUTTON_PRESS) {
|
||||
if (event->button == GDK_BUTTON_PRIMARY)
|
||||
UpdateGuildIconPicker();
|
||||
else if (event->button == GDK_BUTTON_SECONDARY)
|
||||
UpdateGuildIconClipboard();
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
} else if (guild.HasIcon()) {
|
||||
std::string guild_icon_url;
|
||||
if (guild.HasAnimatedIcon())
|
||||
guild_icon_url = guild.GetIconURL("gif", "512");
|
||||
else
|
||||
guild_icon_url = guild.GetIconURL("png", "512");
|
||||
m_guild_icon_ev.signal_button_press_event().connect([this, guild_icon_url](GdkEventButton *event) -> bool {
|
||||
if (event->type == GDK_BUTTON_PRESS)
|
||||
if (event->button == GDK_BUTTON_PRIMARY)
|
||||
LaunchBrowser(guild_icon_url);
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
m_guild_icon.show();
|
||||
m_guild_icon_ev.show();
|
||||
|
||||
m_guild_icon_ev.add(m_guild_icon);
|
||||
attach(m_guild_icon_ev, 0, 0, 1, 1);
|
||||
attach(m_guild_name_label, 0, 1, 1, 1);
|
||||
attach_next_to(m_guild_name, m_guild_name_label, Gtk::POS_RIGHT, 1, 1);
|
||||
}
|
||||
|
||||
void GuildSettingsInfoPane::FetchGuildIcon(const GuildData &guild) {
|
||||
m_guild_icon.property_pixbuf() = Abaddon::Get().GetImageManager().GetPlaceholder(32);
|
||||
if (guild.HasIcon()) {
|
||||
if (Abaddon::Get().GetSettings().GetShowAnimations() && guild.HasAnimatedIcon()) {
|
||||
auto cb = [this](const Glib::RefPtr<Gdk::PixbufAnimation> &pixbuf) {
|
||||
m_guild_icon.property_pixbuf_animation() = pixbuf;
|
||||
};
|
||||
Abaddon::Get().GetImageManager().LoadAnimationFromURL(guild.GetIconURL("gif", "64"), 64, 64, sigc::track_obj(cb, *this));
|
||||
}
|
||||
|
||||
auto cb = [this](const Glib::RefPtr<Gdk::Pixbuf> &pixbuf) {
|
||||
m_guild_icon.property_pixbuf() = pixbuf->scale_simple(64, 64, Gdk::INTERP_BILINEAR);
|
||||
};
|
||||
Abaddon::Get().GetImageManager().LoadFromURL(guild.GetIconURL("png", "64"), sigc::track_obj(cb, *this));
|
||||
}
|
||||
}
|
||||
|
||||
void GuildSettingsInfoPane::UpdateGuildName() {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
if (discord.GetGuild(GuildID)->Name == m_guild_name.get_text()) return;
|
||||
|
||||
auto cb = [this](DiscordError code) {
|
||||
if (code != DiscordError::NONE) {
|
||||
m_guild_name.set_text(Abaddon::Get().GetDiscordClient().GetGuild(GuildID)->Name);
|
||||
Gtk::MessageDialog dlg("Failed to set guild name", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
dlg.run();
|
||||
}
|
||||
};
|
||||
discord.SetGuildName(GuildID, m_guild_name.get_text(), sigc::track_obj(cb, *this));
|
||||
}
|
||||
|
||||
void GuildSettingsInfoPane::UpdateGuildIconFromData(const std::vector<uint8_t> &data, const std::string &mime) {
|
||||
auto encoded = "data:" + mime + ";base64," + Glib::Base64::encode(std::string(data.begin(), data.end()));
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
|
||||
auto cb = [this](DiscordError code) {
|
||||
if (code != DiscordError::NONE) {
|
||||
Gtk::MessageDialog dlg("Failed to set guild icon", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
dlg.run();
|
||||
}
|
||||
};
|
||||
discord.SetGuildIcon(GuildID, encoded, sigc::track_obj(cb, *this));
|
||||
}
|
||||
|
||||
void GuildSettingsInfoPane::UpdateGuildIconFromPixbuf(Glib::RefPtr<Gdk::Pixbuf> pixbuf) {
|
||||
int w = pixbuf->get_width();
|
||||
int h = pixbuf->get_height();
|
||||
if (w > 1024 || h > 1024) {
|
||||
GetImageDimensions(w, h, w, h, 1024, 1024);
|
||||
pixbuf = pixbuf->scale_simple(w, h, Gdk::INTERP_BILINEAR);
|
||||
}
|
||||
gchar *buffer;
|
||||
gsize buffer_size;
|
||||
pixbuf->save_to_buffer(buffer, buffer_size, "png");
|
||||
std::vector<uint8_t> data(buffer_size);
|
||||
std::memcpy(data.data(), buffer, buffer_size);
|
||||
UpdateGuildIconFromData(data, "image/png");
|
||||
}
|
||||
|
||||
void GuildSettingsInfoPane::UpdateGuildIconPicker() {
|
||||
// this picker fucking sucks
|
||||
Gtk::FileChooserDialog dlg("Choose new guild icon", Gtk::FILE_CHOOSER_ACTION_OPEN);
|
||||
dlg.get_style_context()->remove_provider(Abaddon::Get().GetStyleProvider());
|
||||
dlg.set_modal(true);
|
||||
dlg.signal_response().connect([this, &dlg](int response) {
|
||||
if (response == Gtk::RESPONSE_OK) {
|
||||
auto data = ReadWholeFile(dlg.get_filename());
|
||||
if (GetExtension(dlg.get_filename()) == ".gif")
|
||||
UpdateGuildIconFromData(data, "image/gif");
|
||||
else
|
||||
try {
|
||||
auto loader = Gdk::PixbufLoader::create();
|
||||
loader->signal_size_prepared().connect([&loader](int inw, int inh) {
|
||||
int w, h;
|
||||
GetImageDimensions(inw, inh, w, h, 1024, 1024);
|
||||
loader->set_size(w, h);
|
||||
});
|
||||
loader->write(data.data(), data.size());
|
||||
loader->close();
|
||||
UpdateGuildIconFromPixbuf(loader->get_pixbuf());
|
||||
} catch (const std::exception &) {};
|
||||
}
|
||||
});
|
||||
|
||||
dlg.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK);
|
||||
dlg.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
|
||||
|
||||
auto filter_images = Gtk::FileFilter::create();
|
||||
if (Abaddon::Get().GetDiscordClient().GetGuild(GuildID)->HasFeature("ANIMATED_ICON")) {
|
||||
filter_images->set_name("Supported images (*.jpg, *.jpeg, *.png, *.gif)");
|
||||
filter_images->add_pattern("*.gif");
|
||||
} else {
|
||||
filter_images->set_name("Supported images (*.jpg, *.jpeg, *.png)");
|
||||
}
|
||||
filter_images->add_pattern("*.jpg");
|
||||
filter_images->add_pattern("*.jpeg");
|
||||
filter_images->add_pattern("*.png");
|
||||
dlg.add_filter(filter_images);
|
||||
|
||||
auto filter_all = Gtk::FileFilter::create();
|
||||
filter_all->set_name("All files (*.*)");
|
||||
filter_all->add_pattern("*.*");
|
||||
dlg.add_filter(filter_all);
|
||||
|
||||
dlg.run();
|
||||
}
|
||||
|
||||
void GuildSettingsInfoPane::UpdateGuildIconClipboard() {
|
||||
std::vector<uint8_t> icon_data;
|
||||
|
||||
auto cb = Gtk::Clipboard::get();
|
||||
// query for file path then for actual image
|
||||
if (cb->wait_is_text_available()) {
|
||||
auto path = cb->wait_for_text();
|
||||
if (!std::filesystem::exists(path.c_str())) return;
|
||||
auto data = ReadWholeFile(path);
|
||||
try {
|
||||
auto loader = Gdk::PixbufLoader::create();
|
||||
loader->signal_size_prepared().connect([&loader](int inw, int inh) {
|
||||
int w, h;
|
||||
GetImageDimensions(inw, inh, w, h, 1024, 1024);
|
||||
loader->set_size(w, h);
|
||||
});
|
||||
loader->write(data.data(), data.size());
|
||||
loader->close();
|
||||
auto pb = loader->get_pixbuf();
|
||||
UpdateGuildIconFromPixbuf(pb);
|
||||
|
||||
return;
|
||||
} catch (const std::exception &) {};
|
||||
}
|
||||
|
||||
if (cb->wait_is_image_available()) {
|
||||
auto pb = cb->wait_for_image();
|
||||
UpdateGuildIconFromPixbuf(pb);
|
||||
return;
|
||||
}
|
||||
}
|
||||
26
src/windows/guildsettings/infopane.hpp
Normal file
26
src/windows/guildsettings/infopane.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/guild.hpp"
|
||||
|
||||
class GuildSettingsInfoPane : public Gtk::Grid {
|
||||
public:
|
||||
GuildSettingsInfoPane(Snowflake id);
|
||||
|
||||
private:
|
||||
void FetchGuildIcon(const GuildData &guild);
|
||||
|
||||
void UpdateGuildName();
|
||||
void UpdateGuildIconFromData(const std::vector<uint8_t> &data, const std::string &mime);
|
||||
void UpdateGuildIconFromPixbuf(Glib::RefPtr<Gdk::Pixbuf> pixbuf);
|
||||
void UpdateGuildIconPicker();
|
||||
void UpdateGuildIconClipboard();
|
||||
|
||||
Gtk::Label m_guild_icon_label;
|
||||
Gtk::EventBox m_guild_icon_ev; // necessary to make custom cursor behave properly
|
||||
Gtk::Image m_guild_icon;
|
||||
|
||||
Gtk::Label m_guild_name_label;
|
||||
Gtk::Entry m_guild_name;
|
||||
|
||||
Snowflake GuildID;
|
||||
};
|
||||
136
src/windows/guildsettings/invitespane.cpp
Normal file
136
src/windows/guildsettings/invitespane.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
#include "invitespane.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
GuildSettingsInvitesPane::GuildSettingsInvitesPane(Snowflake id)
|
||||
: GuildID(id)
|
||||
, m_model(Gtk::ListStore::create(m_columns))
|
||||
, m_menu_delete("Delete") {
|
||||
signal_map().connect(sigc::mem_fun(*this, &GuildSettingsInvitesPane::OnMap));
|
||||
set_name("guild-invites-pane");
|
||||
set_hexpand(true);
|
||||
set_vexpand(true);
|
||||
|
||||
m_view.signal_button_press_event().connect(sigc::mem_fun(*this, &GuildSettingsInvitesPane::OnTreeButtonPress), false);
|
||||
|
||||
m_menu_delete.signal_activate().connect(sigc::mem_fun(*this, &GuildSettingsInvitesPane::OnMenuDelete));
|
||||
m_menu.append(m_menu_delete);
|
||||
m_menu.show_all();
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
|
||||
discord.signal_invite_create().connect(sigc::mem_fun(*this, &GuildSettingsInvitesPane::OnInviteCreate));
|
||||
discord.signal_invite_delete().connect(sigc::mem_fun(*this, &GuildSettingsInvitesPane::OnInviteDelete));
|
||||
|
||||
m_view.show();
|
||||
add(m_view);
|
||||
|
||||
m_view.set_enable_search(false);
|
||||
m_view.set_model(m_model);
|
||||
m_view.append_column("Code", m_columns.m_col_code);
|
||||
m_view.append_column("Expires", m_columns.m_col_expires);
|
||||
m_view.append_column("Created by", m_columns.m_col_inviter);
|
||||
m_view.append_column("Uses", m_columns.m_col_uses);
|
||||
m_view.append_column("Max uses", m_columns.m_col_max_uses);
|
||||
m_view.append_column("Grants temporary membership", m_columns.m_col_temporary);
|
||||
|
||||
for (const auto column : m_view.get_columns())
|
||||
column->set_resizable(true);
|
||||
}
|
||||
|
||||
void GuildSettingsInvitesPane::OnMap() {
|
||||
if (m_requested) return;
|
||||
m_requested = true;
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto self_id = discord.GetUserData().ID;
|
||||
|
||||
if (discord.HasGuildPermission(self_id, GuildID, Permission::MANAGE_GUILD))
|
||||
discord.FetchGuildInvites(GuildID, sigc::mem_fun(*this, &GuildSettingsInvitesPane::OnInvitesFetch));
|
||||
}
|
||||
|
||||
void GuildSettingsInvitesPane::AppendInvite(const InviteData &invite) {
|
||||
auto row = *m_model->append();
|
||||
row[m_columns.m_col_code] = invite.Code;
|
||||
if (invite.Inviter.has_value())
|
||||
row[m_columns.m_col_inviter] = invite.Inviter->Username + "#" + invite.Inviter->Discriminator;
|
||||
|
||||
if (invite.MaxAge.has_value()) {
|
||||
if (*invite.MaxAge == 0)
|
||||
row[m_columns.m_col_expires] = "Never";
|
||||
else
|
||||
row[m_columns.m_col_expires] = FormatISO8601(*invite.CreatedAt, *invite.MaxAge);
|
||||
}
|
||||
|
||||
row[m_columns.m_col_uses] = *invite.Uses;
|
||||
if (*invite.MaxUses == 0)
|
||||
row[m_columns.m_col_max_uses] = "Unlimited";
|
||||
else
|
||||
row[m_columns.m_col_max_uses] = std::to_string(*invite.MaxUses);
|
||||
|
||||
row[m_columns.m_col_temporary] = *invite.IsTemporary ? "Yes" : "No";
|
||||
}
|
||||
|
||||
void GuildSettingsInvitesPane::OnInviteFetch(const std::optional<InviteData> &invite) {
|
||||
if (!invite.has_value()) return;
|
||||
AppendInvite(*invite);
|
||||
}
|
||||
|
||||
void GuildSettingsInvitesPane::OnInvitesFetch(const std::vector<InviteData> &invites) {
|
||||
for (const auto &invite : invites)
|
||||
AppendInvite(invite);
|
||||
}
|
||||
|
||||
void GuildSettingsInvitesPane::OnInviteCreate(const InviteData &invite) {
|
||||
if (invite.Guild->ID == GuildID)
|
||||
OnInviteFetch(std::make_optional(invite));
|
||||
}
|
||||
|
||||
void GuildSettingsInvitesPane::OnInviteDelete(const InviteDeleteObject &data) {
|
||||
if (*data.GuildID == GuildID)
|
||||
for (auto &row : m_model->children())
|
||||
if (row[m_columns.m_col_code] == data.Code)
|
||||
m_model->erase(row);
|
||||
}
|
||||
|
||||
void GuildSettingsInvitesPane::OnMenuDelete() {
|
||||
auto selected_row = *m_view.get_selection()->get_selected();
|
||||
if (selected_row) {
|
||||
auto code = static_cast<Glib::ustring>(selected_row[m_columns.m_col_code]);
|
||||
auto cb = [this](DiscordError code) {
|
||||
if (code != DiscordError::NONE) {
|
||||
Gtk::MessageDialog dlg("Failed to delete invite", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
dlg.run();
|
||||
}
|
||||
};
|
||||
Abaddon::Get().GetDiscordClient().DeleteInvite(code, sigc::track_obj(cb, *this));
|
||||
}
|
||||
}
|
||||
|
||||
bool GuildSettingsInvitesPane::OnTreeButtonPress(GdkEventButton *event) {
|
||||
if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_SECONDARY) {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto self_id = discord.GetUserData().ID;
|
||||
const auto can_manage = discord.HasGuildPermission(self_id, GuildID, Permission::MANAGE_GUILD);
|
||||
m_menu_delete.set_sensitive(can_manage);
|
||||
auto selection = m_view.get_selection();
|
||||
Gtk::TreeModel::Path path;
|
||||
if (m_view.get_path_at_pos(event->x, event->y, path)) {
|
||||
m_view.get_selection()->select(path);
|
||||
m_menu.popup_at_pointer(reinterpret_cast<GdkEvent *>(event));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
GuildSettingsInvitesPane::ModelColumns::ModelColumns() {
|
||||
add(m_col_code);
|
||||
add(m_col_expires);
|
||||
add(m_col_inviter);
|
||||
add(m_col_temporary);
|
||||
add(m_col_uses);
|
||||
add(m_col_max_uses);
|
||||
}
|
||||
43
src/windows/guildsettings/invitespane.hpp
Normal file
43
src/windows/guildsettings/invitespane.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/objects.hpp"
|
||||
|
||||
class GuildSettingsInvitesPane : public Gtk::ScrolledWindow {
|
||||
public:
|
||||
GuildSettingsInvitesPane(Snowflake id);
|
||||
|
||||
private:
|
||||
void OnMap();
|
||||
|
||||
bool m_requested = false;
|
||||
|
||||
void AppendInvite(const InviteData &invite);
|
||||
void OnInviteFetch(const std::optional<InviteData> &invite);
|
||||
void OnInvitesFetch(const std::vector<InviteData> &invites);
|
||||
void OnInviteCreate(const InviteData &invite);
|
||||
void OnInviteDelete(const InviteDeleteObject &data);
|
||||
void OnMenuDelete();
|
||||
bool OnTreeButtonPress(GdkEventButton *event);
|
||||
|
||||
Gtk::TreeView m_view;
|
||||
|
||||
Snowflake GuildID;
|
||||
|
||||
class ModelColumns : public Gtk::TreeModel::ColumnRecord {
|
||||
public:
|
||||
ModelColumns();
|
||||
|
||||
Gtk::TreeModelColumn<Glib::ustring> m_col_code;
|
||||
Gtk::TreeModelColumn<Glib::ustring> m_col_expires;
|
||||
Gtk::TreeModelColumn<Glib::ustring> m_col_inviter;
|
||||
Gtk::TreeModelColumn<Glib::ustring> m_col_temporary;
|
||||
Gtk::TreeModelColumn<int> m_col_uses;
|
||||
Gtk::TreeModelColumn<Glib::ustring> m_col_max_uses;
|
||||
};
|
||||
|
||||
ModelColumns m_columns;
|
||||
Glib::RefPtr<Gtk::ListStore> m_model;
|
||||
|
||||
Gtk::Menu m_menu;
|
||||
Gtk::MenuItem m_menu_delete;
|
||||
};
|
||||
410
src/windows/guildsettings/memberspane.cpp
Normal file
410
src/windows/guildsettings/memberspane.cpp
Normal file
@@ -0,0 +1,410 @@
|
||||
#include "memberspane.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
GuildSettingsMembersPane::GuildSettingsMembersPane(Snowflake id)
|
||||
: Gtk::Box(Gtk::ORIENTATION_VERTICAL)
|
||||
, GuildID(id)
|
||||
, m_layout(Gtk::ORIENTATION_HORIZONTAL)
|
||||
, m_member_list(id)
|
||||
, m_member_info(id) {
|
||||
set_name("guild-members-pane");
|
||||
set_hexpand(true);
|
||||
set_vexpand(true);
|
||||
|
||||
m_member_list.signal_member_select().connect(sigc::mem_fun(m_member_info, &GuildSettingsMembersPaneInfo::SetUser));
|
||||
|
||||
m_note.set_label("Some members may not be shown if the client is not aware of them");
|
||||
m_note.set_single_line_mode(true);
|
||||
m_note.set_ellipsize(Pango::ELLIPSIZE_END);
|
||||
|
||||
m_layout.set_homogeneous(true);
|
||||
m_layout.add(m_member_list);
|
||||
m_layout.add(m_member_info);
|
||||
add(m_note);
|
||||
add(m_layout);
|
||||
|
||||
m_member_list.show();
|
||||
m_member_info.show();
|
||||
m_note.show();
|
||||
m_layout.show();
|
||||
}
|
||||
|
||||
GuildSettingsMembersPaneMembers::GuildSettingsMembersPaneMembers(Snowflake id)
|
||||
: Gtk::Box(Gtk::ORIENTATION_VERTICAL)
|
||||
, GuildID(id) {
|
||||
m_list_scroll.get_style_context()->add_class("guild-members-pane-list");
|
||||
|
||||
m_list_scroll.set_hexpand(true);
|
||||
m_list_scroll.set_vexpand(true);
|
||||
m_list_scroll.set_propagate_natural_height(true);
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
auto members = discord.GetUsersInGuild(id);
|
||||
const auto guild = *discord.GetGuild(GuildID);
|
||||
for (const auto member_id : members) {
|
||||
auto member = discord.GetMember(member_id, GuildID);
|
||||
if (!member.has_value()) continue; // fixme this should not be necessary
|
||||
member->User = discord.GetUser(member_id);
|
||||
if (member->User->IsDeleted()) continue;
|
||||
auto *row = Gtk::manage(new GuildSettingsMembersListItem(guild, *member));
|
||||
row->show();
|
||||
m_list.add(*row);
|
||||
}
|
||||
|
||||
m_list.set_selection_mode(Gtk::SELECTION_SINGLE);
|
||||
m_list.signal_row_selected().connect([this](Gtk::ListBoxRow *selected_) {
|
||||
if (auto *selected = dynamic_cast<GuildSettingsMembersListItem *>(selected_))
|
||||
m_signal_member_select.emit(selected->UserID);
|
||||
});
|
||||
|
||||
m_search.set_placeholder_text("Filter");
|
||||
m_search.signal_changed().connect([this] {
|
||||
m_list.invalidate_filter();
|
||||
});
|
||||
|
||||
m_list.set_filter_func([this](Gtk::ListBoxRow *row_) -> bool {
|
||||
const auto search_term = m_search.get_text();
|
||||
if (search_term.size() == 0) return true;
|
||||
if (auto *row = dynamic_cast<GuildSettingsMembersListItem *>(row_))
|
||||
return StringContainsCaseless(row->DisplayTerm, m_search.get_text());
|
||||
return true;
|
||||
});
|
||||
|
||||
m_list_scroll.add(m_list);
|
||||
add(m_search);
|
||||
add(m_list_scroll);
|
||||
|
||||
m_search.show();
|
||||
m_list.show();
|
||||
m_list_scroll.show();
|
||||
}
|
||||
|
||||
GuildSettingsMembersPaneMembers::type_signal_member_select GuildSettingsMembersPaneMembers::signal_member_select() {
|
||||
return m_signal_member_select;
|
||||
}
|
||||
|
||||
GuildSettingsMembersListItem::GuildSettingsMembersListItem(const GuildData &guild, const GuildMember &member)
|
||||
: UserID(member.User->ID)
|
||||
, GuildID(guild.ID)
|
||||
, m_avatar(32, 32) {
|
||||
m_avatar.SetAnimated(true);
|
||||
|
||||
m_ev.signal_button_press_event().connect([this](GdkEventButton *event) -> bool {
|
||||
if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_SECONDARY) {
|
||||
Abaddon::Get().ShowUserMenu(reinterpret_cast<GdkEvent *>(event), UserID, GuildID);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
|
||||
if (member.User->HasAnimatedAvatar() && Abaddon::Get().GetSettings().GetShowAnimations())
|
||||
m_avatar.SetURL(member.User->GetAvatarURL("gif", "32"));
|
||||
else
|
||||
m_avatar.SetURL(member.User->GetAvatarURL("png", "32"));
|
||||
|
||||
DisplayTerm = member.User->Username + "#" + member.User->Discriminator;
|
||||
|
||||
const auto member_update_cb = [this](Snowflake guild_id, Snowflake user_id) {
|
||||
if (user_id == UserID)
|
||||
UpdateColor();
|
||||
};
|
||||
discord.signal_guild_member_update().connect(sigc::track_obj(member_update_cb, *this));
|
||||
UpdateColor();
|
||||
|
||||
static bool crown = Abaddon::Get().GetSettings().GetShowOwnerCrown();
|
||||
if (crown && guild.OwnerID == member.User->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_start(10);
|
||||
m_crown->show();
|
||||
} catch (...) {}
|
||||
}
|
||||
|
||||
m_avatar.set_margin_end(5);
|
||||
m_avatar.set_halign(Gtk::ALIGN_START);
|
||||
m_avatar.set_valign(Gtk::ALIGN_CENTER);
|
||||
m_name.set_halign(Gtk::ALIGN_START);
|
||||
m_name.set_valign(Gtk::ALIGN_CENTER);
|
||||
|
||||
m_main.set_hexpand(true);
|
||||
|
||||
m_main.add(m_avatar);
|
||||
m_main.add(m_name);
|
||||
if (m_crown != nullptr)
|
||||
m_main.add(*m_crown);
|
||||
|
||||
m_ev.add(m_main);
|
||||
add(m_ev);
|
||||
|
||||
m_avatar.show();
|
||||
m_name.show();
|
||||
m_main.show();
|
||||
m_ev.show();
|
||||
}
|
||||
|
||||
void GuildSettingsMembersListItem::UpdateColor() {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto user = *discord.GetUser(UserID);
|
||||
if (auto color_id = discord.GetMemberHoistedRole(GuildID, UserID, true); color_id.IsValid()) {
|
||||
auto role = *discord.GetRole(color_id);
|
||||
m_name.set_markup("<span color='#" + IntToCSSColor(role.Color) + "'>" + user.GetEscapedBoldString<false>() + "</span>");
|
||||
} else
|
||||
m_name.set_markup(user.GetEscapedBoldString<false>());
|
||||
}
|
||||
|
||||
GuildSettingsMembersPaneInfo::GuildSettingsMembersPaneInfo(Snowflake guild_id)
|
||||
: GuildID(guild_id)
|
||||
, m_roles(guild_id)
|
||||
, m_box(Gtk::ORIENTATION_VERTICAL) {
|
||||
get_style_context()->add_class("guild-members-pane-info");
|
||||
|
||||
const auto label = [](Gtk::Label &lbl) {
|
||||
lbl.set_single_line_mode(true);
|
||||
lbl.set_halign(Gtk::ALIGN_START);
|
||||
lbl.set_valign(Gtk::ALIGN_START);
|
||||
lbl.set_ellipsize(Pango::ELLIPSIZE_END);
|
||||
lbl.set_margin_bottom(5);
|
||||
lbl.show();
|
||||
};
|
||||
|
||||
m_bot.set_text("User is a bot");
|
||||
|
||||
label(m_bot);
|
||||
label(m_id);
|
||||
label(m_created);
|
||||
label(m_joined);
|
||||
label(m_nickname);
|
||||
label(m_boosting);
|
||||
|
||||
m_box.set_halign(Gtk::ALIGN_FILL);
|
||||
m_box.set_valign(Gtk::ALIGN_START);
|
||||
m_box.set_hexpand(true);
|
||||
m_box.set_vexpand(true);
|
||||
m_box.add(m_bot);
|
||||
m_box.add(m_id);
|
||||
m_box.add(m_created);
|
||||
m_box.add(m_joined);
|
||||
m_box.add(m_nickname);
|
||||
m_box.add(m_boosting);
|
||||
m_box.add(m_roles);
|
||||
|
||||
m_bot.hide();
|
||||
m_box.show();
|
||||
|
||||
add(m_box);
|
||||
}
|
||||
|
||||
void GuildSettingsMembersPaneInfo::SetUser(Snowflake user_id) {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto guild = *discord.GetGuild(GuildID);
|
||||
auto member = *discord.GetMember(user_id, GuildID);
|
||||
member.User = discord.GetUser(user_id);
|
||||
|
||||
m_bot.set_visible(member.User->IsBot.has_value() && *member.User->IsBot);
|
||||
|
||||
m_id.set_text("User ID: " + std::to_string(user_id));
|
||||
m_created.set_text("Account created: " + user_id.GetLocalTimestamp());
|
||||
if (member.JoinedAt != "")
|
||||
m_joined.set_text("Joined server: " + FormatISO8601(member.JoinedAt));
|
||||
else
|
||||
m_joined.set_text("Joined server: Unknown");
|
||||
m_nickname.set_text("Nickname: " + member.Nickname);
|
||||
m_nickname.set_visible(member.Nickname != "");
|
||||
if (member.PremiumSince.has_value()) {
|
||||
m_boosting.set_text("Boosting since " + FormatISO8601(*member.PremiumSince));
|
||||
m_boosting.show();
|
||||
} else
|
||||
m_boosting.hide();
|
||||
|
||||
m_roles.show();
|
||||
m_roles.SetRoles(user_id, member.Roles, guild.OwnerID == discord.GetUserData().ID);
|
||||
}
|
||||
|
||||
GuildSettingsMembersPaneRoles::GuildSettingsMembersPaneRoles(Snowflake guild_id)
|
||||
: GuildID(guild_id) {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto self_id = discord.GetUserData().ID;
|
||||
const bool can_modify = discord.HasGuildPermission(self_id, guild_id, Permission::MANAGE_ROLES);
|
||||
const auto highest = discord.GetMemberHighestRole(GuildID, self_id);
|
||||
if (highest.has_value())
|
||||
m_hoisted_position = highest->Position;
|
||||
|
||||
discord.signal_role_create().connect(sigc::mem_fun(*this, &GuildSettingsMembersPaneRoles::OnRoleCreate));
|
||||
discord.signal_role_update().connect(sigc::mem_fun(*this, &GuildSettingsMembersPaneRoles::OnRoleUpdate));
|
||||
discord.signal_role_delete().connect(sigc::mem_fun(*this, &GuildSettingsMembersPaneRoles::OnRoleDelete));
|
||||
|
||||
const auto guild = *discord.GetGuild(guild_id);
|
||||
const auto roles = guild.FetchRoles();
|
||||
for (const auto &role : roles) {
|
||||
CreateRow(can_modify, role, guild.OwnerID == self_id);
|
||||
}
|
||||
|
||||
m_list.set_sort_func([this](Gtk::ListBoxRow *a, Gtk::ListBoxRow *b) -> int {
|
||||
auto *rowa = dynamic_cast<GuildSettingsMembersPaneRolesItem *>(a);
|
||||
auto *rowb = dynamic_cast<GuildSettingsMembersPaneRolesItem *>(b);
|
||||
return rowb->Position - rowa->Position;
|
||||
});
|
||||
|
||||
set_propagate_natural_height(true);
|
||||
set_propagate_natural_width(true);
|
||||
set_hexpand(true);
|
||||
set_vexpand(true);
|
||||
set_halign(Gtk::ALIGN_FILL);
|
||||
set_valign(Gtk::ALIGN_START);
|
||||
|
||||
m_list.show();
|
||||
|
||||
add(m_list);
|
||||
}
|
||||
|
||||
void GuildSettingsMembersPaneRoles::SetRoles(Snowflake user_id, const std::vector<Snowflake> &roles, bool is_owner) {
|
||||
UserID = user_id;
|
||||
|
||||
for (auto it = m_update_connection.begin(); it != m_update_connection.end();) {
|
||||
it->disconnect();
|
||||
it = m_update_connection.erase(it);
|
||||
}
|
||||
|
||||
m_set_role_ids = { roles.begin(), roles.end() };
|
||||
for (const auto &[role_id, row] : m_rows) {
|
||||
auto role = *Abaddon::Get().GetDiscordClient().GetRole(role_id);
|
||||
if (role.ID == GuildID) {
|
||||
row->SetChecked(true);
|
||||
row->SetToggleable(false);
|
||||
} else {
|
||||
row->SetToggleable(role.Position < m_hoisted_position || is_owner);
|
||||
row->SetChecked(m_set_role_ids.find(role_id) != m_set_role_ids.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GuildSettingsMembersPaneRoles::CreateRow(bool has_manage_roles, const RoleData &role, bool is_owner) {
|
||||
auto *row = Gtk::manage(new GuildSettingsMembersPaneRolesItem(has_manage_roles, role));
|
||||
if (role.ID == GuildID) {
|
||||
row->SetChecked(true);
|
||||
row->SetToggleable(false);
|
||||
} else
|
||||
row->SetToggleable(role.Position < m_hoisted_position || is_owner);
|
||||
row->signal_role_click().connect(sigc::mem_fun(*this, &GuildSettingsMembersPaneRoles::OnRoleToggle));
|
||||
row->show();
|
||||
m_rows[role.ID] = row;
|
||||
m_list.add(*row);
|
||||
}
|
||||
|
||||
void GuildSettingsMembersPaneRoles::OnRoleToggle(Snowflake role_id, bool new_set) {
|
||||
auto row = m_rows.at(role_id);
|
||||
row->SetToggleable(false);
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
auto cb = [this, new_set, role_id, row](bool success) {
|
||||
if (!success) { // undo
|
||||
if (new_set)
|
||||
m_set_role_ids.erase(role_id);
|
||||
else
|
||||
m_set_role_ids.insert(role_id);
|
||||
} else
|
||||
row->SetChecked(new_set);
|
||||
|
||||
row->SetToggleable(true);
|
||||
};
|
||||
|
||||
if (new_set)
|
||||
m_set_role_ids.insert(role_id);
|
||||
else
|
||||
m_set_role_ids.erase(role_id);
|
||||
|
||||
// hack to prevent cb from being called if SetRoles is called before callback completion
|
||||
sigc::signal<void, bool> tmp;
|
||||
m_update_connection.push_back(tmp.connect(std::move(cb)));
|
||||
const auto tmp_cb = [this, tmp = std::move(tmp)](DiscordError code) { tmp.emit(code == DiscordError::NONE); };
|
||||
discord.SetMemberRoles(GuildID, UserID, m_set_role_ids.begin(), m_set_role_ids.end(), sigc::track_obj(tmp_cb, *this));
|
||||
}
|
||||
|
||||
void GuildSettingsMembersPaneRoles::OnRoleCreate(Snowflake guild_id, Snowflake role_id) {
|
||||
if (guild_id != GuildID) return;
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto self_id = discord.GetUserData().ID;
|
||||
const bool can_modify = discord.HasGuildPermission(self_id, GuildID, Permission::MANAGE_ROLES);
|
||||
const auto role = *discord.GetRole(role_id);
|
||||
CreateRow(can_modify, role, discord.GetGuild(guild_id)->OwnerID == self_id);
|
||||
}
|
||||
|
||||
void GuildSettingsMembersPaneRoles::OnRoleUpdate(Snowflake guild_id, Snowflake role_id) {
|
||||
if (guild_id != GuildID) return;
|
||||
auto role = *Abaddon::Get().GetDiscordClient().GetRole(role_id);
|
||||
m_rows.at(role_id)->UpdateRoleData(role);
|
||||
m_list.invalidate_sort();
|
||||
}
|
||||
|
||||
void GuildSettingsMembersPaneRoles::OnRoleDelete(Snowflake guild_id, Snowflake role_id) {
|
||||
if (guild_id != GuildID) return;
|
||||
delete m_rows.at(role_id);
|
||||
}
|
||||
|
||||
GuildSettingsMembersPaneRolesItem::GuildSettingsMembersPaneRolesItem(bool sensitive, const RoleData &role)
|
||||
: RoleID(role.ID) {
|
||||
UpdateRoleData(role);
|
||||
|
||||
m_main.set_hexpand(true);
|
||||
m_main.set_vexpand(true);
|
||||
|
||||
const auto cb = [this](GdkEventButton *event) -> bool {
|
||||
if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_PRIMARY) {
|
||||
m_signal_role_click.emit(RoleID, !m_check.get_active());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
m_check.signal_button_press_event().connect(cb, false);
|
||||
|
||||
m_desired_sensitivity = sensitive;
|
||||
ComputeSensitivity();
|
||||
|
||||
m_check.set_margin_start(5);
|
||||
m_label.set_margin_start(5);
|
||||
|
||||
m_main.add(m_check);
|
||||
m_main.add(m_label);
|
||||
add(m_main);
|
||||
m_check.show();
|
||||
m_label.show();
|
||||
m_main.show();
|
||||
}
|
||||
|
||||
void GuildSettingsMembersPaneRolesItem::SetChecked(bool checked) {
|
||||
m_check.set_active(checked);
|
||||
}
|
||||
|
||||
void GuildSettingsMembersPaneRolesItem::SetToggleable(bool toggleable) {
|
||||
m_desired_sensitivity = toggleable;
|
||||
ComputeSensitivity();
|
||||
}
|
||||
|
||||
void GuildSettingsMembersPaneRolesItem::UpdateRoleData(const RoleData &role) {
|
||||
m_role = role;
|
||||
Position = role.Position;
|
||||
UpdateLabel();
|
||||
}
|
||||
|
||||
void GuildSettingsMembersPaneRolesItem::UpdateLabel() {
|
||||
if (m_role.Color)
|
||||
m_label.set_markup("<span color='#" + IntToCSSColor(m_role.Color) + "'>" + Glib::Markup::escape_text(m_role.Name) + "</span>");
|
||||
else
|
||||
m_label.set_text(m_role.Name);
|
||||
}
|
||||
|
||||
void GuildSettingsMembersPaneRolesItem::ComputeSensitivity() {
|
||||
if (m_role.IsManaged) {
|
||||
m_check.set_sensitive(false);
|
||||
return;
|
||||
}
|
||||
m_check.set_sensitive(m_desired_sensitivity);
|
||||
}
|
||||
|
||||
GuildSettingsMembersPaneRolesItem::type_signal_role_click GuildSettingsMembersPaneRolesItem::signal_role_click() {
|
||||
return m_signal_role_click;
|
||||
}
|
||||
135
src/windows/guildsettings/memberspane.hpp
Normal file
135
src/windows/guildsettings/memberspane.hpp
Normal file
@@ -0,0 +1,135 @@
|
||||
#pragma once
|
||||
#include <unordered_set>
|
||||
#include <gtkmm.h>
|
||||
#include "discord/member.hpp"
|
||||
#include "discord/guild.hpp"
|
||||
#include "components/lazyimage.hpp"
|
||||
|
||||
class GuildSettingsMembersPaneRolesItem : public Gtk::ListBoxRow {
|
||||
public:
|
||||
GuildSettingsMembersPaneRolesItem(bool sensitive, const RoleData &role);
|
||||
void SetChecked(bool checked);
|
||||
void SetToggleable(bool toggleable);
|
||||
void UpdateRoleData(const RoleData &role);
|
||||
|
||||
Snowflake RoleID;
|
||||
int Position;
|
||||
|
||||
private:
|
||||
void UpdateLabel();
|
||||
void ComputeSensitivity();
|
||||
bool m_desired_sensitivity = true;
|
||||
|
||||
RoleData m_role;
|
||||
|
||||
Gtk::Box m_main;
|
||||
Gtk::CheckButton m_check;
|
||||
Gtk::Label m_label;
|
||||
|
||||
// own thing so we can stop it from actually changing
|
||||
typedef sigc::signal<void, Snowflake, bool> type_signal_role_click;
|
||||
|
||||
type_signal_role_click m_signal_role_click;
|
||||
|
||||
public:
|
||||
type_signal_role_click signal_role_click();
|
||||
};
|
||||
|
||||
class GuildSettingsMembersPaneRoles : public Gtk::ScrolledWindow {
|
||||
public:
|
||||
GuildSettingsMembersPaneRoles(Snowflake guild_id);
|
||||
|
||||
void SetRoles(Snowflake user_id, const std::vector<Snowflake> &roles, bool is_owner);
|
||||
|
||||
private:
|
||||
void CreateRow(bool has_manage_roles, const RoleData &role, bool is_owner);
|
||||
|
||||
void OnRoleToggle(Snowflake role_id, bool new_set);
|
||||
|
||||
void OnRoleCreate(Snowflake guild_id, Snowflake role_id);
|
||||
void OnRoleUpdate(Snowflake guild_id, Snowflake role_id);
|
||||
void OnRoleDelete(Snowflake guild_id, Snowflake role_id);
|
||||
|
||||
int m_hoisted_position = 0;
|
||||
|
||||
std::vector<sigc::connection> m_update_connection;
|
||||
|
||||
std::unordered_set<Snowflake> m_set_role_ids;
|
||||
|
||||
Snowflake GuildID;
|
||||
Snowflake UserID;
|
||||
|
||||
Gtk::ListBox m_list;
|
||||
|
||||
std::unordered_map<Snowflake, GuildSettingsMembersPaneRolesItem *> m_rows;
|
||||
};
|
||||
|
||||
class GuildSettingsMembersPaneInfo : public Gtk::ScrolledWindow {
|
||||
public:
|
||||
GuildSettingsMembersPaneInfo(Snowflake guild_id);
|
||||
|
||||
void SetUser(Snowflake user_id);
|
||||
|
||||
private:
|
||||
Snowflake GuildID;
|
||||
Snowflake UserID;
|
||||
|
||||
Gtk::Label m_bot;
|
||||
Gtk::Label m_id;
|
||||
Gtk::Label m_created;
|
||||
Gtk::Label m_joined;
|
||||
Gtk::Label m_nickname;
|
||||
Gtk::Label m_boosting;
|
||||
GuildSettingsMembersPaneRoles m_roles;
|
||||
Gtk::Box m_box;
|
||||
};
|
||||
|
||||
class GuildSettingsMembersPaneMembers : public Gtk::Box {
|
||||
public:
|
||||
GuildSettingsMembersPaneMembers(Snowflake id);
|
||||
|
||||
private:
|
||||
Snowflake GuildID;
|
||||
|
||||
Gtk::Entry m_search;
|
||||
Gtk::ScrolledWindow m_list_scroll;
|
||||
Gtk::ListBox m_list;
|
||||
|
||||
typedef sigc::signal<void, Snowflake> type_signal_member_select;
|
||||
type_signal_member_select m_signal_member_select;
|
||||
|
||||
public:
|
||||
type_signal_member_select signal_member_select();
|
||||
};
|
||||
|
||||
class GuildSettingsMembersListItem : public Gtk::ListBoxRow {
|
||||
public:
|
||||
GuildSettingsMembersListItem(const GuildData &guild, const GuildMember &member);
|
||||
|
||||
Glib::ustring DisplayTerm;
|
||||
|
||||
Snowflake UserID;
|
||||
Snowflake GuildID;
|
||||
|
||||
private:
|
||||
void UpdateColor();
|
||||
|
||||
Gtk::EventBox m_ev;
|
||||
LazyImage m_avatar;
|
||||
Gtk::Label m_name;
|
||||
Gtk::Box m_main;
|
||||
Gtk::Image *m_crown = nullptr;
|
||||
};
|
||||
|
||||
class GuildSettingsMembersPane : public Gtk::Box {
|
||||
public:
|
||||
GuildSettingsMembersPane(Snowflake id);
|
||||
|
||||
private:
|
||||
Snowflake GuildID;
|
||||
|
||||
Gtk::Box m_layout;
|
||||
Gtk::Label m_note;
|
||||
GuildSettingsMembersPaneMembers m_member_list;
|
||||
GuildSettingsMembersPaneInfo m_member_info;
|
||||
};
|
||||
419
src/windows/guildsettings/rolespane.cpp
Normal file
419
src/windows/guildsettings/rolespane.cpp
Normal file
@@ -0,0 +1,419 @@
|
||||
#include "rolespane.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
GuildSettingsRolesPane::GuildSettingsRolesPane(Snowflake id)
|
||||
: Gtk::Box(Gtk::ORIENTATION_HORIZONTAL)
|
||||
, GuildID(id)
|
||||
, m_roles_list(id)
|
||||
, m_roles_perms(id) {
|
||||
set_name("guild-roles-pane");
|
||||
set_hexpand(true);
|
||||
set_vexpand(true);
|
||||
|
||||
m_roles_list.signal_role_select().connect(sigc::mem_fun(*this, &GuildSettingsRolesPane::OnRoleSelect));
|
||||
|
||||
m_roles_perms.set_sensitive(false);
|
||||
|
||||
m_layout.set_homogeneous(true);
|
||||
m_layout.add(m_roles_list);
|
||||
m_layout.add(m_roles_perms);
|
||||
add(m_layout);
|
||||
|
||||
m_roles_list.show();
|
||||
m_roles_perms.show();
|
||||
m_layout.show();
|
||||
}
|
||||
|
||||
void GuildSettingsRolesPane::OnRoleSelect(Snowflake role_id) {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto role = *discord.GetRole(role_id);
|
||||
m_roles_perms.SetRole(role);
|
||||
m_roles_perms.set_sensitive(discord.CanModifyRole(GuildID, role_id));
|
||||
}
|
||||
|
||||
static std::vector<Gtk::TargetEntry> g_target_entries = {
|
||||
Gtk::TargetEntry("GTK_LIST_ROLES_ROW", Gtk::TARGET_SAME_APP, 0)
|
||||
};
|
||||
|
||||
GuildSettingsRolesPaneRoles::GuildSettingsRolesPaneRoles(Snowflake guild_id)
|
||||
: Gtk::Box(Gtk::ORIENTATION_VERTICAL)
|
||||
, GuildID(guild_id) {
|
||||
m_list.get_style_context()->add_class("guild-roles-pane-list");
|
||||
|
||||
m_list_scroll.set_hexpand(true);
|
||||
m_list_scroll.set_vexpand(true);
|
||||
m_list_scroll.set_propagate_natural_height(true);
|
||||
m_list_scroll.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
|
||||
|
||||
m_list.set_selection_mode(Gtk::SELECTION_SINGLE);
|
||||
m_list.signal_row_selected().connect([this](Gtk::ListBoxRow *selected_) {
|
||||
if (auto *selected = dynamic_cast<GuildSettingsRolesPaneRolesListItem *>(selected_))
|
||||
m_signal_role_select.emit(selected->RoleID);
|
||||
});
|
||||
|
||||
m_list.set_focus_vadjustment(m_list_scroll.get_vadjustment());
|
||||
m_list.signal_on_drop().connect([this](Gtk::ListBoxRow *row_, int new_index) -> bool {
|
||||
if (auto *row = dynamic_cast<GuildSettingsRolesPaneRolesListItem *>(row_)) {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto num_rows = m_list.get_children().size();
|
||||
const auto new_pos = num_rows - new_index - 1;
|
||||
if (row->RoleID == GuildID) return true; // moving role @everyone
|
||||
if (static_cast<size_t>(new_index) == num_rows) return true; // trying to move row below @everyone
|
||||
// make sure it wont modify a neighbor role u dont have perms to modify
|
||||
if (!discord.CanModifyRole(GuildID, row->RoleID)) return false;
|
||||
const auto cb = [this](DiscordError code) {
|
||||
if (code != DiscordError::NONE) {
|
||||
Gtk::MessageDialog dlg("Failed to set role position", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER_ON_PARENT);
|
||||
dlg.run();
|
||||
}
|
||||
};
|
||||
discord.ModifyRolePosition(GuildID, row->RoleID, new_pos, sigc::track_obj(cb, *this));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
discord.signal_role_create().connect(sigc::mem_fun(*this, &GuildSettingsRolesPaneRoles::OnRoleCreate));
|
||||
discord.signal_role_delete().connect(sigc::mem_fun(*this, &GuildSettingsRolesPaneRoles::OnRoleDelete));
|
||||
|
||||
const auto guild = *discord.GetGuild(GuildID);
|
||||
const auto roles = guild.FetchRoles();
|
||||
const bool can_modify = discord.HasGuildPermission(discord.GetUserData().ID, GuildID, Permission::MANAGE_ROLES);
|
||||
for (const auto &role : roles) {
|
||||
auto *row = Gtk::manage(new GuildSettingsRolesPaneRolesListItem(guild, role));
|
||||
row->drag_source_set(g_target_entries, Gdk::BUTTON1_MASK, Gdk::ACTION_MOVE);
|
||||
row->set_margin_start(5);
|
||||
row->set_halign(Gtk::ALIGN_FILL);
|
||||
row->show();
|
||||
m_rows[role.ID] = row;
|
||||
if (can_modify)
|
||||
m_list.add_draggable(row);
|
||||
else
|
||||
m_list.add(*row);
|
||||
}
|
||||
|
||||
m_list.set_sort_func([this](Gtk::ListBoxRow *rowa_, Gtk::ListBoxRow *rowb_) -> int {
|
||||
auto *rowa = dynamic_cast<GuildSettingsRolesPaneRolesListItem *>(rowa_);
|
||||
auto *rowb = dynamic_cast<GuildSettingsRolesPaneRolesListItem *>(rowb_);
|
||||
return rowb->Position - rowa->Position;
|
||||
});
|
||||
m_list.invalidate_sort();
|
||||
|
||||
m_list.set_filter_func([this](Gtk::ListBoxRow *row_) -> bool {
|
||||
const auto search_term = m_search.get_text();
|
||||
if (search_term.size() == 0) return true;
|
||||
if (auto *row = dynamic_cast<GuildSettingsRolesPaneRolesListItem *>(row_))
|
||||
return StringContainsCaseless(row->DisplayTerm, m_search.get_text());
|
||||
return true;
|
||||
});
|
||||
|
||||
m_search.set_placeholder_text("Filter");
|
||||
m_search.signal_changed().connect([this] {
|
||||
m_list.invalidate_filter();
|
||||
});
|
||||
|
||||
m_list_scroll.add(m_list);
|
||||
add(m_search);
|
||||
add(m_list_scroll);
|
||||
|
||||
m_search.show();
|
||||
m_list.show();
|
||||
m_list_scroll.show();
|
||||
}
|
||||
|
||||
void GuildSettingsRolesPaneRoles::OnRoleCreate(Snowflake guild_id, Snowflake role_id) {
|
||||
if (guild_id != GuildID) return;
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const bool can_modify = discord.HasGuildPermission(discord.GetUserData().ID, guild_id, Permission::MANAGE_ROLES);
|
||||
const auto guild = *discord.GetGuild(guild_id);
|
||||
const auto role = *discord.GetRole(role_id);
|
||||
auto *row = Gtk::manage(new GuildSettingsRolesPaneRolesListItem(guild, role));
|
||||
row->show();
|
||||
m_rows[role_id] = row;
|
||||
if (can_modify)
|
||||
m_list.add_draggable(row);
|
||||
else
|
||||
m_list.add(*row);
|
||||
}
|
||||
|
||||
void GuildSettingsRolesPaneRoles::OnRoleDelete(Snowflake guild_id, Snowflake role_id) {
|
||||
if (guild_id != GuildID) return;
|
||||
auto it = m_rows.find(role_id);
|
||||
delete it->second;
|
||||
m_rows.erase(it);
|
||||
}
|
||||
|
||||
GuildSettingsRolesPaneRoles::type_signal_role_select GuildSettingsRolesPaneRoles::signal_role_select() {
|
||||
return m_signal_role_select;
|
||||
}
|
||||
|
||||
GuildSettingsRolesPaneRolesListItem::GuildSettingsRolesPaneRolesListItem(const GuildData &guild, const RoleData &role)
|
||||
: GuildID(guild.ID)
|
||||
, RoleID(role.ID)
|
||||
, Position(role.Position) {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
|
||||
set_hexpand(true);
|
||||
|
||||
UpdateItem(role);
|
||||
|
||||
discord.signal_role_update().connect(sigc::mem_fun(*this, &GuildSettingsRolesPaneRolesListItem::OnRoleUpdate));
|
||||
|
||||
m_name.set_ellipsize(Pango::ELLIPSIZE_END);
|
||||
|
||||
m_ev.set_halign(Gtk::ALIGN_START);
|
||||
m_ev.add(m_name);
|
||||
add(m_ev);
|
||||
|
||||
m_name.show();
|
||||
m_ev.show();
|
||||
}
|
||||
|
||||
void GuildSettingsRolesPaneRolesListItem::UpdateItem(const RoleData &role) {
|
||||
DisplayTerm = role.Name;
|
||||
|
||||
if (role.Color != 0)
|
||||
m_name.set_markup("<span color='#" + IntToCSSColor(role.Color) + "'>" +
|
||||
Glib::Markup::escape_text(role.Name) +
|
||||
"</span>");
|
||||
else
|
||||
m_name.set_text(role.Name);
|
||||
}
|
||||
|
||||
void GuildSettingsRolesPaneRolesListItem::OnRoleUpdate(Snowflake guild_id, Snowflake role_id) {
|
||||
if (guild_id != GuildID || role_id != RoleID) return;
|
||||
const auto role = Abaddon::Get().GetDiscordClient().GetRole(RoleID);
|
||||
if (!role.has_value()) return;
|
||||
Position = role->Position;
|
||||
UpdateItem(*role);
|
||||
changed();
|
||||
}
|
||||
|
||||
GuildSettingsRolesPaneInfo::GuildSettingsRolesPaneInfo(Snowflake guild_id)
|
||||
: GuildID(guild_id)
|
||||
, m_layout(Gtk::ORIENTATION_VERTICAL)
|
||||
, m_meta(Gtk::ORIENTATION_HORIZONTAL) {
|
||||
set_propagate_natural_height(true);
|
||||
set_propagate_natural_width(true);
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
discord.signal_role_update().connect(sigc::mem_fun(*this, &GuildSettingsRolesPaneInfo::OnRoleUpdate));
|
||||
|
||||
const auto cb = [this](GdkEventKey *e) -> bool {
|
||||
if (e->keyval == GDK_KEY_Return)
|
||||
UpdateRoleName();
|
||||
return false;
|
||||
};
|
||||
m_role_name.signal_key_press_event().connect(cb, false);
|
||||
|
||||
m_role_name.set_tooltip_text("Press enter to submit");
|
||||
|
||||
m_role_name.set_max_length(100);
|
||||
|
||||
m_role_name.set_margin_top(5);
|
||||
m_role_name.set_margin_bottom(5);
|
||||
m_role_name.set_margin_start(5);
|
||||
m_role_name.set_margin_end(5);
|
||||
|
||||
m_color_button.set_margin_top(5);
|
||||
m_color_button.set_margin_bottom(5);
|
||||
m_color_button.set_margin_start(5);
|
||||
m_color_button.set_margin_end(5);
|
||||
|
||||
m_color_button.signal_color_set().connect([this, &discord]() {
|
||||
const auto color = m_color_button.get_rgba();
|
||||
const auto cb = [this, &discord](DiscordError code) {
|
||||
if (code != DiscordError::NONE) {
|
||||
m_color_button.set_rgba(IntToRGBA(discord.GetRole(RoleID)->Color));
|
||||
Gtk::MessageDialog dlg("Failed to set role color", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER_ON_PARENT);
|
||||
dlg.run();
|
||||
}
|
||||
};
|
||||
discord.ModifyRoleColor(GuildID, RoleID, color, cb);
|
||||
});
|
||||
|
||||
int left_ypos = 0;
|
||||
int right_ypos = 0;
|
||||
|
||||
const int LEFT = 0;
|
||||
const int RIGHT = 1;
|
||||
|
||||
auto add_perms = [&](const std::string &label, int side, const std::initializer_list<Permission> &perms) {
|
||||
int &pos = side == LEFT ? left_ypos : right_ypos;
|
||||
auto *header = Gtk::manage(new Gtk::Label(label));
|
||||
header->show();
|
||||
m_grid.attach(*header, side, pos++, 1, 1);
|
||||
for (const auto perm : perms) {
|
||||
auto *btn = Gtk::manage(new GuildSettingsRolesPanePermItem(perm));
|
||||
btn->signal_permission_click().connect(sigc::mem_fun(*this, &GuildSettingsRolesPaneInfo::OnPermissionToggle));
|
||||
m_perm_items[perm] = btn;
|
||||
btn->show();
|
||||
m_grid.attach(*btn, side, pos++, 1, 1);
|
||||
}
|
||||
pos++;
|
||||
};
|
||||
|
||||
// fuck you clang-format you suck
|
||||
// clang-format off
|
||||
add_perms("General", LEFT, {
|
||||
Permission::VIEW_CHANNEL,
|
||||
Permission::MANAGE_CHANNELS,
|
||||
Permission::MANAGE_ROLES,
|
||||
Permission::MANAGE_EMOJIS,
|
||||
Permission::VIEW_AUDIT_LOG,
|
||||
Permission::VIEW_GUILD_INSIGHTS,
|
||||
Permission::MANAGE_WEBHOOKS,
|
||||
Permission::MANAGE_GUILD });
|
||||
|
||||
add_perms("Membership", LEFT, {
|
||||
Permission::CREATE_INSTANT_INVITE,
|
||||
Permission::CHANGE_NICKNAME,
|
||||
Permission::MANAGE_NICKNAMES,
|
||||
Permission::KICK_MEMBERS,
|
||||
Permission::BAN_MEMBERS });
|
||||
|
||||
add_perms("Text Channels", RIGHT, {
|
||||
Permission::SEND_MESSAGES,
|
||||
Permission::USE_PUBLIC_THREADS,
|
||||
Permission::USE_PRIVATE_THREADS,
|
||||
Permission::EMBED_LINKS,
|
||||
Permission::ATTACH_FILES,
|
||||
Permission::ADD_REACTIONS,
|
||||
Permission::USE_EXTERNAL_EMOJIS,
|
||||
Permission::MENTION_EVERYONE,
|
||||
Permission::MANAGE_MESSAGES,
|
||||
Permission::MANAGE_THREADS,
|
||||
Permission::READ_MESSAGE_HISTORY,
|
||||
Permission::SEND_TTS_MESSAGES,
|
||||
Permission::USE_SLASH_COMMANDS });
|
||||
|
||||
add_perms("Voice Channels", RIGHT, {
|
||||
Permission::CONNECT,
|
||||
Permission::SPEAK,
|
||||
Permission::STREAM,
|
||||
Permission::USE_VAD,
|
||||
Permission::PRIORITY_SPEAKER,
|
||||
Permission::MUTE_MEMBERS,
|
||||
Permission::DEAFEN_MEMBERS,
|
||||
Permission::MOVE_MEMBERS });
|
||||
|
||||
add_perms("Advanced", LEFT, { Permission::ADMINISTRATOR });
|
||||
|
||||
// clang-format on
|
||||
|
||||
m_meta.add(m_role_name);
|
||||
m_meta.add(m_color_button);
|
||||
m_layout.add(m_meta);
|
||||
m_layout.add(m_grid);
|
||||
add(m_layout);
|
||||
m_meta.show();
|
||||
m_color_button.show();
|
||||
m_role_name.show();
|
||||
m_layout.show();
|
||||
m_grid.show();
|
||||
}
|
||||
|
||||
void GuildSettingsRolesPaneInfo::SetRole(const RoleData &role) {
|
||||
for (auto it = m_update_connections.begin(); it != m_update_connections.end();) {
|
||||
it->disconnect();
|
||||
it = m_update_connections.erase(it);
|
||||
}
|
||||
|
||||
if (role.Color != 0) {
|
||||
m_color_button.set_rgba(IntToRGBA(role.Color));
|
||||
} else {
|
||||
static Gdk::RGBA trans;
|
||||
trans.set_alpha(0.0);
|
||||
m_color_button.set_rgba(trans);
|
||||
}
|
||||
|
||||
m_role_name.set_text(role.Name);
|
||||
|
||||
RoleID = role.ID;
|
||||
m_perms = role.Permissions;
|
||||
for (const auto [perm, btn] : m_perm_items) {
|
||||
btn->set_sensitive(true);
|
||||
btn->set_active((role.Permissions & perm) == perm);
|
||||
}
|
||||
}
|
||||
|
||||
void GuildSettingsRolesPaneInfo::OnRoleUpdate(Snowflake guild_id, Snowflake role_id) {
|
||||
if (guild_id != GuildID || role_id != RoleID) return;
|
||||
const auto role = *Abaddon::Get().GetDiscordClient().GetRole(RoleID);
|
||||
m_role_name.set_text(role.Name);
|
||||
|
||||
if (role.Color != 0) {
|
||||
m_color_button.set_rgba(IntToRGBA(role.Color));
|
||||
} else {
|
||||
static Gdk::RGBA trans;
|
||||
trans.set_alpha(0.0);
|
||||
m_color_button.set_rgba(trans);
|
||||
}
|
||||
|
||||
m_perms = role.Permissions;
|
||||
for (const auto [perm, btn] : m_perm_items)
|
||||
btn->set_active((role.Permissions & perm) == perm);
|
||||
}
|
||||
|
||||
void GuildSettingsRolesPaneInfo::OnPermissionToggle(Permission perm, bool new_set) {
|
||||
auto btn = m_perm_items.at(perm);
|
||||
btn->set_sensitive(false);
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
auto cb = [this, new_set, perm, btn](bool success) {
|
||||
if (!success) { // undo
|
||||
if (new_set)
|
||||
m_perms &= ~perm;
|
||||
else
|
||||
m_perms |= perm;
|
||||
} else
|
||||
btn->set_active(new_set);
|
||||
btn->set_sensitive(true);
|
||||
};
|
||||
|
||||
if (new_set)
|
||||
m_perms |= perm;
|
||||
else
|
||||
m_perms &= ~perm;
|
||||
|
||||
sigc::signal<void, bool> tmp;
|
||||
m_update_connections.push_back(tmp.connect(std::move(cb)));
|
||||
const auto tmp_cb = [this, tmp = std::move(tmp)](DiscordError code) { tmp.emit(code == DiscordError::NONE); };
|
||||
discord.ModifyRolePermissions(GuildID, RoleID, m_perms, sigc::track_obj(tmp_cb, *this));
|
||||
}
|
||||
|
||||
void GuildSettingsRolesPaneInfo::UpdateRoleName() {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
if (discord.GetRole(RoleID)->Name == m_role_name.get_text()) return;
|
||||
|
||||
const auto cb = [this, &discord](DiscordError code) {
|
||||
if (code != DiscordError::NONE) {
|
||||
m_role_name.set_text(discord.GetRole(RoleID)->Name);
|
||||
Gtk::MessageDialog dlg("Failed to set role name", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER_ON_PARENT);
|
||||
dlg.run();
|
||||
}
|
||||
};
|
||||
discord.ModifyRoleName(GuildID, RoleID, m_role_name.get_text(), cb);
|
||||
}
|
||||
|
||||
GuildSettingsRolesPanePermItem::GuildSettingsRolesPanePermItem(Permission perm)
|
||||
: Gtk::CheckButton(GetPermissionString(perm))
|
||||
, m_permission(perm) {
|
||||
set_tooltip_text(GetPermissionDescription(m_permission));
|
||||
|
||||
const auto cb = [this](GdkEventButton *event) -> bool {
|
||||
if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_PRIMARY) {
|
||||
m_signal_permission.emit(m_permission, !get_active());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
signal_button_press_event().connect(cb, false);
|
||||
}
|
||||
|
||||
GuildSettingsRolesPanePermItem::type_signal_permission_click GuildSettingsRolesPanePermItem::signal_permission_click() {
|
||||
return m_signal_permission;
|
||||
}
|
||||
102
src/windows/guildsettings/rolespane.hpp
Normal file
102
src/windows/guildsettings/rolespane.hpp
Normal file
@@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <unordered_map>
|
||||
#include "discord/guild.hpp"
|
||||
#include "components/draglistbox.hpp"
|
||||
|
||||
class GuildSettingsRolesPaneRolesListItem : public Gtk::ListBoxRow {
|
||||
public:
|
||||
GuildSettingsRolesPaneRolesListItem(const GuildData &guild, const RoleData &role);
|
||||
|
||||
Glib::ustring DisplayTerm;
|
||||
|
||||
Snowflake GuildID;
|
||||
Snowflake RoleID;
|
||||
int Position;
|
||||
|
||||
private:
|
||||
void UpdateItem(const RoleData &role);
|
||||
void OnRoleUpdate(Snowflake guild_id, Snowflake role_id);
|
||||
|
||||
Gtk::EventBox m_ev;
|
||||
Gtk::Label m_name;
|
||||
};
|
||||
|
||||
class GuildSettingsRolesPaneRoles : public Gtk::Box {
|
||||
public:
|
||||
GuildSettingsRolesPaneRoles(Snowflake guild_id);
|
||||
|
||||
private:
|
||||
void OnRoleCreate(Snowflake guild_id, Snowflake role_id);
|
||||
void OnRoleDelete(Snowflake guild_id, Snowflake role_id);
|
||||
|
||||
Snowflake GuildID;
|
||||
|
||||
Gtk::Entry m_search;
|
||||
Gtk::ScrolledWindow m_list_scroll;
|
||||
DragListBox m_list;
|
||||
|
||||
typedef sigc::signal<void, Snowflake /* role_id */> type_signal_role_select;
|
||||
type_signal_role_select m_signal_role_select;
|
||||
|
||||
public:
|
||||
std::unordered_map<Snowflake, GuildSettingsRolesPaneRolesListItem *> m_rows;
|
||||
type_signal_role_select signal_role_select();
|
||||
};
|
||||
|
||||
class GuildSettingsRolesPanePermItem : public Gtk::CheckButton {
|
||||
public:
|
||||
GuildSettingsRolesPanePermItem(Permission perm);
|
||||
|
||||
private:
|
||||
Permission m_permission;
|
||||
|
||||
typedef sigc::signal<void, Permission, bool> type_signal_permission_click;
|
||||
|
||||
type_signal_permission_click m_signal_permission;
|
||||
|
||||
public:
|
||||
type_signal_permission_click signal_permission_click();
|
||||
};
|
||||
|
||||
class GuildSettingsRolesPaneInfo : public Gtk::ScrolledWindow {
|
||||
public:
|
||||
GuildSettingsRolesPaneInfo(Snowflake guild_id);
|
||||
|
||||
void SetRole(const RoleData &role);
|
||||
|
||||
private:
|
||||
void OnRoleUpdate(Snowflake guild_id, Snowflake role_id);
|
||||
void OnPermissionToggle(Permission perm, bool new_set);
|
||||
|
||||
void UpdateRoleName();
|
||||
|
||||
Snowflake GuildID;
|
||||
Snowflake RoleID;
|
||||
|
||||
Permission m_perms;
|
||||
|
||||
std::vector<sigc::connection> m_update_connections;
|
||||
|
||||
Gtk::Box m_layout;
|
||||
Gtk::Box m_meta;
|
||||
Gtk::Entry m_role_name;
|
||||
Gtk::ColorButton m_color_button;
|
||||
Gtk::Grid m_grid;
|
||||
|
||||
std::unordered_map<Permission, GuildSettingsRolesPanePermItem *> m_perm_items;
|
||||
};
|
||||
|
||||
class GuildSettingsRolesPane : public Gtk::Box {
|
||||
public:
|
||||
GuildSettingsRolesPane(Snowflake id);
|
||||
|
||||
private:
|
||||
void OnRoleSelect(Snowflake role_id);
|
||||
|
||||
Snowflake GuildID;
|
||||
|
||||
Gtk::Box m_layout;
|
||||
GuildSettingsRolesPaneRoles m_roles_list;
|
||||
GuildSettingsRolesPaneInfo m_roles_perms;
|
||||
};
|
||||
76
src/windows/guildsettingswindow.cpp
Normal file
76
src/windows/guildsettingswindow.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#include "guildsettingswindow.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
GuildSettingsWindow::GuildSettingsWindow(Snowflake id)
|
||||
: m_main(Gtk::ORIENTATION_VERTICAL)
|
||||
, m_pane_info(id)
|
||||
, m_pane_members(id)
|
||||
, m_pane_roles(id)
|
||||
, m_pane_bans(id)
|
||||
, m_pane_invites(id)
|
||||
, m_pane_emojis(id)
|
||||
, m_pane_audit_log(id)
|
||||
, GuildID(id) {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto guild = *discord.GetGuild(id);
|
||||
|
||||
auto guild_update_cb = [this](Snowflake id) {
|
||||
if (id != GuildID) return;
|
||||
const auto guild = *Abaddon::Get().GetDiscordClient().GetGuild(id);
|
||||
set_title(guild.Name);
|
||||
if (guild.HasIcon())
|
||||
Abaddon::Get().GetImageManager().LoadFromURL(guild.GetIconURL(), sigc::mem_fun(*this, &GuildSettingsWindow::set_icon));
|
||||
};
|
||||
discord.signal_guild_update().connect(sigc::track_obj(guild_update_cb, *this));
|
||||
|
||||
set_name("guild-settings");
|
||||
set_default_size(800, 600);
|
||||
set_title(guild.Name);
|
||||
set_position(Gtk::WIN_POS_CENTER);
|
||||
get_style_context()->add_class("app-window");
|
||||
get_style_context()->add_class("app-popup");
|
||||
get_style_context()->add_class("guild-settings-window");
|
||||
|
||||
if (guild.HasIcon()) {
|
||||
Abaddon::Get().GetImageManager().LoadFromURL(guild.GetIconURL(), sigc::mem_fun(*this, &GuildSettingsWindow::set_icon));
|
||||
}
|
||||
|
||||
m_switcher.set_stack(m_stack);
|
||||
m_switcher.set_halign(Gtk::ALIGN_CENTER);
|
||||
m_switcher.set_hexpand(true);
|
||||
m_switcher.set_margin_top(10);
|
||||
m_switcher.show();
|
||||
|
||||
m_pane_info.show();
|
||||
m_pane_members.show();
|
||||
m_pane_roles.show();
|
||||
m_pane_bans.show();
|
||||
m_pane_invites.show();
|
||||
m_pane_emojis.show();
|
||||
m_pane_audit_log.show();
|
||||
|
||||
m_stack.set_transition_duration(100);
|
||||
m_stack.set_transition_type(Gtk::STACK_TRANSITION_TYPE_CROSSFADE);
|
||||
m_stack.set_margin_top(10);
|
||||
m_stack.set_margin_bottom(10);
|
||||
m_stack.set_margin_left(10);
|
||||
m_stack.set_margin_right(10);
|
||||
|
||||
const auto self_id = discord.GetUserData().ID;
|
||||
|
||||
m_stack.add(m_pane_info, "info", "Info");
|
||||
m_stack.add(m_pane_members, "members", "Members");
|
||||
m_stack.add(m_pane_roles, "roles", "Roles");
|
||||
m_stack.add(m_pane_bans, "bans", "Bans");
|
||||
if (discord.HasGuildPermission(self_id, GuildID, Permission::MANAGE_GUILD))
|
||||
m_stack.add(m_pane_invites, "invites", "Invites");
|
||||
m_stack.add(m_pane_emojis, "emojis", "Emojis");
|
||||
if (discord.HasGuildPermission(self_id, GuildID, Permission::VIEW_AUDIT_LOG))
|
||||
m_stack.add(m_pane_audit_log, "audit-log", "Audit Log");
|
||||
m_stack.show();
|
||||
|
||||
m_main.add(m_switcher);
|
||||
m_main.add(m_stack);
|
||||
m_main.show();
|
||||
add(m_main);
|
||||
}
|
||||
30
src/windows/guildsettingswindow.hpp
Normal file
30
src/windows/guildsettingswindow.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/snowflake.hpp"
|
||||
#include "guildsettings/infopane.hpp"
|
||||
#include "guildsettings/banspane.hpp"
|
||||
#include "guildsettings/invitespane.hpp"
|
||||
#include "guildsettings/auditlogpane.hpp"
|
||||
#include "guildsettings/memberspane.hpp"
|
||||
#include "guildsettings/rolespane.hpp"
|
||||
#include "guildsettings/emojispane.hpp"
|
||||
|
||||
class GuildSettingsWindow : public Gtk::Window {
|
||||
public:
|
||||
GuildSettingsWindow(Snowflake id);
|
||||
|
||||
private:
|
||||
Gtk::Box m_main;
|
||||
Gtk::Stack m_stack;
|
||||
Gtk::StackSwitcher m_switcher;
|
||||
|
||||
GuildSettingsInfoPane m_pane_info;
|
||||
GuildSettingsMembersPane m_pane_members;
|
||||
GuildSettingsRolesPane m_pane_roles;
|
||||
GuildSettingsBansPane m_pane_bans;
|
||||
GuildSettingsInvitesPane m_pane_invites;
|
||||
GuildSettingsEmojisPane m_pane_emojis;
|
||||
GuildSettingsAuditLogPane m_pane_audit_log;
|
||||
|
||||
Snowflake GuildID;
|
||||
};
|
||||
308
src/windows/mainwindow.cpp
Normal file
308
src/windows/mainwindow.cpp
Normal file
@@ -0,0 +1,308 @@
|
||||
#include "mainwindow.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
MainWindow::MainWindow()
|
||||
: m_main_box(Gtk::ORIENTATION_VERTICAL)
|
||||
, m_content_box(Gtk::ORIENTATION_HORIZONTAL)
|
||||
, m_chan_content_paned(Gtk::ORIENTATION_HORIZONTAL)
|
||||
, m_content_members_paned(Gtk::ORIENTATION_HORIZONTAL) {
|
||||
set_default_size(1200, 800);
|
||||
get_style_context()->add_class("app-window");
|
||||
|
||||
m_menu_discord.set_label("Discord");
|
||||
m_menu_discord.set_submenu(m_menu_discord_sub);
|
||||
m_menu_discord_connect.set_label("Connect");
|
||||
m_menu_discord_connect.set_sensitive(false);
|
||||
m_menu_discord_disconnect.set_label("Disconnect");
|
||||
m_menu_discord_disconnect.set_sensitive(false);
|
||||
m_menu_discord_set_token.set_label("Set Token");
|
||||
m_menu_discord_join_guild.set_label("Accept Invite");
|
||||
m_menu_discord_join_guild.set_sensitive(false);
|
||||
m_menu_discord_set_status.set_label("Set Status");
|
||||
m_menu_discord_set_status.set_sensitive(false);
|
||||
m_menu_discord_add_recipient.set_label("Add user to DM");
|
||||
m_menu_discord_sub.append(m_menu_discord_connect);
|
||||
m_menu_discord_sub.append(m_menu_discord_disconnect);
|
||||
m_menu_discord_sub.append(m_menu_discord_set_token);
|
||||
m_menu_discord_sub.append(m_menu_discord_join_guild);
|
||||
m_menu_discord_sub.append(m_menu_discord_set_status);
|
||||
m_menu_discord_sub.append(m_menu_discord_add_recipient);
|
||||
m_menu_discord_sub.signal_popped_up().connect(sigc::mem_fun(*this, &MainWindow::OnDiscordSubmenuPopup)); // this gets called twice for some reason
|
||||
m_menu_discord.set_submenu(m_menu_discord_sub);
|
||||
|
||||
m_menu_file.set_label("File");
|
||||
m_menu_file.set_submenu(m_menu_file_sub);
|
||||
m_menu_file_reload_css.set_label("Reload CSS");
|
||||
m_menu_file_clear_cache.set_label("Clear file cache");
|
||||
m_menu_file_sub.append(m_menu_file_reload_css);
|
||||
m_menu_file_sub.append(m_menu_file_clear_cache);
|
||||
|
||||
m_menu_view.set_label("View");
|
||||
m_menu_view.set_submenu(m_menu_view_sub);
|
||||
m_menu_view_friends.set_label("Friends");
|
||||
m_menu_view_pins.set_label("Pins");
|
||||
m_menu_view_threads.set_label("Threads");
|
||||
m_menu_view_sub.append(m_menu_view_friends);
|
||||
m_menu_view_sub.append(m_menu_view_pins);
|
||||
m_menu_view_sub.append(m_menu_view_threads);
|
||||
m_menu_view_sub.signal_popped_up().connect(sigc::mem_fun(*this, &MainWindow::OnViewSubmenuPopup));
|
||||
|
||||
m_menu_bar.append(m_menu_file);
|
||||
m_menu_bar.append(m_menu_discord);
|
||||
m_menu_bar.append(m_menu_view);
|
||||
m_menu_bar.show_all();
|
||||
|
||||
m_menu_discord_connect.signal_activate().connect([this] {
|
||||
m_signal_action_connect.emit();
|
||||
});
|
||||
|
||||
m_menu_discord_disconnect.signal_activate().connect([this] {
|
||||
m_signal_action_disconnect.emit();
|
||||
});
|
||||
|
||||
m_menu_discord_set_token.signal_activate().connect([this] {
|
||||
m_signal_action_set_token.emit();
|
||||
});
|
||||
|
||||
m_menu_discord_join_guild.signal_activate().connect([this] {
|
||||
m_signal_action_join_guild.emit();
|
||||
});
|
||||
|
||||
m_menu_file_reload_css.signal_activate().connect([this] {
|
||||
m_signal_action_reload_css.emit();
|
||||
});
|
||||
|
||||
m_menu_discord_set_status.signal_activate().connect([this] {
|
||||
m_signal_action_set_status.emit();
|
||||
});
|
||||
|
||||
m_menu_file_clear_cache.signal_activate().connect([this] {
|
||||
Abaddon::Get().GetImageManager().ClearCache();
|
||||
});
|
||||
|
||||
m_menu_discord_add_recipient.signal_activate().connect([this] {
|
||||
m_signal_action_add_recipient.emit(GetChatActiveChannel());
|
||||
});
|
||||
|
||||
m_menu_view_friends.signal_activate().connect([this] {
|
||||
UpdateChatActiveChannel(Snowflake::Invalid);
|
||||
m_members.UpdateMemberList();
|
||||
m_content_stack.set_visible_child("friends");
|
||||
});
|
||||
|
||||
m_menu_view_pins.signal_activate().connect([this] {
|
||||
m_signal_action_view_pins.emit(GetChatActiveChannel());
|
||||
});
|
||||
|
||||
m_menu_view_threads.signal_activate().connect([this] {
|
||||
m_signal_action_view_threads.emit(GetChatActiveChannel());
|
||||
});
|
||||
|
||||
m_content_box.set_hexpand(true);
|
||||
m_content_box.set_vexpand(true);
|
||||
m_content_box.show();
|
||||
|
||||
m_main_box.add(m_menu_bar);
|
||||
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);
|
||||
chat->set_hexpand(true);
|
||||
chat->show();
|
||||
|
||||
m_channel_list.set_vexpand(true);
|
||||
m_channel_list.set_size_request(-1, -1);
|
||||
m_channel_list.show();
|
||||
|
||||
member_list->set_vexpand(true);
|
||||
member_list->show();
|
||||
|
||||
m_friends.set_vexpand(true);
|
||||
m_friends.set_hexpand(true);
|
||||
m_friends.show();
|
||||
|
||||
m_content_stack.add(*chat, "chat");
|
||||
m_content_stack.add(m_friends, "friends");
|
||||
m_content_stack.set_vexpand(true);
|
||||
m_content_stack.set_hexpand(true);
|
||||
m_content_stack.set_visible_child("chat");
|
||||
m_content_stack.show();
|
||||
|
||||
m_chan_content_paned.pack1(m_channel_list);
|
||||
m_chan_content_paned.pack2(m_content_members_paned);
|
||||
m_chan_content_paned.child_property_shrink(m_channel_list) = false;
|
||||
m_chan_content_paned.child_property_resize(m_channel_list) = false;
|
||||
m_chan_content_paned.set_position(200);
|
||||
m_chan_content_paned.show();
|
||||
m_content_box.add(m_chan_content_paned);
|
||||
|
||||
m_content_members_paned.pack1(m_content_stack);
|
||||
m_content_members_paned.pack2(*member_list);
|
||||
m_content_members_paned.child_property_shrink(*member_list) = false;
|
||||
m_content_members_paned.child_property_resize(*member_list) = false;
|
||||
int w, h;
|
||||
get_default_size(w, h); // :s
|
||||
m_content_members_paned.set_position(w - m_chan_content_paned.get_position() - 150);
|
||||
m_content_members_paned.show();
|
||||
|
||||
add(m_main_box);
|
||||
}
|
||||
|
||||
void MainWindow::UpdateComponents() {
|
||||
bool discord_active = Abaddon::Get().IsDiscordActive();
|
||||
|
||||
if (!discord_active) {
|
||||
m_chat.Clear();
|
||||
m_members.Clear();
|
||||
} else {
|
||||
m_members.UpdateMemberList();
|
||||
}
|
||||
UpdateChannelListing();
|
||||
}
|
||||
|
||||
void MainWindow::UpdateMembers() {
|
||||
m_members.UpdateMemberList();
|
||||
}
|
||||
|
||||
void MainWindow::UpdateChannelListing() {
|
||||
m_channel_list.UpdateListing();
|
||||
}
|
||||
|
||||
void MainWindow::UpdateChatWindowContents() {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
auto msgs = discord.GetMessagesForChannel(m_chat.GetActiveChannel(), 50);
|
||||
m_chat.SetMessages(msgs);
|
||||
m_members.UpdateMemberList();
|
||||
}
|
||||
|
||||
void MainWindow::UpdateChatActiveChannel(Snowflake id) {
|
||||
m_chat.SetActiveChannel(id);
|
||||
m_members.SetActiveChannel(id);
|
||||
m_channel_list.SetActiveChannel(id);
|
||||
m_content_stack.set_visible_child("chat");
|
||||
}
|
||||
|
||||
Snowflake MainWindow::GetChatActiveChannel() const {
|
||||
return m_chat.GetActiveChannel();
|
||||
}
|
||||
|
||||
void MainWindow::UpdateChatNewMessage(const Message &data) {
|
||||
if (data.ChannelID == GetChatActiveChannel()) {
|
||||
m_chat.AddNewMessage(data);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::UpdateChatMessageDeleted(Snowflake id, Snowflake channel_id) {
|
||||
if (channel_id == GetChatActiveChannel())
|
||||
m_chat.DeleteMessage(id);
|
||||
}
|
||||
|
||||
void MainWindow::UpdateChatMessageUpdated(Snowflake id, Snowflake channel_id) {
|
||||
if (channel_id == GetChatActiveChannel())
|
||||
m_chat.UpdateMessage(id);
|
||||
}
|
||||
|
||||
void MainWindow::UpdateChatPrependHistory(const std::vector<Message> &msgs) {
|
||||
m_chat.AddNewHistory(msgs); // given vector should be sorted ascending
|
||||
}
|
||||
|
||||
void MainWindow::InsertChatInput(std::string text) {
|
||||
m_chat.InsertChatInput(text);
|
||||
}
|
||||
|
||||
Snowflake MainWindow::GetChatOldestListedMessage() {
|
||||
return m_chat.GetOldestListedMessage();
|
||||
}
|
||||
|
||||
void MainWindow::UpdateChatReactionAdd(Snowflake id, const Glib::ustring ¶m) {
|
||||
m_chat.UpdateReactions(id);
|
||||
}
|
||||
|
||||
void MainWindow::UpdateChatReactionRemove(Snowflake id, const Glib::ustring ¶m) {
|
||||
m_chat.UpdateReactions(id);
|
||||
}
|
||||
|
||||
void MainWindow::OnDiscordSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y) {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
auto channel_id = GetChatActiveChannel();
|
||||
m_menu_discord_add_recipient.set_visible(false);
|
||||
if (channel_id.IsValid()) {
|
||||
auto channel = discord.GetChannel(channel_id);
|
||||
if (channel.has_value() && channel->GetDMRecipients().size() + 1 < 10)
|
||||
m_menu_discord_add_recipient.set_visible(channel->Type == ChannelType::GROUP_DM);
|
||||
}
|
||||
|
||||
const bool discord_active = Abaddon::Get().GetDiscordClient().IsStarted();
|
||||
|
||||
std::string token = Abaddon::Get().GetDiscordToken();
|
||||
m_menu_discord_connect.set_sensitive(token.size() > 0 && !discord_active);
|
||||
m_menu_discord_disconnect.set_sensitive(discord_active);
|
||||
m_menu_discord_join_guild.set_sensitive(discord_active);
|
||||
m_menu_discord_set_token.set_sensitive(!discord_active);
|
||||
m_menu_discord_set_status.set_sensitive(discord_active);
|
||||
}
|
||||
|
||||
void MainWindow::OnViewSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y) {
|
||||
m_menu_view_friends.set_sensitive(Abaddon::Get().GetDiscordClient().IsStarted());
|
||||
auto channel_id = GetChatActiveChannel();
|
||||
m_menu_view_pins.set_sensitive(false);
|
||||
m_menu_view_threads.set_sensitive(false);
|
||||
if (channel_id.IsValid()) {
|
||||
auto channel = Abaddon::Get().GetDiscordClient().GetChannel(channel_id);
|
||||
if (channel.has_value()) {
|
||||
m_menu_view_threads.set_sensitive(channel->Type == ChannelType::GUILD_TEXT || channel->IsThread());
|
||||
m_menu_view_pins.set_sensitive(channel->Type == ChannelType::GUILD_TEXT || channel->Type == ChannelType::DM || channel->Type == ChannelType::GROUP_DM || channel->IsThread());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ChannelList *MainWindow::GetChannelList() {
|
||||
return &m_channel_list;
|
||||
}
|
||||
|
||||
ChatWindow *MainWindow::GetChatWindow() {
|
||||
return &m_chat;
|
||||
}
|
||||
|
||||
MemberList *MainWindow::GetMemberList() {
|
||||
return &m_members;
|
||||
}
|
||||
|
||||
MainWindow::type_signal_action_connect MainWindow::signal_action_connect() {
|
||||
return m_signal_action_connect;
|
||||
}
|
||||
|
||||
MainWindow::type_signal_action_disconnect MainWindow::signal_action_disconnect() {
|
||||
return m_signal_action_disconnect;
|
||||
}
|
||||
|
||||
MainWindow::type_signal_action_set_token MainWindow::signal_action_set_token() {
|
||||
return m_signal_action_set_token;
|
||||
}
|
||||
|
||||
MainWindow::type_signal_action_reload_css MainWindow::signal_action_reload_css() {
|
||||
return m_signal_action_reload_css;
|
||||
}
|
||||
|
||||
MainWindow::type_signal_action_join_guild MainWindow::signal_action_join_guild() {
|
||||
return m_signal_action_join_guild;
|
||||
}
|
||||
|
||||
MainWindow::type_signal_action_set_status MainWindow::signal_action_set_status() {
|
||||
return m_signal_action_set_status;
|
||||
}
|
||||
|
||||
MainWindow::type_signal_action_add_recipient MainWindow::signal_action_add_recipient() {
|
||||
return m_signal_action_add_recipient;
|
||||
}
|
||||
|
||||
MainWindow::type_signal_action_view_pins MainWindow::signal_action_view_pins() {
|
||||
return m_signal_action_view_pins;
|
||||
}
|
||||
|
||||
MainWindow::type_signal_action_view_threads MainWindow::signal_action_view_threads() {
|
||||
return m_signal_action_view_threads;
|
||||
}
|
||||
99
src/windows/mainwindow.hpp
Normal file
99
src/windows/mainwindow.hpp
Normal file
@@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
#include "components/channels.hpp"
|
||||
#include "components/chatwindow.hpp"
|
||||
#include "components/memberlist.hpp"
|
||||
#include "components/friendslist.hpp"
|
||||
#include <gtkmm.h>
|
||||
|
||||
class MainWindow : public Gtk::Window {
|
||||
public:
|
||||
MainWindow();
|
||||
|
||||
void UpdateComponents();
|
||||
void UpdateMembers();
|
||||
void UpdateChannelListing();
|
||||
void UpdateChatWindowContents();
|
||||
void UpdateChatActiveChannel(Snowflake id);
|
||||
Snowflake GetChatActiveChannel() const;
|
||||
void UpdateChatNewMessage(const Message &data);
|
||||
void UpdateChatMessageDeleted(Snowflake id, Snowflake channel_id);
|
||||
void UpdateChatMessageUpdated(Snowflake id, Snowflake channel_id);
|
||||
void UpdateChatPrependHistory(const std::vector<Message> &msgs);
|
||||
void InsertChatInput(std::string text);
|
||||
Snowflake GetChatOldestListedMessage();
|
||||
void UpdateChatReactionAdd(Snowflake id, const Glib::ustring ¶m);
|
||||
void UpdateChatReactionRemove(Snowflake id, const Glib::ustring ¶m);
|
||||
|
||||
ChannelList *GetChannelList();
|
||||
ChatWindow *GetChatWindow();
|
||||
MemberList *GetMemberList();
|
||||
|
||||
public:
|
||||
typedef sigc::signal<void> type_signal_action_connect;
|
||||
typedef sigc::signal<void> type_signal_action_disconnect;
|
||||
typedef sigc::signal<void> type_signal_action_set_token;
|
||||
typedef sigc::signal<void> type_signal_action_reload_css;
|
||||
typedef sigc::signal<void> type_signal_action_join_guild;
|
||||
typedef sigc::signal<void> type_signal_action_set_status;
|
||||
// this should probably be removed
|
||||
typedef sigc::signal<void, Snowflake> type_signal_action_add_recipient; // channel id
|
||||
typedef sigc::signal<void, Snowflake> type_signal_action_view_pins; // channel id
|
||||
typedef sigc::signal<void, Snowflake> type_signal_action_view_threads; // channel id
|
||||
|
||||
type_signal_action_connect signal_action_connect();
|
||||
type_signal_action_disconnect signal_action_disconnect();
|
||||
type_signal_action_set_token signal_action_set_token();
|
||||
type_signal_action_reload_css signal_action_reload_css();
|
||||
type_signal_action_join_guild signal_action_join_guild();
|
||||
type_signal_action_set_status signal_action_set_status();
|
||||
type_signal_action_add_recipient signal_action_add_recipient();
|
||||
type_signal_action_view_pins signal_action_view_pins();
|
||||
type_signal_action_view_threads signal_action_view_threads();
|
||||
|
||||
protected:
|
||||
type_signal_action_connect m_signal_action_connect;
|
||||
type_signal_action_disconnect m_signal_action_disconnect;
|
||||
type_signal_action_set_token m_signal_action_set_token;
|
||||
type_signal_action_reload_css m_signal_action_reload_css;
|
||||
type_signal_action_join_guild m_signal_action_join_guild;
|
||||
type_signal_action_set_status m_signal_action_set_status;
|
||||
type_signal_action_add_recipient m_signal_action_add_recipient;
|
||||
type_signal_action_view_pins m_signal_action_view_pins;
|
||||
type_signal_action_view_threads m_signal_action_view_threads;
|
||||
|
||||
protected:
|
||||
Gtk::Box m_main_box;
|
||||
Gtk::Box m_content_box;
|
||||
Gtk::Paned m_chan_content_paned;
|
||||
Gtk::Paned m_content_members_paned;
|
||||
|
||||
ChannelList m_channel_list;
|
||||
ChatWindow m_chat;
|
||||
MemberList m_members;
|
||||
FriendsList m_friends;
|
||||
|
||||
Gtk::Stack m_content_stack;
|
||||
|
||||
Gtk::MenuBar m_menu_bar;
|
||||
Gtk::MenuItem m_menu_discord;
|
||||
Gtk::Menu m_menu_discord_sub;
|
||||
Gtk::MenuItem m_menu_discord_connect;
|
||||
Gtk::MenuItem m_menu_discord_disconnect;
|
||||
Gtk::MenuItem m_menu_discord_set_token;
|
||||
Gtk::MenuItem m_menu_discord_join_guild;
|
||||
Gtk::MenuItem m_menu_discord_set_status;
|
||||
Gtk::MenuItem m_menu_discord_add_recipient; // move me somewhere else some day
|
||||
void OnDiscordSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y);
|
||||
|
||||
Gtk::MenuItem m_menu_file;
|
||||
Gtk::Menu m_menu_file_sub;
|
||||
Gtk::MenuItem m_menu_file_reload_css;
|
||||
Gtk::MenuItem m_menu_file_clear_cache;
|
||||
|
||||
Gtk::MenuItem m_menu_view;
|
||||
Gtk::Menu m_menu_view_sub;
|
||||
Gtk::MenuItem m_menu_view_friends;
|
||||
Gtk::MenuItem m_menu_view_pins;
|
||||
Gtk::MenuItem m_menu_view_threads;
|
||||
void OnViewSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y);
|
||||
};
|
||||
47
src/windows/pinnedwindow.cpp
Normal file
47
src/windows/pinnedwindow.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include "pinnedwindow.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
PinnedWindow::PinnedWindow(const ChannelData &data)
|
||||
: ChannelID(data.ID) {
|
||||
if (data.GuildID.has_value())
|
||||
GuildID = *data.GuildID;
|
||||
|
||||
set_name("pinned-messages");
|
||||
set_default_size(450, 375);
|
||||
if (data.Name.has_value())
|
||||
set_title("#" + *data.Name + " - Pinned Messages");
|
||||
else
|
||||
set_title("Pinned Messages");
|
||||
set_position(Gtk::WIN_POS_CENTER);
|
||||
get_style_context()->add_class("app-window");
|
||||
get_style_context()->add_class("app-popup");
|
||||
get_style_context()->add_class("pinned-messages-window");
|
||||
|
||||
add(m_chat);
|
||||
m_chat.show();
|
||||
|
||||
m_chat.SetSeparateAll(true);
|
||||
m_chat.SetActiveChannel(ChannelID);
|
||||
m_chat.SetUsePinnedMenu();
|
||||
|
||||
Abaddon::Get().GetDiscordClient().signal_message_pinned().connect(sigc::mem_fun(*this, &PinnedWindow::OnMessagePinned));
|
||||
Abaddon::Get().GetDiscordClient().signal_message_unpinned().connect(sigc::mem_fun(*this, &PinnedWindow::OnMessageUnpinned));
|
||||
FetchPinned();
|
||||
}
|
||||
|
||||
void PinnedWindow::OnMessagePinned(const Message &msg) {
|
||||
FetchPinned();
|
||||
}
|
||||
|
||||
void PinnedWindow::OnMessageUnpinned(const Message &msg) {
|
||||
m_chat.ActuallyRemoveMessage(msg.ID);
|
||||
}
|
||||
|
||||
void PinnedWindow::FetchPinned() {
|
||||
Abaddon::Get().GetDiscordClient().FetchPinned(ChannelID, sigc::mem_fun(*this, &PinnedWindow::OnFetchedPinned));
|
||||
}
|
||||
|
||||
void PinnedWindow::OnFetchedPinned(const std::vector<Message> &msgs, DiscordError code) {
|
||||
if (code != DiscordError::NONE) return;
|
||||
m_chat.SetMessages(msgs.begin(), msgs.end());
|
||||
}
|
||||
22
src/windows/pinnedwindow.hpp
Normal file
22
src/windows/pinnedwindow.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/errors.hpp"
|
||||
#include "discord/channel.hpp"
|
||||
#include "discord/message.hpp"
|
||||
#include "components/chatlist.hpp"
|
||||
|
||||
class PinnedWindow : public Gtk::Window {
|
||||
public:
|
||||
PinnedWindow(const ChannelData &data);
|
||||
|
||||
Snowflake GuildID;
|
||||
Snowflake ChannelID;
|
||||
|
||||
private:
|
||||
void OnMessagePinned(const Message &msg);
|
||||
void OnMessageUnpinned(const Message &msg);
|
||||
void FetchPinned();
|
||||
void OnFetchedPinned(const std::vector<Message> &msgs, DiscordError code);
|
||||
|
||||
ChatList m_chat;
|
||||
};
|
||||
58
src/windows/profile/mutualfriendspane.cpp
Normal file
58
src/windows/profile/mutualfriendspane.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#include "mutualfriendspane.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
MutualFriendItem::MutualFriendItem(const UserData &user)
|
||||
: Gtk::Box(Gtk::ORIENTATION_HORIZONTAL) {
|
||||
get_style_context()->add_class("mutual-friend-item");
|
||||
m_name.get_style_context()->add_class("mutual-friend-item-name");
|
||||
m_avatar.get_style_context()->add_class("mutual-friend-item-avatar");
|
||||
|
||||
m_avatar.set_margin_end(10);
|
||||
|
||||
const auto show_animations = Abaddon::Get().GetSettings().GetShowAnimations();
|
||||
auto &img = Abaddon::Get().GetImageManager();
|
||||
m_avatar.property_pixbuf() = img.GetPlaceholder(24);
|
||||
if (user.HasAnimatedAvatar() && show_animations) {
|
||||
auto cb = [this](const Glib::RefPtr<Gdk::PixbufAnimation> &pb) {
|
||||
m_avatar.property_pixbuf_animation() = pb;
|
||||
};
|
||||
img.LoadAnimationFromURL(user.GetAvatarURL("gif", "32"), 24, 24, sigc::track_obj(cb, *this));
|
||||
} else {
|
||||
auto cb = [this](const Glib::RefPtr<Gdk::Pixbuf> &pb) {
|
||||
m_avatar.property_pixbuf() = pb->scale_simple(24, 24, Gdk::INTERP_BILINEAR);
|
||||
};
|
||||
img.LoadFromURL(user.GetAvatarURL("png", "32"), sigc::track_obj(cb, *this));
|
||||
}
|
||||
|
||||
m_name.set_markup(user.GetEscapedBoldString<false>());
|
||||
|
||||
m_name.set_valign(Gtk::ALIGN_CENTER);
|
||||
add(m_avatar);
|
||||
add(m_name);
|
||||
show_all_children();
|
||||
}
|
||||
|
||||
MutualFriendsPane::MutualFriendsPane(Snowflake id)
|
||||
: UserID(id) {
|
||||
signal_map().connect(sigc::mem_fun(*this, &MutualFriendsPane::OnMap));
|
||||
add(m_list);
|
||||
show_all_children();
|
||||
}
|
||||
|
||||
void MutualFriendsPane::OnFetchRelationships(const std::vector<UserData> &users) {
|
||||
for (auto child : m_list.get_children())
|
||||
delete child;
|
||||
|
||||
for (const auto &user : users) {
|
||||
auto *item = Gtk::manage(new MutualFriendItem(user));
|
||||
item->show();
|
||||
m_list.add(*item);
|
||||
}
|
||||
}
|
||||
|
||||
void MutualFriendsPane::OnMap() {
|
||||
if (m_requested) return;
|
||||
m_requested = true;
|
||||
|
||||
Abaddon::Get().GetDiscordClient().FetchUserRelationships(UserID, sigc::mem_fun(*this, &MutualFriendsPane::OnFetchRelationships));
|
||||
}
|
||||
28
src/windows/profile/mutualfriendspane.hpp
Normal file
28
src/windows/profile/mutualfriendspane.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/objects.hpp"
|
||||
|
||||
class MutualFriendItem : public Gtk::Box {
|
||||
public:
|
||||
MutualFriendItem(const UserData &user);
|
||||
|
||||
private:
|
||||
Gtk::Image m_avatar;
|
||||
Gtk::Label m_name;
|
||||
};
|
||||
|
||||
class MutualFriendsPane : public Gtk::ScrolledWindow {
|
||||
public:
|
||||
MutualFriendsPane(Snowflake id);
|
||||
|
||||
Snowflake UserID;
|
||||
|
||||
private:
|
||||
void OnMap();
|
||||
|
||||
bool m_requested = false;
|
||||
|
||||
void OnFetchRelationships(const std::vector<UserData> &users);
|
||||
|
||||
Gtk::ListBox m_list;
|
||||
};
|
||||
73
src/windows/profile/mutualguildspane.cpp
Normal file
73
src/windows/profile/mutualguildspane.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#include "mutualguildspane.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
MutualGuildItem::MutualGuildItem(const MutualGuildData &guild)
|
||||
: Gtk::Box(Gtk::ORIENTATION_HORIZONTAL)
|
||||
, m_box(Gtk::ORIENTATION_VERTICAL) {
|
||||
get_style_context()->add_class("mutual-guild-item");
|
||||
m_name.get_style_context()->add_class("mutual-guild-item-name");
|
||||
m_icon.get_style_context()->add_class("mutual-guild-item-icon");
|
||||
|
||||
m_icon.set_margin_end(10);
|
||||
|
||||
// discord will return info (id + nick) for "deleted" guilds from this endpoint. strange !
|
||||
const auto data = Abaddon::Get().GetDiscordClient().GetGuild(guild.ID);
|
||||
if (data.has_value()) {
|
||||
const auto show_animations = Abaddon::Get().GetSettings().GetShowAnimations();
|
||||
auto &img = Abaddon::Get().GetImageManager();
|
||||
m_icon.property_pixbuf() = img.GetPlaceholder(24);
|
||||
if (data->HasIcon()) {
|
||||
if (data->HasAnimatedIcon() && show_animations) {
|
||||
auto cb = [this](const Glib::RefPtr<Gdk::PixbufAnimation> &pb) {
|
||||
m_icon.property_pixbuf_animation() = pb;
|
||||
};
|
||||
img.LoadAnimationFromURL(data->GetIconURL("gif", "32"), 24, 24, sigc::track_obj(cb, *this));
|
||||
} else {
|
||||
auto cb = [this](const Glib::RefPtr<Gdk::Pixbuf> &pb) {
|
||||
m_icon.property_pixbuf() = pb->scale_simple(24, 24, Gdk::INTERP_BILINEAR);
|
||||
};
|
||||
img.LoadFromURL(data->GetIconURL("png", "32"), sigc::track_obj(cb, *this));
|
||||
}
|
||||
}
|
||||
|
||||
m_name.set_markup("<b>" + Glib::Markup::escape_text(data->Name) + "</b>");
|
||||
} else {
|
||||
m_icon.property_pixbuf() = Abaddon::Get().GetImageManager().GetPlaceholder(24);
|
||||
m_name.set_markup("<b>Unknown server</b>");
|
||||
}
|
||||
|
||||
if (guild.Nick.has_value()) {
|
||||
m_nick = Gtk::manage(new Gtk::Label(*guild.Nick));
|
||||
m_nick->get_style_context()->add_class("mutual-guild-item-nick");
|
||||
m_nick->set_margin_start(5);
|
||||
m_nick->set_halign(Gtk::ALIGN_START);
|
||||
m_nick->set_single_line_mode(true);
|
||||
m_nick->set_ellipsize(Pango::ELLIPSIZE_END);
|
||||
}
|
||||
|
||||
m_box.set_valign(Gtk::ALIGN_CENTER);
|
||||
|
||||
m_box.add(m_name);
|
||||
if (m_nick != nullptr)
|
||||
m_box.add(*m_nick);
|
||||
add(m_icon);
|
||||
add(m_box);
|
||||
show_all_children();
|
||||
}
|
||||
|
||||
UserMutualGuildsPane::UserMutualGuildsPane(Snowflake id)
|
||||
: UserID(id) {
|
||||
add(m_list);
|
||||
show_all_children();
|
||||
}
|
||||
|
||||
void UserMutualGuildsPane::SetMutualGuilds(const std::vector<MutualGuildData> &guilds) {
|
||||
for (auto child : m_list.get_children())
|
||||
delete child;
|
||||
|
||||
for (const auto &guild : guilds) {
|
||||
auto *item = Gtk::manage(new MutualGuildItem(guild));
|
||||
item->show();
|
||||
m_list.add(*item);
|
||||
}
|
||||
}
|
||||
26
src/windows/profile/mutualguildspane.hpp
Normal file
26
src/windows/profile/mutualguildspane.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/objects.hpp"
|
||||
|
||||
class MutualGuildItem : public Gtk::Box {
|
||||
public:
|
||||
MutualGuildItem(const MutualGuildData &guild);
|
||||
|
||||
private:
|
||||
Gtk::Image m_icon;
|
||||
Gtk::Box m_box;
|
||||
Gtk::Label m_name;
|
||||
Gtk::Label *m_nick = nullptr;
|
||||
};
|
||||
|
||||
class UserMutualGuildsPane : public Gtk::ScrolledWindow {
|
||||
public:
|
||||
UserMutualGuildsPane(Snowflake id);
|
||||
|
||||
void SetMutualGuilds(const std::vector<MutualGuildData> &guilds);
|
||||
|
||||
Snowflake UserID;
|
||||
|
||||
private:
|
||||
Gtk::ListBox m_list;
|
||||
};
|
||||
236
src/windows/profile/userinfopane.cpp
Normal file
236
src/windows/profile/userinfopane.cpp
Normal file
@@ -0,0 +1,236 @@
|
||||
#include "userinfopane.hpp"
|
||||
#include <unordered_set>
|
||||
#include "abaddon.hpp"
|
||||
|
||||
ConnectionItem::ConnectionItem(const ConnectionData &conn)
|
||||
: m_box(Gtk::ORIENTATION_HORIZONTAL)
|
||||
, m_name(conn.Name) {
|
||||
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
|
||||
try {
|
||||
pixbuf = Gdk::Pixbuf::create_from_file(Abaddon::GetResPath("/" + conn.Type + ".png"), 32, 32);
|
||||
} catch (const Glib::Exception &e) {}
|
||||
std::string url;
|
||||
if (conn.Type == "github")
|
||||
url = "https://github.com/" + conn.Name;
|
||||
else if (conn.Type == "steam")
|
||||
url = "https://steamcommunity.com/profiles/" + conn.ID;
|
||||
else if (conn.Type == "twitch")
|
||||
url = "https://twitch.tv/" + conn.Name;
|
||||
else if (conn.Type == "twitter")
|
||||
url = "https://twitter.com/i/user/" + conn.ID;
|
||||
else if (conn.Type == "spotify")
|
||||
url = "https://open.spotify.com/user/" + conn.ID;
|
||||
else if (conn.Type == "reddit")
|
||||
url = "https://reddit.com/u/" + conn.Name;
|
||||
else if (conn.Type == "youtube")
|
||||
url = "https://www.youtube.com/channel/" + conn.ID;
|
||||
else if (conn.Type == "facebook")
|
||||
url = "https://www.facebook.com/" + conn.ID;
|
||||
if (pixbuf) {
|
||||
m_image = Gtk::manage(new Gtk::Image(pixbuf));
|
||||
m_image->get_style_context()->add_class("profile-connection-image");
|
||||
m_box.add(*m_image);
|
||||
}
|
||||
m_box.set_halign(Gtk::ALIGN_START);
|
||||
m_box.set_size_request(200, -1);
|
||||
m_box.get_style_context()->add_class("profile-connection");
|
||||
m_name.get_style_context()->add_class("profile-connection-label");
|
||||
m_name.set_valign(Gtk::ALIGN_CENTER);
|
||||
m_name.set_single_line_mode(true);
|
||||
m_name.set_ellipsize(Pango::ELLIPSIZE_END);
|
||||
m_box.add(m_name);
|
||||
if (url != "") {
|
||||
auto cb = [this, url](GdkEventButton *event) -> bool {
|
||||
if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_PRIMARY) {
|
||||
LaunchBrowser(url);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
signal_button_press_event().connect(sigc::track_obj(cb, *this));
|
||||
AddPointerCursor(*this);
|
||||
}
|
||||
m_overlay.add(m_box);
|
||||
if (conn.IsVerified) {
|
||||
try {
|
||||
const static auto checkmarks_path = Abaddon::GetResPath("/checkmark.png");
|
||||
static auto pb = Gdk::Pixbuf::create_from_file(checkmarks_path, 24, 24);
|
||||
m_check = Gtk::manage(new Gtk::Image(pb));
|
||||
m_check->get_style_context()->add_class("profile-connection-check");
|
||||
m_check->set_margin_end(25);
|
||||
m_check->set_valign(Gtk::ALIGN_CENTER);
|
||||
m_check->set_halign(Gtk::ALIGN_END);
|
||||
m_check->show();
|
||||
m_overlay.add_overlay(*m_check);
|
||||
} catch (const Glib::Exception &e) {}
|
||||
}
|
||||
m_overlay.set_hexpand(false);
|
||||
m_overlay.set_halign(Gtk::ALIGN_START);
|
||||
add(m_overlay);
|
||||
show_all_children();
|
||||
}
|
||||
|
||||
ConnectionsContainer::ConnectionsContainer() {
|
||||
get_style_context()->add_class("profile-connections");
|
||||
set_column_homogeneous(true);
|
||||
set_row_spacing(10);
|
||||
set_column_spacing(10);
|
||||
show_all_children();
|
||||
}
|
||||
|
||||
void ConnectionsContainer::SetConnections(const std::vector<ConnectionData> &connections) {
|
||||
for (auto child : get_children())
|
||||
delete child;
|
||||
|
||||
static const std::unordered_set<std::string> supported_services = {
|
||||
"battlenet",
|
||||
"github",
|
||||
"leagueoflegends",
|
||||
"reddit",
|
||||
"skype",
|
||||
"spotify",
|
||||
"steam",
|
||||
"twitch",
|
||||
"twitter",
|
||||
"xbox",
|
||||
"youtube",
|
||||
"facebook"
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < connections.size(); i++) {
|
||||
const auto &conn = connections[i];
|
||||
if (supported_services.find(conn.Type) == supported_services.end()) continue;
|
||||
auto widget = Gtk::manage(new ConnectionItem(conn));
|
||||
widget->show();
|
||||
attach(*widget, i % 2, i / 2, 1, 1);
|
||||
}
|
||||
|
||||
set_halign(Gtk::ALIGN_FILL);
|
||||
set_hexpand(true);
|
||||
}
|
||||
|
||||
NotesContainer::NotesContainer()
|
||||
: Gtk::Box(Gtk::ORIENTATION_VERTICAL) {
|
||||
get_style_context()->add_class("profile-notes");
|
||||
m_label.get_style_context()->add_class("profile-notes-label");
|
||||
m_note.get_style_context()->add_class("profile-notes-text");
|
||||
|
||||
m_label.set_markup("<b>NOTE</b>");
|
||||
m_label.set_halign(Gtk::ALIGN_START);
|
||||
|
||||
m_note.set_wrap_mode(Gtk::WRAP_WORD_CHAR);
|
||||
m_note.signal_key_press_event().connect(sigc::mem_fun(*this, &NotesContainer::OnNoteKeyPress), false);
|
||||
|
||||
add(m_label);
|
||||
add(m_note);
|
||||
show_all_children();
|
||||
}
|
||||
|
||||
void NotesContainer::SetNote(const std::string ¬e) {
|
||||
m_note.get_buffer()->set_text(note);
|
||||
}
|
||||
|
||||
void NotesContainer::UpdateNote() {
|
||||
auto text = m_note.get_buffer()->get_text();
|
||||
if (text.size() > 256)
|
||||
text = text.substr(0, 256);
|
||||
m_signal_update_note.emit(text);
|
||||
}
|
||||
|
||||
bool NotesContainer::OnNoteKeyPress(GdkEventKey *event) {
|
||||
if (event->type != GDK_KEY_PRESS) return false;
|
||||
const auto text = m_note.get_buffer()->get_text();
|
||||
if (event->keyval == GDK_KEY_Return) {
|
||||
if (event->state & GDK_SHIFT_MASK) {
|
||||
int newlines = 0;
|
||||
for (const auto c : text)
|
||||
if (c == '\n') newlines++;
|
||||
return newlines >= 5;
|
||||
} else {
|
||||
UpdateNote();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
NotesContainer::type_signal_update_note NotesContainer::signal_update_note() {
|
||||
return m_signal_update_note;
|
||||
}
|
||||
|
||||
BioContainer::BioContainer()
|
||||
: Gtk::Box(Gtk::ORIENTATION_VERTICAL) {
|
||||
m_label.set_markup("<b>ABOUT ME</b>");
|
||||
m_label.set_halign(Gtk::ALIGN_START);
|
||||
m_bio.set_halign(Gtk::ALIGN_START);
|
||||
m_bio.set_line_wrap(true);
|
||||
m_bio.set_line_wrap_mode(Pango::WRAP_WORD_CHAR);
|
||||
|
||||
m_label.show();
|
||||
m_bio.show();
|
||||
|
||||
add(m_label);
|
||||
add(m_bio);
|
||||
}
|
||||
|
||||
void BioContainer::SetBio(const std::string &bio) {
|
||||
m_bio.set_text(bio);
|
||||
}
|
||||
|
||||
ProfileUserInfoPane::ProfileUserInfoPane(Snowflake ID)
|
||||
: Gtk::Box(Gtk::ORIENTATION_VERTICAL)
|
||||
, UserID(ID) {
|
||||
get_style_context()->add_class("profile-info-pane");
|
||||
m_created.get_style_context()->add_class("profile-info-created");
|
||||
|
||||
m_note.signal_update_note().connect([this](const Glib::ustring ¬e) {
|
||||
auto cb = [this](DiscordError code) {
|
||||
if (code != DiscordError::NONE) {
|
||||
Gtk::MessageDialog dlg("Failed to set note", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
dlg.run();
|
||||
}
|
||||
};
|
||||
Abaddon::Get().GetDiscordClient().SetUserNote(UserID, note, sigc::track_obj(cb, *this));
|
||||
});
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
auto note_update_cb = [this](Snowflake id, std::string note) {
|
||||
if (id == UserID)
|
||||
m_note.SetNote(note);
|
||||
};
|
||||
discord.signal_note_update().connect(sigc::track_obj(note_update_cb, m_note));
|
||||
|
||||
auto fetch_note_cb = [this](const std::string ¬e) {
|
||||
m_note.SetNote(note);
|
||||
};
|
||||
discord.FetchUserNote(UserID, sigc::track_obj(fetch_note_cb, *this));
|
||||
|
||||
m_created.set_halign(Gtk::ALIGN_START);
|
||||
m_created.set_margin_top(5);
|
||||
m_created.set_text("Account created: " + ID.GetLocalTimestamp());
|
||||
|
||||
m_conns.set_halign(Gtk::ALIGN_START);
|
||||
m_conns.set_hexpand(true);
|
||||
|
||||
m_created.show();
|
||||
m_note.show();
|
||||
m_conns.show();
|
||||
|
||||
add(m_created);
|
||||
add(m_bio);
|
||||
add(m_note);
|
||||
add(m_conns);
|
||||
}
|
||||
|
||||
void ProfileUserInfoPane::SetProfile(const UserProfileData &data) {
|
||||
if (data.User.Bio.has_value() && *data.User.Bio != "") {
|
||||
m_bio.SetBio(*data.User.Bio);
|
||||
m_bio.show();
|
||||
} else {
|
||||
m_bio.hide();
|
||||
}
|
||||
|
||||
m_conns.SetConnections(data.ConnectedAccounts);
|
||||
}
|
||||
65
src/windows/profile/userinfopane.hpp
Normal file
65
src/windows/profile/userinfopane.hpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/objects.hpp"
|
||||
|
||||
class ConnectionItem : public Gtk::EventBox {
|
||||
public:
|
||||
ConnectionItem(const ConnectionData &connection);
|
||||
|
||||
private:
|
||||
Gtk::Overlay m_overlay;
|
||||
Gtk::Box m_box;
|
||||
Gtk::Label m_name;
|
||||
Gtk::Image *m_image = nullptr;
|
||||
Gtk::Image *m_check = nullptr;
|
||||
};
|
||||
|
||||
class ConnectionsContainer : public Gtk::Grid {
|
||||
public:
|
||||
ConnectionsContainer();
|
||||
void SetConnections(const std::vector<ConnectionData> &connections);
|
||||
};
|
||||
|
||||
class NotesContainer : public Gtk::Box {
|
||||
public:
|
||||
NotesContainer();
|
||||
void SetNote(const std::string ¬e);
|
||||
|
||||
private:
|
||||
void UpdateNote();
|
||||
bool OnNoteKeyPress(GdkEventKey *event);
|
||||
|
||||
Gtk::Label m_label;
|
||||
Gtk::TextView m_note;
|
||||
|
||||
typedef sigc::signal<void, Glib::ustring> type_signal_update_note;
|
||||
type_signal_update_note m_signal_update_note;
|
||||
|
||||
public:
|
||||
type_signal_update_note signal_update_note();
|
||||
};
|
||||
|
||||
class BioContainer : public Gtk::Box {
|
||||
public:
|
||||
BioContainer();
|
||||
void SetBio(const std::string &bio);
|
||||
|
||||
private:
|
||||
Gtk::Label m_label;
|
||||
Gtk::Label m_bio;
|
||||
};
|
||||
|
||||
class ProfileUserInfoPane : public Gtk::Box {
|
||||
public:
|
||||
ProfileUserInfoPane(Snowflake ID);
|
||||
void SetProfile(const UserProfileData &data);
|
||||
|
||||
Snowflake UserID;
|
||||
|
||||
private:
|
||||
Gtk::Label m_created;
|
||||
|
||||
BioContainer m_bio;
|
||||
NotesContainer m_note;
|
||||
ConnectionsContainer m_conns;
|
||||
};
|
||||
127
src/windows/profilewindow.cpp
Normal file
127
src/windows/profilewindow.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
#include "profilewindow.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
ProfileWindow::ProfileWindow(Snowflake user_id)
|
||||
: ID(user_id)
|
||||
, m_main(Gtk::ORIENTATION_VERTICAL)
|
||||
, m_upper(Gtk::ORIENTATION_HORIZONTAL)
|
||||
, m_badges(Gtk::ORIENTATION_HORIZONTAL)
|
||||
, m_pane_info(user_id)
|
||||
, m_pane_guilds(user_id)
|
||||
, m_pane_friends(user_id) {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
auto user = *discord.GetUser(ID);
|
||||
|
||||
discord.FetchUserProfile(user_id, sigc::mem_fun(*this, &ProfileWindow::OnFetchProfile));
|
||||
|
||||
set_name("user-profile");
|
||||
set_default_size(450, 375);
|
||||
set_title(user.Username + "#" + user.Discriminator);
|
||||
set_position(Gtk::WIN_POS_CENTER);
|
||||
get_style_context()->add_class("app-window");
|
||||
get_style_context()->add_class("app-popup");
|
||||
get_style_context()->add_class("user-profile-window");
|
||||
m_main.get_style_context()->add_class("profile-main-container");
|
||||
m_avatar.get_style_context()->add_class("profile-avatar");
|
||||
m_username.get_style_context()->add_class("profile-username");
|
||||
m_switcher.get_style_context()->add_class("profile-switcher");
|
||||
m_stack.get_style_context()->add_class("profile-stack");
|
||||
m_badges.get_style_context()->add_class("profile-badges");
|
||||
|
||||
m_scroll.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
|
||||
m_scroll.set_vexpand(true);
|
||||
m_scroll.set_propagate_natural_height(true);
|
||||
|
||||
if (user.HasAvatar())
|
||||
AddPointerCursor(m_avatar_ev);
|
||||
m_avatar_ev.signal_button_press_event().connect([this, user](GdkEventButton *event) -> bool {
|
||||
if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_PRIMARY) {
|
||||
if (user.HasAnimatedAvatar())
|
||||
LaunchBrowser(user.GetAvatarURL("gif", "512"));
|
||||
else
|
||||
LaunchBrowser(user.GetAvatarURL("png", "512"));
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
static const bool show_animations = Abaddon::Get().GetSettings().GetShowAnimations();
|
||||
auto &img = Abaddon::Get().GetImageManager();
|
||||
m_avatar.property_pixbuf() = img.GetPlaceholder(64);
|
||||
auto icon_cb = [this](const Glib::RefPtr<Gdk::Pixbuf> &pb) {
|
||||
set_icon(pb);
|
||||
};
|
||||
img.LoadFromURL(user.GetAvatarURL("png", "64"), sigc::track_obj(icon_cb, *this));
|
||||
|
||||
if (show_animations && user.HasAnimatedAvatar()) {
|
||||
auto cb = [this](const Glib::RefPtr<Gdk::PixbufAnimation> &pb) {
|
||||
m_avatar.property_pixbuf_animation() = pb;
|
||||
};
|
||||
img.LoadAnimationFromURL(user.GetAvatarURL("gif", "64"), 64, 64, sigc::track_obj(cb, *this));
|
||||
} else {
|
||||
auto cb = [this](const Glib::RefPtr<Gdk::Pixbuf> &pb) {
|
||||
m_avatar.property_pixbuf() = pb->scale_simple(64, 64, Gdk::INTERP_BILINEAR);
|
||||
};
|
||||
img.LoadFromURL(user.GetAvatarURL("png", "64"), sigc::track_obj(cb, *this));
|
||||
}
|
||||
|
||||
m_username.set_markup(user.GetEscapedString());
|
||||
|
||||
m_switcher.set_stack(m_stack);
|
||||
m_switcher.set_halign(Gtk::ALIGN_START);
|
||||
m_switcher.set_hexpand(true);
|
||||
|
||||
m_stack.add(m_pane_info, "info", "User Info");
|
||||
m_stack.add(m_pane_guilds, "guilds", "Mutual Servers");
|
||||
m_stack.add(m_pane_friends, "friends", "Mutual Friends");
|
||||
|
||||
m_badges.set_valign(Gtk::ALIGN_CENTER);
|
||||
m_badges_scroll.set_hexpand(true);
|
||||
m_badges_scroll.set_propagate_natural_width(true);
|
||||
m_badges_scroll.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_NEVER);
|
||||
|
||||
m_upper.set_halign(Gtk::ALIGN_START);
|
||||
m_avatar.set_halign(Gtk::ALIGN_START);
|
||||
m_username.set_halign(Gtk::ALIGN_START);
|
||||
m_avatar_ev.add(m_avatar);
|
||||
m_upper.add(m_avatar_ev);
|
||||
m_upper.add(m_username);
|
||||
m_badges_scroll.add(m_badges);
|
||||
m_upper.add(m_badges_scroll);
|
||||
m_main.add(m_upper);
|
||||
m_main.add(m_switcher);
|
||||
m_scroll.add(m_stack);
|
||||
m_main.add(m_scroll);
|
||||
add(m_main);
|
||||
show_all_children();
|
||||
}
|
||||
|
||||
void ProfileWindow::OnFetchProfile(const UserProfileData &data) {
|
||||
m_pane_info.SetProfile(data);
|
||||
m_pane_guilds.SetMutualGuilds(data.MutualGuilds);
|
||||
|
||||
for (auto child : m_badges.get_children())
|
||||
delete child;
|
||||
|
||||
if (!data.User.PublicFlags.has_value()) return;
|
||||
const auto x = *data.User.PublicFlags;
|
||||
for (uint64_t i = 1; i <= UserData::MaxFlag; i <<= 1) {
|
||||
if (!(x & i)) continue;
|
||||
const std::string name = UserData::GetFlagName(i);
|
||||
if (name == "unknown") continue;
|
||||
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
|
||||
try {
|
||||
if (name == "verifiedbot")
|
||||
pixbuf = Gdk::Pixbuf::create_from_file(Abaddon::GetResPath("/checkmark.png"), 24, 24);
|
||||
else
|
||||
pixbuf = Gdk::Pixbuf::create_from_file(Abaddon::GetResPath("/" + name + ".png"), 24, 24);
|
||||
} catch (const Glib::Exception &e) {
|
||||
pixbuf = Abaddon::Get().GetImageManager().GetPlaceholder(24);
|
||||
}
|
||||
if (!pixbuf) continue;
|
||||
auto *image = Gtk::manage(new Gtk::Image(pixbuf));
|
||||
image->get_style_context()->add_class("profile-badge");
|
||||
image->set_tooltip_text(UserData::GetFlagReadableName(i));
|
||||
image->show();
|
||||
m_badges.add(*image);
|
||||
}
|
||||
}
|
||||
31
src/windows/profilewindow.hpp
Normal file
31
src/windows/profilewindow.hpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/snowflake.hpp"
|
||||
#include "profile/userinfopane.hpp"
|
||||
#include "profile/mutualguildspane.hpp"
|
||||
#include "profile/mutualfriendspane.hpp"
|
||||
|
||||
class ProfileWindow : public Gtk::Window {
|
||||
public:
|
||||
ProfileWindow(Snowflake user_id);
|
||||
|
||||
Snowflake ID;
|
||||
|
||||
private:
|
||||
void OnFetchProfile(const UserProfileData &data);
|
||||
|
||||
Gtk::Box m_main;
|
||||
Gtk::Box m_upper;
|
||||
Gtk::Box m_badges;
|
||||
Gtk::ScrolledWindow m_badges_scroll;
|
||||
Gtk::EventBox m_avatar_ev;
|
||||
Gtk::Image m_avatar;
|
||||
Gtk::Label m_username;
|
||||
Gtk::ScrolledWindow m_scroll;
|
||||
Gtk::Stack m_stack;
|
||||
Gtk::StackSwitcher m_switcher;
|
||||
|
||||
ProfileUserInfoPane m_pane_info;
|
||||
UserMutualGuildsPane m_pane_guilds;
|
||||
MutualFriendsPane m_pane_friends;
|
||||
};
|
||||
149
src/windows/threadswindow.cpp
Normal file
149
src/windows/threadswindow.cpp
Normal file
@@ -0,0 +1,149 @@
|
||||
#include "threadswindow.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
ThreadsWindow::ThreadsWindow(const ChannelData &channel)
|
||||
: m_channel_id(channel.ID)
|
||||
, m_filter_public(m_group, "Public")
|
||||
, m_filter_private(m_group, "Private")
|
||||
, m_box(Gtk::ORIENTATION_VERTICAL)
|
||||
, m_active(channel, sigc::mem_fun(*this, &ThreadsWindow::ListFilterFunc))
|
||||
, m_archived(channel, sigc::mem_fun(*this, &ThreadsWindow::ListFilterFunc)) {
|
||||
set_name("threads-window");
|
||||
set_default_size(450, 375);
|
||||
set_title("#" + *channel.Name + " - Threads");
|
||||
set_position(Gtk::WIN_POS_CENTER);
|
||||
get_style_context()->add_class("app-window");
|
||||
get_style_context()->add_class("app-popup");
|
||||
get_style_context()->add_class("threads-window");
|
||||
|
||||
const auto cb = [this](Snowflake id) {
|
||||
Abaddon::Get().ActionChannelOpened(id);
|
||||
hide();
|
||||
};
|
||||
m_active.signal_thread_open().connect(cb);
|
||||
m_archived.signal_thread_open().connect(cb);
|
||||
|
||||
m_switcher.set_halign(Gtk::ALIGN_CENTER);
|
||||
m_switcher.set_stack(m_stack);
|
||||
|
||||
m_stack.add(m_active, "active", "Active Threads");
|
||||
m_stack.add(m_archived, "archived", "Archived Threads");
|
||||
|
||||
m_filter_buttons.set_homogeneous(true);
|
||||
m_filter_buttons.set_halign(Gtk::ALIGN_CENTER);
|
||||
m_filter_buttons.add(m_filter_public);
|
||||
m_filter_buttons.add(m_filter_private);
|
||||
|
||||
// a little strange
|
||||
const auto btncb = [this](Gtk::RadioButton *btn) {
|
||||
if (btn->get_active()) {
|
||||
if (btn == &m_filter_public)
|
||||
m_filter_mode = FILTER_PUBLIC;
|
||||
else if (btn == &m_filter_private)
|
||||
m_filter_mode = FILTER_PRIVATE;
|
||||
|
||||
m_active.InvalidateFilter();
|
||||
m_archived.InvalidateFilter();
|
||||
}
|
||||
};
|
||||
m_filter_public.signal_toggled().connect(sigc::bind(btncb, &m_filter_public));
|
||||
m_filter_private.signal_toggled().connect(sigc::bind(btncb, &m_filter_private));
|
||||
|
||||
m_active.show();
|
||||
m_archived.show();
|
||||
m_switcher.show();
|
||||
m_filter_buttons.show_all();
|
||||
m_stack.show();
|
||||
m_box.show();
|
||||
|
||||
m_box.add(m_switcher);
|
||||
m_box.add(m_filter_buttons);
|
||||
m_box.add(m_stack);
|
||||
add(m_box);
|
||||
}
|
||||
|
||||
bool ThreadsWindow::ListFilterFunc(Gtk::ListBoxRow *row_) {
|
||||
if (auto *row = dynamic_cast<ThreadListRow *>(row_))
|
||||
return (m_filter_mode == FILTER_PUBLIC && (row->Type == ChannelType::GUILD_PUBLIC_THREAD || row->Type == ChannelType::GUILD_NEWS_THREAD)) ||
|
||||
(m_filter_mode == FILTER_PRIVATE && row->Type == ChannelType::GUILD_PRIVATE_THREAD);
|
||||
return false;
|
||||
}
|
||||
|
||||
ThreadListRow::ThreadListRow(const ChannelData &channel)
|
||||
: ID(channel.ID)
|
||||
, Type(channel.Type)
|
||||
, m_label(*channel.Name, Gtk::ALIGN_START) {
|
||||
m_label.show();
|
||||
add(m_label);
|
||||
}
|
||||
|
||||
ActiveThreadsList::ActiveThreadsList(const ChannelData &channel, const Gtk::ListBox::SlotFilter &filter) {
|
||||
set_vexpand(true);
|
||||
|
||||
m_list.set_filter_func(filter);
|
||||
m_list.set_selection_mode(Gtk::SELECTION_SINGLE);
|
||||
m_list.set_hexpand(true);
|
||||
m_list.show();
|
||||
|
||||
add(m_list);
|
||||
|
||||
m_list.signal_button_press_event().connect([this](GdkEventButton *ev) -> bool {
|
||||
if (ev->button == GDK_BUTTON_PRIMARY && ev->type == GDK_2BUTTON_PRESS) {
|
||||
if (auto row = dynamic_cast<ThreadListRow *>(m_list.get_selected_row()))
|
||||
m_signal_thread_open.emit(row->ID);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const auto threads = Abaddon::Get().GetDiscordClient().GetActiveThreads(channel.ID);
|
||||
for (const auto &thread : threads) {
|
||||
auto row = Gtk::manage(new ThreadListRow(thread));
|
||||
row->show();
|
||||
m_list.add(*row);
|
||||
}
|
||||
}
|
||||
|
||||
void ActiveThreadsList::InvalidateFilter() {
|
||||
m_list.invalidate_filter();
|
||||
}
|
||||
|
||||
ActiveThreadsList::type_signal_thread_open ActiveThreadsList::signal_thread_open() {
|
||||
return m_signal_thread_open;
|
||||
}
|
||||
|
||||
ArchivedThreadsList::ArchivedThreadsList(const ChannelData &channel, const Gtk::ListBox::SlotFilter &filter) {
|
||||
set_vexpand(true);
|
||||
|
||||
m_list.set_filter_func(filter);
|
||||
m_list.set_selection_mode(Gtk::SELECTION_SINGLE);
|
||||
m_list.set_hexpand(true);
|
||||
m_list.show();
|
||||
|
||||
add(m_list);
|
||||
|
||||
m_list.signal_button_press_event().connect([this](GdkEventButton *ev) -> bool {
|
||||
if (ev->button == GDK_BUTTON_PRIMARY && ev->type == GDK_2BUTTON_PRESS) {
|
||||
if (auto row = dynamic_cast<ThreadListRow *>(m_list.get_selected_row()))
|
||||
m_signal_thread_open.emit(row->ID);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
Abaddon::Get().GetDiscordClient().GetArchivedPublicThreads(channel.ID, sigc::mem_fun(*this, &ArchivedThreadsList::OnPublicFetched));
|
||||
}
|
||||
|
||||
void ArchivedThreadsList::InvalidateFilter() {
|
||||
m_list.invalidate_filter();
|
||||
}
|
||||
|
||||
void ArchivedThreadsList::OnPublicFetched(DiscordError code, const ArchivedThreadsResponseData &data) {
|
||||
for (const auto &thread : data.Threads) {
|
||||
auto row = Gtk::manage(new ThreadListRow(thread));
|
||||
row->show();
|
||||
m_list.add(*row);
|
||||
}
|
||||
}
|
||||
|
||||
ArchivedThreadsList::type_signal_thread_open ArchivedThreadsList::signal_thread_open() {
|
||||
return m_signal_thread_open;
|
||||
}
|
||||
79
src/windows/threadswindow.hpp
Normal file
79
src/windows/threadswindow.hpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/objects.hpp"
|
||||
|
||||
class ActiveThreadsList : public Gtk::ScrolledWindow {
|
||||
public:
|
||||
ActiveThreadsList(const ChannelData &channel, const Gtk::ListBox::SlotFilter &filter);
|
||||
|
||||
void InvalidateFilter();
|
||||
|
||||
private:
|
||||
Gtk::ListBox m_list;
|
||||
|
||||
using type_signal_thread_open = sigc::signal<void, Snowflake>;
|
||||
type_signal_thread_open m_signal_thread_open;
|
||||
|
||||
public:
|
||||
type_signal_thread_open signal_thread_open();
|
||||
};
|
||||
|
||||
class ArchivedThreadsList : public Gtk::ScrolledWindow {
|
||||
public:
|
||||
ArchivedThreadsList(const ChannelData &channel, const Gtk::ListBox::SlotFilter &filter);
|
||||
|
||||
void InvalidateFilter();
|
||||
|
||||
private:
|
||||
Gtk::ListBox m_list;
|
||||
|
||||
void OnPublicFetched(DiscordError code, const ArchivedThreadsResponseData &data);
|
||||
|
||||
using type_signal_thread_open = sigc::signal<void, Snowflake>;
|
||||
type_signal_thread_open m_signal_thread_open;
|
||||
|
||||
public:
|
||||
type_signal_thread_open signal_thread_open();
|
||||
};
|
||||
|
||||
// view all threads in a channel
|
||||
class ThreadsWindow : public Gtk::Window {
|
||||
public:
|
||||
ThreadsWindow(const ChannelData &channel);
|
||||
|
||||
private:
|
||||
// this filtering is rather cringe but idk what a better alternative would be
|
||||
bool ListFilterFunc(Gtk::ListBoxRow *row_);
|
||||
|
||||
enum FilterMode {
|
||||
FILTER_PUBLIC = 0,
|
||||
FILTER_PRIVATE = 1,
|
||||
};
|
||||
bool m_filter_mode = FILTER_PUBLIC;
|
||||
|
||||
Snowflake m_channel_id;
|
||||
|
||||
Gtk::StackSwitcher m_switcher;
|
||||
Gtk::Stack m_stack;
|
||||
|
||||
Gtk::RadioButtonGroup m_group;
|
||||
Gtk::ButtonBox m_filter_buttons;
|
||||
Gtk::RadioButton m_filter_public;
|
||||
Gtk::RadioButton m_filter_private;
|
||||
|
||||
Gtk::Box m_box;
|
||||
|
||||
ActiveThreadsList m_active;
|
||||
ArchivedThreadsList m_archived;
|
||||
};
|
||||
|
||||
class ThreadListRow : public Gtk::ListBoxRow {
|
||||
public:
|
||||
ThreadListRow(const ChannelData &channel);
|
||||
|
||||
Snowflake ID;
|
||||
ChannelType Type;
|
||||
|
||||
private:
|
||||
Gtk::Label m_label;
|
||||
};
|
||||
Reference in New Issue
Block a user