From a51a54bc5979a2491f152abc47ad54e6b63f27c8 Mon Sep 17 00:00:00 2001 From: Dylam De La Torre Date: Tue, 23 Nov 2021 05:21:56 +0100 Subject: Restructure source and resource files (#46) importantly, res is now res/res and css is now res/css --- src/windows/guildsettings/auditlogpane.cpp | 636 +++++++++++++++++++++++++++++ src/windows/guildsettings/auditlogpane.hpp | 19 + src/windows/guildsettings/banspane.cpp | 161 ++++++++ src/windows/guildsettings/banspane.hpp | 45 ++ src/windows/guildsettings/emojispane.cpp | 257 ++++++++++++ src/windows/guildsettings/emojispane.hpp | 53 +++ src/windows/guildsettings/infopane.cpp | 220 ++++++++++ src/windows/guildsettings/infopane.hpp | 26 ++ src/windows/guildsettings/invitespane.cpp | 136 ++++++ src/windows/guildsettings/invitespane.hpp | 43 ++ src/windows/guildsettings/memberspane.cpp | 410 +++++++++++++++++++ src/windows/guildsettings/memberspane.hpp | 135 ++++++ src/windows/guildsettings/rolespane.cpp | 419 +++++++++++++++++++ src/windows/guildsettings/rolespane.hpp | 102 +++++ 14 files changed, 2662 insertions(+) create mode 100644 src/windows/guildsettings/auditlogpane.cpp create mode 100644 src/windows/guildsettings/auditlogpane.hpp create mode 100644 src/windows/guildsettings/banspane.cpp create mode 100644 src/windows/guildsettings/banspane.hpp create mode 100644 src/windows/guildsettings/emojispane.cpp create mode 100644 src/windows/guildsettings/emojispane.hpp create mode 100644 src/windows/guildsettings/infopane.cpp create mode 100644 src/windows/guildsettings/infopane.hpp create mode 100644 src/windows/guildsettings/invitespane.cpp create mode 100644 src/windows/guildsettings/invitespane.hpp create mode 100644 src/windows/guildsettings/memberspane.cpp create mode 100644 src/windows/guildsettings/memberspane.hpp create mode 100644 src/windows/guildsettings/rolespane.cpp create mode 100644 src/windows/guildsettings/rolespane.hpp (limited to 'src/windows/guildsettings') diff --git a/src/windows/guildsettings/auditlogpane.cpp b/src/windows/guildsettings/auditlogpane.cpp new file mode 100644 index 0000000..08f99da --- /dev/null +++ b/src/windows/guildsettings/auditlogpane.cpp @@ -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 = "Unknown User"; + if (entry.UserID.has_value()) { + if (auto user = discord.GetUser(*entry.UserID); user.has_value()) + user_markup = discord.GetUser(*entry.UserID)->GetEscapedBoldString(); + } + + // spaghetti moment + Glib::ustring markup; + std::vector extra_markup; + switch (entry.Type) { + case AuditLogActionType::GUILD_UPDATE: { + markup = + user_markup + + " made changes to " + + Glib::Markup::escape_text(guild.Name) + + ""; + + 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 " + + Glib::Markup::escape_text(new_name->get()) + + ""); + else + extra_markup.push_back("Set the server name"); + } + } + } break; + case AuditLogActionType::CHANNEL_CREATE: { + const auto type = *entry.GetNewFromKey("type"); + markup = user_markup + + " created a " + (type == ChannelType::GUILD_VOICE ? "voice" : "text") + + " channel #" + + Glib::Markup::escape_text(*entry.GetNewFromKey("name")) + + ""; + 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 " + + Glib::Markup::escape_text(change.NewValue->get()) + + ""); + 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 #" + + Glib::Markup::escape_text(*target_channel->Name) + + ""; + } else { + markup = user_markup + + " made changes to <#" + + entry.TargetID + + ">"; + } + 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 " + + Glib::Markup::escape_text(change.OldValue->get()) + + " to " + + Glib::Markup::escape_text(change.NewValue->get()) + + ""); + else + extra_markup.push_back("Changed the name to " + + Glib::Markup::escape_text(change.NewValue->get()) + + ""); + } else if (change.Key == "topic") { + if (change.NewValue.has_value()) + extra_markup.push_back("Changed the topic to " + + Glib::Markup::escape_text(change.NewValue->get()) + + ""); + 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(); + if (secs == 0) + extra_markup.push_back("Disabled slowmode"); + else + extra_markup.push_back("Set slowmode to " + + std::to_string(secs) + " seconds"); + } + } + } break; + case AuditLogActionType::CHANNEL_DELETE: { + markup = user_markup + + " removed #" + + Glib::Markup::escape_text(*entry.GetOldFromKey("name")) + + ""; + } break; + case AuditLogActionType::CHANNEL_OVERWRITE_CREATE: { + const auto channel = discord.GetChannel(entry.TargetID); + if (channel.has_value()) { + markup = user_markup + + " created channel overrides for #" + + Glib::Markup::escape_text(*channel->Name) + ""; + } else { + markup = user_markup + + " created channel overrides for <#" + + entry.TargetID + ">"; + } + } break; + case AuditLogActionType::CHANNEL_OVERWRITE_UPDATE: { + const auto channel = discord.GetChannel(entry.TargetID); + if (channel.has_value()) { + markup = user_markup + + " updated channel overrides for #" + + Glib::Markup::escape_text(*channel->Name) + ""; + } else { + markup = user_markup + + " updated channel overrides for <#" + + entry.TargetID + ">"; + } + } break; + case AuditLogActionType::CHANNEL_OVERWRITE_DELETE: { + const auto channel = discord.GetChannel(entry.TargetID); + if (channel.has_value()) { + markup = user_markup + + " removed channel overrides for #" + + Glib::Markup::escape_text(*channel->Name) + ""; + } else { + markup = user_markup + + " removed channel overrides for <#" + + entry.TargetID + ">"; + } + } break; + case AuditLogActionType::MEMBER_KICK: { + const auto target_user = discord.GetUser(entry.TargetID); + markup = user_markup + + " kicked " + + target_user->GetEscapedString() + + ""; + } break; + case AuditLogActionType::MEMBER_PRUNE: { + markup = user_markup + + " pruned " + + *entry.Options->MembersRemoved + + " members"; + extra_markup.push_back("For " + + *entry.Options->DeleteMemberDays + + " days of inactivity"); + } break; + case AuditLogActionType::MEMBER_BAN_ADD: { + const auto target_user = discord.GetUser(entry.TargetID); + markup = user_markup + + " banned " + + target_user->GetEscapedString() + + ""; + } break; + case AuditLogActionType::MEMBER_BAN_REMOVE: { + const auto target_user = discord.GetUser(entry.TargetID); + markup = user_markup + + " removed the ban for " + + target_user->GetEscapedString() + + ""; + } break; + case AuditLogActionType::MEMBER_UPDATE: { + const auto target_user = discord.GetUser(entry.TargetID); + markup = user_markup + + " updated " + + target_user->GetEscapedString() + + ""; + 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() ? "Deafened"s : "Undeafened"s) + + " them"); + else if (change.Key == "mute" && change.NewValue.has_value()) + extra_markup.push_back( + (change.NewValue->get() ? "Muted"s : "Unmuted"s) + + " them"); + else if (change.Key == "nick" && change.NewValue.has_value()) + extra_markup.push_back("Set their nickname to " + + Glib::Markup::escape_text(change.NewValue->get()) + + ""); + } + } break; + case AuditLogActionType::MEMBER_ROLE_UPDATE: { + const auto target_user = discord.GetUser(entry.TargetID); + markup = user_markup + + " updated roles for " + + target_user->GetEscapedString() + ""; + if (entry.Changes.has_value()) + for (const auto &change : *entry.Changes) { + if (change.Key == "$remove" && change.NewValue.has_value()) { + extra_markup.push_back("Removed a role " + + Glib::Markup::escape_text(change.NewValue.value()[0].at("name").get()) + + ""); + } else if (change.Key == "$add" && change.NewValue.has_value()) { + extra_markup.push_back("Added a role " + + Glib::Markup::escape_text(change.NewValue.value()[0].at("name").get()) + + ""); + } + } + } break; + case AuditLogActionType::MEMBER_MOVE: { + const auto channel = discord.GetChannel(*entry.Options->ChannelID); + markup = user_markup + + " moved " + + *entry.Options->Count + + " user" + + (*entry.Options->Count == "1" ? ""s : "s"s) + + " to " + + Glib::Markup::escape_text(*channel->Name) + + ""; + } break; + case AuditLogActionType::MEMBER_DISCONNECT: { + markup = user_markup + + " disconnected " + + *entry.Options->Count + + " users from voice"; + } break; + case AuditLogActionType::BOT_ADD: { + const auto target_user = discord.GetUser(entry.TargetID); + markup = user_markup + + " added " + + target_user->GetEscapedString() + + " to the server"; + } break; + case AuditLogActionType::ROLE_CREATE: { + markup = user_markup + + " created the role " + + *entry.GetNewFromKey("name") + + ""; + } break; + case AuditLogActionType::ROLE_UPDATE: { + const auto role = discord.GetRole(entry.TargetID); + markup = user_markup + + " updated the role " + + (role.has_value() ? Glib::Markup::escape_text(role->Name) : Glib::ustring(entry.TargetID)) + + ""; + 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 " + + Glib::Markup::escape_text(change.NewValue->get()) + + ""); + } else if (change.Key == "color" && change.NewValue.has_value()) { + const auto col = change.NewValue->get(); + if (col == 0) + extra_markup.push_back("Removed the color"); + else + extra_markup.push_back("Set the color to " + + IntToCSSColor(col) + + ""); + } 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() ? "Mentionable" : "Not mentionable"); + } else if (change.Key == "hoist" && change.NewValue.has_value()) { + extra_markup.push_back(change.NewValue->get() ? "Not hoisted" : "Hoisted"); + } + } + } break; + case AuditLogActionType::ROLE_DELETE: { + markup = user_markup + + " deleted the role " + + *entry.GetOldFromKey("name") + + ""; + } break; + case AuditLogActionType::INVITE_CREATE: { + const auto code = *entry.GetNewFromKey("code"); + markup = user_markup + + " created an invite " + code + ""; + 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()); + if (!channel.has_value()) continue; + extra_markup.push_back("For channel #" + + Glib::Markup::escape_text(*channel->Name) + + ""); + } else if (change.Key == "max_uses" && change.NewValue.has_value()) { + const auto uses = change.NewValue->get(); + if (uses == 0) + extra_markup.push_back("Which has unlimited uses"); + else + extra_markup.push_back("Which has " + std::to_string(uses) + " uses"); + } else if (change.Key == "temporary" && change.NewValue.has_value()) { + extra_markup.push_back("With temporary "s + + (change.NewValue->get() ? "on" : "off") + + ""); + } // no max_age cuz fuck time + } + } break; + case AuditLogActionType::INVITE_DELETE: { + markup = user_markup + + " deleted an invite " + + *entry.GetOldFromKey("code") + + ""; + } break; + case AuditLogActionType::WEBHOOK_CREATE: { + markup = user_markup + + " created the webhook " + + Glib::Markup::escape_text(*entry.GetNewFromKey("name")) + + ""; + for (const auto &change : *entry.Changes) { + if (change.Key == "channel_id" && change.NewValue.has_value()) { + const auto channel = discord.GetChannel(change.NewValue->get()); + if (channel.has_value()) { + extra_markup.push_back("With channel #" + + Glib::Markup::escape_text(*channel->Name) + + ""); + } + } + } + } 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 " + + Glib::Markup::escape_text(webhookptr->Name) + + ""; + } 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 " + + Glib::Markup::escape_text(change.NewValue->get()) + + ""); + } 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()); + if (channel.has_value()) { + extra_markup.push_back("Changed the channel to #" + + Glib::Markup::escape_text(*channel->Name) + + ""); + } else { + extra_markup.push_back("Changed the channel"); + } + } + } + } break; + case AuditLogActionType::WEBHOOK_DELETE: { + markup = user_markup + + " deleted the webhook " + + Glib::Markup::escape_text(*entry.GetOldFromKey("name")) + + ""; + } break; + case AuditLogActionType::EMOJI_CREATE: { + markup = user_markup + + " created the emoji " + + Glib::Markup::escape_text(*entry.GetNewFromKey("name")) + + ""; + } break; + case AuditLogActionType::EMOJI_UPDATE: { + markup = user_markup + + " updated the emoji " + + Glib::Markup::escape_text(*entry.GetOldFromKey("name")) + + ""; + extra_markup.push_back("Changed the name from " + + Glib::Markup::escape_text(*entry.GetOldFromKey("name")) + + " to " + + Glib::Markup::escape_text(*entry.GetNewFromKey("name")) + + ""); + } break; + case AuditLogActionType::EMOJI_DELETE: { + markup = user_markup + + " deleted the emoji " + + Glib::Markup::escape_text(*entry.GetOldFromKey("name")) + + ""; + } 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 " + count + " messages in #" + + Glib::Markup::escape_text(*channel->Name) + + ""; + } else { + markup = user_markup + + " deleted " + count + " messages"; + } + } break; + case AuditLogActionType::MESSAGE_BULK_DELETE: { + const auto channel = discord.GetChannel(entry.TargetID); + if (channel.has_value()) { + markup = user_markup + + " deleted " + + *entry.Options->Count + + " messages in #" + + Glib::Markup::escape_text(*channel->Name) + + ""; + } else { + markup = user_markup + + " deleted " + + *entry.Options->Count + + " messages"; + } + } break; + case AuditLogActionType::MESSAGE_PIN: { + const auto target_user = discord.GetUser(entry.TargetID); + markup = user_markup + + " pinned a message by " + + target_user->GetEscapedString() + + ""; + } break; + case AuditLogActionType::MESSAGE_UNPIN: { + const auto target_user = discord.GetUser(entry.TargetID); + markup = user_markup + + " unpinned a message by " + + target_user->GetEscapedString() + + ""; + } 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 " + + Glib::Markup::escape_text(*channel->Name) + + ""; + } else { + markup = user_markup + + " started the stage for " + + std::to_string(*entry.Options->ChannelID) + + ""; + } + + 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 " + + Glib::Markup::escape_text(change.NewValue->get()) + + ""); + } else if (change.Key == "privacy_level" && change.NewValue.has_value()) { + Glib::ustring str = Glib::Markup::escape_text(GetStagePrivacyDisplayString(change.NewValue->get())); + extra_markup.push_back( + "Set the privacy level to " + + str + + ""); + } + } + } + } 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 " + + Glib::Markup::escape_text(*channel->Name) + + ""; + } else { + markup = user_markup + + " updated the stage for " + + std::to_string(*entry.Options->ChannelID) + + ""; + } + + 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 " + + Glib::Markup::escape_text(change.NewValue->get()) + + ""); + } else if (change.Key == "privacy_level" && change.NewValue.has_value()) { + Glib::ustring str = Glib::Markup::escape_text(GetStagePrivacyDisplayString(change.NewValue->get())); + extra_markup.push_back( + "Set the privacy level to " + + str + + ""); + } + } + } + } 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 " + + Glib::Markup::escape_text(*channel->Name) + + ""; + } else { + markup = user_markup + + " ended the stage for " + + std::to_string(*entry.Options->ChannelID) + + ""; + } + } break; + case AuditLogActionType::THREAD_CREATE: { + const auto channel = discord.GetChannel(entry.TargetID); + markup = user_markup + + " created a thread " + + (channel.has_value() + ? Glib::Markup::escape_text(*channel->Name) + : Glib::ustring(*entry.GetNewFromKey("name"))) + + ""; + if (entry.Changes.has_value()) { + for (const auto &change : *entry.Changes) { + if (change.Key == "name") + extra_markup.push_back("Set the name to " + Glib::Markup::escape_text(change.NewValue->get()) + ""); + else if (change.Key == "archived") + extra_markup.push_back(change.NewValue->get() ? "Archived the thread" : "Unarchived the thread"); + else if (change.Key == "auto_archive_duration") + extra_markup.push_back("Set auto archive duration to "s + std::to_string(change.NewValue->get()) + " minutes"s); + else if (change.Key == "rate_limit_per_user" && change.NewValue.has_value()) { + const int secs = change.NewValue->get(); + if (secs == 0) + extra_markup.push_back("Disabled slowmode"); + else + extra_markup.push_back("Set slowmode to " + + std::to_string(secs) + " seconds"); + } else if (change.Key == "locked") + extra_markup.push_back(change.NewValue->get() ? "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 " + + (channel.has_value() + ? Glib::Markup::escape_text(*channel->Name) + : Glib::ustring(entry.TargetID)) + + ""; + for (const auto &change : *entry.Changes) { + if (change.Key == "name") + extra_markup.push_back( + "Changed the name from " + + Glib::Markup::escape_text(change.OldValue->get()) + + " to " + + Glib::Markup::escape_text(change.NewValue->get()) + + ""); + else if (change.Key == "auto_archive_duration") + extra_markup.push_back("Set auto archive duration to "s + std::to_string(change.NewValue->get()) + " minutes"s); + else if (change.Key == "rate_limit_per_user" && change.NewValue.has_value()) { + const int secs = change.NewValue->get(); + if (secs == 0) + extra_markup.push_back("Disabled slowmode"); + else + extra_markup.push_back("Set slowmode to " + + std::to_string(secs) + + " seconds"); + } else if (change.Key == "locked") + extra_markup.push_back(change.NewValue->get() ? "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() ? "Archived the thread" : "Unarchived the thread"); + } + } break; + case AuditLogActionType::THREAD_DELETE: { + markup = user_markup + + " deleted the thread " + Glib::Markup::escape_text(*entry.GetOldFromKey("name")) + ""; + } break; + default: + markup = "Unknown action"; + break; + } + + label->set_markup(markup); + expander->set_label_widget(*label); + + if (entry.Reason.has_value()) { + extra_markup.push_back("With reason " + + Glib::Markup::escape_text(*entry.Reason) + + ""); + } + + 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); + } +} diff --git a/src/windows/guildsettings/auditlogpane.hpp b/src/windows/guildsettings/auditlogpane.hpp new file mode 100644 index 0000000..ac12321 --- /dev/null +++ b/src/windows/guildsettings/auditlogpane.hpp @@ -0,0 +1,19 @@ +#pragma once +#include +#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; +}; diff --git a/src/windows/guildsettings/banspane.cpp b/src/windows/guildsettings/banspane.cpp new file mode 100644 index 0000000..97a70c4 --- /dev/null +++ b/src/windows/guildsettings/banspane.cpp @@ -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 &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(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(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(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); +} diff --git a/src/windows/guildsettings/banspane.hpp b/src/windows/guildsettings/banspane.hpp new file mode 100644 index 0000000..b2420a9 --- /dev/null +++ b/src/windows/guildsettings/banspane.hpp @@ -0,0 +1,45 @@ +#pragma once +#include +#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 &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 m_col_user; + Gtk::TreeModelColumn m_col_reason; + Gtk::TreeModelColumn m_col_id; + }; + + ModelColumns m_columns; + Glib::RefPtr m_model; + + Gtk::Menu m_menu; + Gtk::MenuItem m_menu_unban; + Gtk::MenuItem m_menu_copy_id; +}; diff --git a/src/windows/guildsettings/emojispane.cpp b/src/windows/guildsettings/emojispane.cpp new file mode 100644 index 0000000..1f4bfa9 --- /dev/null +++ b/src/windows/guildsettings/emojispane.cpp @@ -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 &pb) { + for (auto &row : m_model->children()) { + if (static_cast(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 &pb) { + for (auto &row : m_model->children()) { + if (static_cast(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 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(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(selected_row[m_columns.m_col_name]); + const auto id = static_cast(selected_row[m_columns.m_col_id]); + if (auto *window = dynamic_cast(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(selected_row[m_columns.m_col_id]); + const bool is_animated = static_cast(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(selected_row[m_columns.m_col_id]); + const bool is_animated = static_cast(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(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); +} diff --git a/src/windows/guildsettings/emojispane.hpp b/src/windows/guildsettings/emojispane.hpp new file mode 100644 index 0000000..1c0edd1 --- /dev/null +++ b/src/windows/guildsettings/emojispane.hpp @@ -0,0 +1,53 @@ +#pragma once +#include +#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 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> m_col_pixbuf; + Gtk::TreeModelColumn> m_col_pixbuf_animation; + Gtk::TreeModelColumn m_col_name; + Gtk::TreeModelColumn m_col_creator; + Gtk::TreeModelColumn m_col_animated; + Gtk::TreeModelColumn m_col_available; + Gtk::TreeModelColumn m_col_id; + }; + + ModelColumns m_columns; + Glib::RefPtr m_model; + Glib::RefPtr 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; +}; diff --git a/src/windows/guildsettings/infopane.cpp b/src/windows/guildsettings/infopane.cpp new file mode 100644 index 0000000..b4f75f3 --- /dev/null +++ b/src/windows/guildsettings/infopane.cpp @@ -0,0 +1,220 @@ +#include "infopane.hpp" +#include "abaddon.hpp" +#include + +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 &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 &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 &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 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 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 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; + } +} diff --git a/src/windows/guildsettings/infopane.hpp b/src/windows/guildsettings/infopane.hpp new file mode 100644 index 0000000..8a7e6a2 --- /dev/null +++ b/src/windows/guildsettings/infopane.hpp @@ -0,0 +1,26 @@ +#pragma once +#include +#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 &data, const std::string &mime); + void UpdateGuildIconFromPixbuf(Glib::RefPtr 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; +}; diff --git a/src/windows/guildsettings/invitespane.cpp b/src/windows/guildsettings/invitespane.cpp new file mode 100644 index 0000000..bec4784 --- /dev/null +++ b/src/windows/guildsettings/invitespane.cpp @@ -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 &invite) { + if (!invite.has_value()) return; + AppendInvite(*invite); +} + +void GuildSettingsInvitesPane::OnInvitesFetch(const std::vector &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(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(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); +} diff --git a/src/windows/guildsettings/invitespane.hpp b/src/windows/guildsettings/invitespane.hpp new file mode 100644 index 0000000..5268d68 --- /dev/null +++ b/src/windows/guildsettings/invitespane.hpp @@ -0,0 +1,43 @@ +#pragma once +#include +#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 &invite); + void OnInvitesFetch(const std::vector &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 m_col_code; + Gtk::TreeModelColumn m_col_expires; + Gtk::TreeModelColumn m_col_inviter; + Gtk::TreeModelColumn m_col_temporary; + Gtk::TreeModelColumn m_col_uses; + Gtk::TreeModelColumn m_col_max_uses; + }; + + ModelColumns m_columns; + Glib::RefPtr m_model; + + Gtk::Menu m_menu; + Gtk::MenuItem m_menu_delete; +}; diff --git a/src/windows/guildsettings/memberspane.cpp b/src/windows/guildsettings/memberspane.cpp new file mode 100644 index 0000000..36c5c0b --- /dev/null +++ b/src/windows/guildsettings/memberspane.cpp @@ -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(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(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(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("" + user.GetEscapedBoldString() + ""); + } else + m_name.set_markup(user.GetEscapedBoldString()); +} + +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(a); + auto *rowb = dynamic_cast(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 &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 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("" + Glib::Markup::escape_text(m_role.Name) + ""); + 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; +} diff --git a/src/windows/guildsettings/memberspane.hpp b/src/windows/guildsettings/memberspane.hpp new file mode 100644 index 0000000..01398da --- /dev/null +++ b/src/windows/guildsettings/memberspane.hpp @@ -0,0 +1,135 @@ +#pragma once +#include +#include +#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 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 &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 m_update_connection; + + std::unordered_set m_set_role_ids; + + Snowflake GuildID; + Snowflake UserID; + + Gtk::ListBox m_list; + + std::unordered_map 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 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; +}; diff --git a/src/windows/guildsettings/rolespane.cpp b/src/windows/guildsettings/rolespane.cpp new file mode 100644 index 0000000..8d355ee --- /dev/null +++ b/src/windows/guildsettings/rolespane.cpp @@ -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 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(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(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(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(rowa_); + auto *rowb = dynamic_cast(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(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("" + + Glib::Markup::escape_text(role.Name) + + ""); + 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 &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 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; +} diff --git a/src/windows/guildsettings/rolespane.hpp b/src/windows/guildsettings/rolespane.hpp new file mode 100644 index 0000000..2999f32 --- /dev/null +++ b/src/windows/guildsettings/rolespane.hpp @@ -0,0 +1,102 @@ +#pragma once +#include +#include +#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 type_signal_role_select; + type_signal_role_select m_signal_role_select; + +public: + std::unordered_map 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 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 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 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; +}; -- cgit v1.2.3 From 4326c5e29b279ba8ca58139848aaea4e3c62fb03 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 24 Nov 2021 03:14:41 -0500 Subject: remove SimpleIni as a dependency use Glib::KeyFile instead which is basically the same file format also read into and save from struct once, cuz its faster and less redundant --- .gitmodules | 3 - CMakeLists.txt | 7 -- README.md | 13 ++- cmake/Findsimpleini.cmake | 15 --- src/abaddon.cpp | 33 +++--- src/abaddon.hpp | 5 +- src/components/channels.cpp | 14 +-- src/components/chatmessage.cpp | 19 +-- src/components/friendslist.cpp | 3 +- src/components/memberlist.cpp | 6 +- src/dialogs/friendpicker.cpp | 2 +- src/discord/discord.cpp | 6 +- src/filecache.cpp | 2 +- src/settings.cpp | 188 +++++++++++++++--------------- src/settings.hpp | 87 +++++++------- src/windows/guildsettings/emojispane.cpp | 3 +- src/windows/guildsettings/infopane.cpp | 2 +- src/windows/guildsettings/memberspane.cpp | 5 +- src/windows/profile/mutualfriendspane.cpp | 3 +- src/windows/profile/mutualguildspane.cpp | 3 +- src/windows/profilewindow.cpp | 3 +- subprojects/simpleini | 1 - 22 files changed, 187 insertions(+), 236 deletions(-) delete mode 100644 cmake/Findsimpleini.cmake delete mode 160000 subprojects/simpleini (limited to 'src/windows/guildsettings') diff --git a/.gitmodules b/.gitmodules index 412fb9a..65a1996 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,9 +7,6 @@ [submodule "ci/gtk-for-windows"] path = ci/gtk-for-windows url = https://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer -[submodule "subprojects/simpleini"] - path = subprojects/simpleini - url = https://github.com/brofield/simpleini [submodule "subprojects/ixwebsocket"] path = subprojects/ixwebsocket url = https://github.com/machinezone/ixwebsocket diff --git a/CMakeLists.txt b/CMakeLists.txt index d27980b..fa56d6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,13 +22,6 @@ if (NOT IXWebSocket_FOUND) include_directories(IXWEBSOCKET_INCLUDE_DIRS) endif() -add_compile_definitions(SI_NO_CONVERSION) # only CSimpleIniA is used -find_package(simpleini QUIET) -if (NOT simpleini_FOUND) - message("simpleini was not found and will be included as a submodule") - include_directories(subprojects/simpleini) -endif() - if(MINGW OR WIN32) link_libraries(ws2_32) endif() diff --git a/README.md b/README.md index 7a890a8..5274c0a 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Current features: ### Building manually (recommended if not on Windows): #### Windows: 1. `git clone https://github.com/uowuo/abaddon && cd abaddon` -2. `vcpkg install gtkmm:x64-windows nlohmann-json:x64-windows ixwebsocket:x64-windows zlib:x64-windows simpleini:x64-windows sqlite3:x64-windows openssl:x64-windows curl:x64-windows` +2. `vcpkg install gtkmm:x64-windows nlohmann-json:x64-windows ixwebsocket:x64-windows zlib:x64-windows sqlite3:x64-windows openssl:x64-windows curl:x64-windows` 3. `mkdir build && cd build` 4. `cmake -G"Visual Studio 16 2019" -A x64 -DCMAKE_TOOLCHAIN_FILE=c:\path\to\vcpkg\scripts\buildsystems\vcpkg.cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DVCPKG_TARGET_TRIPLET=x64-windows ..` 5. Build with Visual Studio @@ -75,7 +75,6 @@ On Linux, `css` and `res` can also be loaded from `~/.local/share/abaddon` or `/ * [IXWebSocket](https://github.com/machinezone/IXWebSocket) * [libcurl](https://curl.se/) * [zlib](https://zlib.net/) -* [simpleini](https://github.com/brofield/simpleini) * [SQLite3](https://www.sqlite.org/index.html) ### TODO: @@ -178,18 +177,24 @@ Used in profile popup: ### Settings Settings are configured (for now) by editing abaddon.ini +The format is similar to the standard Windows ini format **except**: +* `#` is used to begin comments as opposed to `;` +* Section and key names are case-sensitive + You should edit these while the client is closed even though there's an option to reload while running This listing is organized by section. For example, memory_db would be set by adding `memory_db = true` under the line `[discord]` #### discord +* gateway (string) - override url for Discord gateway. must be json format and use zlib stream compression +* api_base (string) - override base url for Discord API * memory_db (true or false, default false) - if true, Discord data will be kept in memory as opposed to on disk * token (string) - Discord token used to login, this can be set from the menu * prefetch (true or false, default false) - if true, new messages will cause the avatar and image attachments to be automatically downloaded #### http * user_agent (string) - sets the user-agent to use in HTTP requests to the Discord API (not including media/images) -* concurrent (int, default 10) - how many images can be concurrently retrieved +* concurrent (int, default 20) - how many images can be concurrently retrieved #### gui * member_list_discriminator (true or false, default true) - show user discriminators in the member list @@ -199,8 +204,6 @@ For example, memory_db would be set by adding `memory_db = true` under the line * animations (true or false, default true) - use animated images where available (e.g. server icons, emojis, avatars). false means static images will be used * animated_guild_hover_only (true or false, default true) - only animate guild icons when the guild is being hovered over * owner_crown (true or false, default true) - show a crown next to the owner -* gateway (string) - override url for Discord gateway. must be json format and use zlib stream compression -* api_base (string) - override base url for Discord API #### style * linkcolor (string) - color to use for links in messages diff --git a/cmake/Findsimpleini.cmake b/cmake/Findsimpleini.cmake deleted file mode 100644 index fa6598a..0000000 --- a/cmake/Findsimpleini.cmake +++ /dev/null @@ -1,15 +0,0 @@ -set(simpleini_LIBRARY_NAME simpleini) - -find_path(simpleini_INCLUDE_DIR - NAMES SimpleIni.h - HINTS /usr/include - /usr/local/include - /opt/local/include - PATH_SUFFIXES ${simpleini_LIBRARY_NAME}) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(simpleini - REQUIRED_VARS - simpleini_INCLUDE_DIR) - -mark_as_advanced(simpleini_INCLUDE_DIR) diff --git a/src/abaddon.cpp b/src/abaddon.cpp index f0f8574..f6c9ef5 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -23,12 +23,12 @@ Abaddon::Abaddon() : m_settings(Platform::FindConfigFile()) - , m_discord(m_settings.GetUseMemoryDB()) // stupid but easy + , m_discord(GetSettings().UseMemoryDB) // stupid but easy , m_emojis(GetResPath("/emojis.bin")) { LoadFromSettings(); // todo: set user agent for non-client(?) - std::string ua = m_settings.GetUserAgent(); + std::string ua = GetSettings().UserAgent; m_discord.SetUserAgent(ua); m_discord.signal_gateway_ready().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnReady)); @@ -43,7 +43,7 @@ Abaddon::Abaddon() m_discord.signal_thread_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnThreadUpdate)); m_discord.signal_message_sent().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnMessageSent)); m_discord.signal_disconnected().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnDisconnect)); - if (m_settings.GetPrefetch()) + if (GetSettings().Prefetch) m_discord.signal_message_create().connect([this](const Message &message) { if (message.Author.HasAvatar()) m_img_mgr.Prefetch(message.Author.GetAvatarURL()); @@ -54,10 +54,6 @@ Abaddon::Abaddon() }); } -Abaddon::~Abaddon() { - m_settings.Close(); -} - Abaddon &Abaddon::Get() { static Abaddon instance; return instance; @@ -85,7 +81,7 @@ int Abaddon::StartGTK() { m_main_window->set_position(Gtk::WIN_POS_CENTER); if (!m_settings.IsValid()) { - Gtk::MessageDialog dlg(*m_main_window, "The settings file could not be created!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); + Gtk::MessageDialog dlg(*m_main_window, "The settings file could not be opened!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); dlg.set_position(Gtk::WIN_POS_CENTER); dlg.run(); } @@ -133,14 +129,19 @@ int Abaddon::StartGTK() { ActionReloadCSS(); - m_gtk_app->signal_shutdown().connect(sigc::mem_fun(*this, &Abaddon::StopDiscord), false); + m_gtk_app->signal_shutdown().connect(sigc::mem_fun(*this, &Abaddon::OnShutdown), false); m_main_window->show(); return m_gtk_app->run(*m_main_window); } +void Abaddon::OnShutdown() { + StopDiscord(); + m_settings.Close(); +} + void Abaddon::LoadFromSettings() { - std::string token = m_settings.GetDiscordToken(); + std::string token = GetSettings().DiscordToken; if (token.size()) { m_discord_token = token; m_discord.UpdateToken(m_discord_token); @@ -248,8 +249,8 @@ void Abaddon::DiscordOnThreadUpdate(const ThreadUpdateData &data) { } } -const SettingsManager &Abaddon::GetSettings() const { - return m_settings; +SettingsManager::Settings &Abaddon::GetSettings() { + return m_settings.GetSettings(); } Glib::RefPtr Abaddon::GetStyleProvider() { @@ -367,7 +368,7 @@ void Abaddon::SetupUserMenu() { } void Abaddon::SaveState() { - if (!m_settings.GetSaveState()) return; + if (!GetSettings().SaveState) return; AbaddonApplicationState state; state.ActiveChannel = m_main_window->GetChatActiveChannel(); @@ -387,7 +388,7 @@ void Abaddon::SaveState() { } void Abaddon::LoadState() { - if (!m_settings.GetSaveState()) return; + if (!GetSettings().SaveState) return; const auto data = ReadWholeFile(GetStateCachePath("/state.json")); if (data.empty()) return; @@ -491,7 +492,7 @@ void Abaddon::ActionSetToken() { m_discord_token = dlg.GetToken(); m_discord.UpdateToken(m_discord_token); m_main_window->UpdateComponents(); - m_settings.SetSetting("discord", "token", m_discord_token); + GetSettings().DiscordToken = m_discord_token; } } @@ -698,7 +699,7 @@ bool Abaddon::ShowConfirm(const Glib::ustring &prompt, Gtk::Window *window) { void Abaddon::ActionReloadCSS() { try { Gtk::StyleContext::remove_provider_for_screen(Gdk::Screen::get_default(), m_css_provider); - m_css_provider->load_from_path(GetCSSPath("/" + m_settings.GetMainCSS())); + m_css_provider->load_from_path(GetCSSPath("/" + GetSettings().MainCSS)); Gtk::StyleContext::add_provider_for_screen(Gdk::Screen::get_default(), m_css_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); Gtk::StyleContext::remove_provider_for_screen(Gdk::Screen::get_default(), m_css_low_provider); diff --git a/src/abaddon.hpp b/src/abaddon.hpp index 0fb4f1f..d9d0bb0 100644 --- a/src/abaddon.hpp +++ b/src/abaddon.hpp @@ -14,7 +14,6 @@ class Abaddon { private: Abaddon(); - ~Abaddon(); Abaddon(const Abaddon &) = delete; Abaddon &operator=(const Abaddon &) = delete; Abaddon(Abaddon &&) = delete; @@ -24,6 +23,8 @@ public: static Abaddon &Get(); int StartGTK(); + void OnShutdown(); + void StartDiscord(); void StopDiscord(); @@ -74,7 +75,7 @@ public: void DiscordOnDisconnect(bool is_reconnecting, GatewayCloseCode close_code); void DiscordOnThreadUpdate(const ThreadUpdateData &data); - const SettingsManager &GetSettings() const; + SettingsManager::Settings &GetSettings(); Glib::RefPtr GetStyleProvider(); diff --git a/src/components/channels.cpp b/src/components/channels.cpp index da31de0..6d5e1a6 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -263,11 +263,9 @@ void ChannelList::UpdateGuild(Snowflake id) { const auto guild = Abaddon::Get().GetDiscordClient().GetGuild(id); if (!iter || !guild.has_value()) return; - static const bool show_animations = Abaddon::Get().GetSettings().GetShowAnimations(); - (*iter)[m_columns.m_name] = "" + Glib::Markup::escape_text(guild->Name) + ""; (*iter)[m_columns.m_icon] = img.GetPlaceholder(GuildIconSize); - if (show_animations && guild->HasAnimatedIcon()) { + if (Abaddon::Get().GetSettings().ShowAnimations && guild->HasAnimatedIcon()) { const auto cb = [this, id](const Glib::RefPtr &pb) { auto iter = GetIteratorForGuildFromID(id); if (iter) (*iter)[m_columns.m_icon_anim] = pb; @@ -436,9 +434,7 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) { guild_row[m_columns.m_name] = "" + Glib::Markup::escape_text(guild.Name) + ""; guild_row[m_columns.m_icon] = img.GetPlaceholder(GuildIconSize); - static const bool show_animations = Abaddon::Get().GetSettings().GetShowAnimations(); - - if (show_animations && guild.HasAnimatedIcon()) { + if (Abaddon::Get().GetSettings().ShowAnimations && guild.HasAnimatedIcon()) { const auto cb = [this, id = guild.ID](const Glib::RefPtr &pb) { auto iter = GetIteratorForGuildFromID(id); if (iter) (*iter)[m_columns.m_icon_anim] = pb; @@ -998,7 +994,7 @@ void CellRendererChannels::render_vfunc_guild(const Cairo::RefPtrmove_to(x1, y1); cr->line_to(x2, y2); cr->line_to(x3, y3); - static const auto expander_color = Gdk::RGBA(Abaddon::Get().GetSettings().GetChannelsExpanderColor()); + const auto expander_color = Gdk::RGBA(Abaddon::Get().GetSettings().ChannelsExpanderColor); cr->set_source_rgb(expander_color.get_red(), expander_color.get_green(), expander_color.get_blue()); cr->stroke(); @@ -1115,7 +1111,7 @@ void CellRendererChannels::render_vfunc_channel(const Cairo::RefPtroverride_color(Gdk::RGBA(color)); title_label->set_markup("" + Glib::Markup::escape_text(*embed.Title) + ""); } @@ -798,7 +798,6 @@ void ChatMessageItemContainer::HandleCustomEmojis(Gtk::TextView &tv) { int mstart, mend; if (!match.fetch_pos(0, mstart, mend)) break; const bool is_animated = match.fetch(0)[1] == 'a'; - const bool show_animations = Abaddon::Get().GetSettings().GetShowAnimations(); const auto chars_start = g_utf8_pointer_to_offset(text.c_str(), text.c_str() + mstart); const auto chars_end = g_utf8_pointer_to_offset(text.c_str(), text.c_str() + mend); @@ -806,7 +805,7 @@ void ChatMessageItemContainer::HandleCustomEmojis(Gtk::TextView &tv) { auto end_it = buf->get_iter_at_offset(chars_end); startpos = mend; - if (is_animated && show_animations) { + if (is_animated && Abaddon::Get().GetSettings().ShowAnimations) { const auto mark_start = buf->create_mark(start_it, false); end_it.backward_char(); const auto mark_end = buf->create_mark(end_it, false); @@ -845,11 +844,8 @@ void ChatMessageItemContainer::HandleCustomEmojis(Gtk::TextView &tv) { } void ChatMessageItemContainer::HandleEmojis(Gtk::TextView &tv) { - static const bool stock_emojis = Abaddon::Get().GetSettings().GetShowStockEmojis(); - static const bool custom_emojis = Abaddon::Get().GetSettings().GetShowCustomEmojis(); - - if (stock_emojis) HandleStockEmojis(tv); - if (custom_emojis) HandleCustomEmojis(tv); + if (Abaddon::Get().GetSettings().ShowStockEmojis) HandleStockEmojis(tv); + if (Abaddon::Get().GetSettings().ShowCustomEmojis) HandleCustomEmojis(tv); } void ChatMessageItemContainer::CleanupEmojis(Glib::RefPtr buf) { @@ -969,9 +965,6 @@ void ChatMessageItemContainer::HandleLinks(Gtk::TextView &tv) { auto buf = tv.get_buffer(); Glib::ustring text = GetText(buf); - // i'd like to let this be done thru css like .message-link { color: #bitch; } but idk how - static auto link_color = Abaddon::Get().GetSettings().GetLinkColor(); - int startpos = 0; Glib::MatchInfo match; while (rgx->match(text, startpos, match)) { @@ -980,7 +973,7 @@ void ChatMessageItemContainer::HandleLinks(Gtk::TextView &tv) { std::string link = match.fetch(0); auto tag = buf->create_tag(); m_link_tagmap[tag] = link; - tag->property_foreground_rgba() = Gdk::RGBA(link_color); + tag->property_foreground_rgba() = Gdk::RGBA(Abaddon::Get().GetSettings().LinkColor); tag->set_property("underline", 1); // stupid workaround for vcpkg bug (i think) const auto chars_start = g_utf8_pointer_to_offset(text.c_str(), text.c_str() + mstart); @@ -1138,7 +1131,7 @@ ChatMessageHeader::ChatMessageHeader(const Message &data) m_content_box_ev.add_events(Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK); m_meta_ev.add_events(Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK); m_avatar_ev.add_events(Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK); - if (Abaddon::Get().GetSettings().GetShowAnimations()) { + if (Abaddon::Get().GetSettings().ShowAnimations) { m_content_box_ev.signal_enter_notify_event().connect(on_enter_cb); m_content_box_ev.signal_leave_notify_event().connect(on_leave_cb); m_meta_ev.signal_enter_notify_event().connect(on_enter_cb); diff --git a/src/components/friendslist.cpp b/src/components/friendslist.cpp index 3896f02..1331d19 100644 --- a/src/components/friendslist.cpp +++ b/src/components/friendslist.cpp @@ -257,8 +257,7 @@ FriendsListFriendRow::FriendsListFriendRow(RelationshipType type, const UserData auto &discord = Abaddon::Get().GetDiscordClient(); discord.signal_presence_update().connect(sigc::mem_fun(*this, &FriendsListFriendRow::OnPresenceUpdate)); - static bool show_animations = Abaddon::Get().GetSettings().GetShowAnimations(); - if (data.HasAnimatedAvatar() && show_animations) { + if (data.HasAnimatedAvatar() && Abaddon::Get().GetSettings().ShowAnimations) { img->SetAnimated(true); img->SetURL(data.GetAvatarURL("gif", "32")); } else { diff --git a/src/components/memberlist.cpp b/src/components/memberlist.cpp index 0c4d9bc..4f21700 100644 --- a/src/components/memberlist.cpp +++ b/src/components/memberlist.cpp @@ -14,8 +14,7 @@ MemberListUserRow::MemberListUserRow(const std::optional &guild, cons m_avatar = Gtk::manage(new LazyImage(16, 16)); m_status_indicator = Gtk::manage(new StatusIndicator(ID)); - static bool crown = Abaddon::Get().GetSettings().GetShowOwnerCrown(); - if (crown && guild.has_value() && guild->OwnerID == data.ID) { + if (Abaddon::Get().GetSettings().ShowOwnerCrown && guild.has_value() && guild->OwnerID == data.ID) { try { const static auto crown_path = Abaddon::GetResPath("/crown.png"); auto pixbuf = Gdk::Pixbuf::create_from_file(crown_path, 12, 12); @@ -40,9 +39,8 @@ MemberListUserRow::MemberListUserRow(const std::optional &guild, cons m_label->set_single_line_mode(true); m_label->set_ellipsize(Pango::ELLIPSIZE_END); - static bool show_discriminator = Abaddon::Get().GetSettings().GetShowMemberListDiscriminators(); std::string display = data.Username; - if (show_discriminator) + if (Abaddon::Get().GetSettings().ShowMemberListDiscriminators) display += "#" + data.Discriminator; if (guild.has_value()) { if (const auto col_id = data.GetHoistedRole(guild->ID, true); col_id.IsValid()) { diff --git a/src/dialogs/friendpicker.cpp b/src/dialogs/friendpicker.cpp index fc099aa..476e5f6 100644 --- a/src/dialogs/friendpicker.cpp +++ b/src/dialogs/friendpicker.cpp @@ -67,7 +67,7 @@ FriendPickerDialogItem::FriendPickerDialogItem(Snowflake user_id) m_name.set_single_line_mode(true); m_avatar.property_pixbuf() = Abaddon::Get().GetImageManager().GetPlaceholder(32); - if (user.HasAnimatedAvatar() && Abaddon::Get().GetSettings().GetShowAnimations()) { + if (user.HasAnimatedAvatar() && Abaddon::Get().GetSettings().ShowAnimations) { auto cb = [this](const Glib::RefPtr &pb) { m_avatar.property_pixbuf_animation() = pb; }; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index bed959f..f920099 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1303,13 +1303,11 @@ void DiscordClient::HandleGatewayHello(const GatewayMessage &msg) { // perhaps this should be set by the main class std::string DiscordClient::GetAPIURL() { - static const auto url = Abaddon::Get().GetSettings().GetAPIBaseURL(); - return url; + return Abaddon::Get().GetSettings().APIBaseURL; } std::string DiscordClient::GetGatewayURL() { - static const auto url = Abaddon::Get().GetSettings().GetGatewayURL(); - return url; + return Abaddon::Get().GetSettings().GatewayURL; } DiscordError DiscordClient::GetCodeFromResponse(const http::response_type &response) { diff --git a/src/filecache.cpp b/src/filecache.cpp index a731750..e04fbcb 100644 --- a/src/filecache.cpp +++ b/src/filecache.cpp @@ -142,7 +142,7 @@ void FileCacheWorkerThread::loop() { m_cv.wait(lock); } - static const auto concurrency = static_cast(Abaddon::Get().GetSettings().GetCacheHTTPConcurrency()); + static const auto concurrency = static_cast(Abaddon::Get().GetSettings().CacheHTTPConcurrency); if (m_handles.size() < concurrency) { std::optional entry; m_queue_mutex.lock(); diff --git a/src/settings.cpp b/src/settings.cpp index 0a7dbb7..beef624 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -2,7 +2,7 @@ #include #include -SettingsManager::SettingsManager(std::string filename) +SettingsManager::SettingsManager(std::string_view filename) : m_filename(filename) { if (!std::filesystem::exists(filename)) { std::fstream fs; @@ -10,106 +10,106 @@ SettingsManager::SettingsManager(std::string filename) fs.close(); } - auto rc = m_ini.LoadFile(filename.c_str()); - m_ok = rc == SI_OK; -} - -void SettingsManager::Reload() { - m_ok = m_ini.LoadFile(m_filename.c_str()) == SI_OK; -} - -std::string SettingsManager::GetSettingString(const std::string §ion, const std::string &key, std::string fallback) const { - return m_ini.GetValue(section.c_str(), key.c_str(), fallback.c_str()); -} - -int SettingsManager::GetSettingInt(const std::string §ion, const std::string &key, int fallback) const { - return std::stoul(GetSettingString(section, key, std::to_string(fallback))); -} + try { + m_ok = m_file.load_from_file(m_filename, Glib::KEY_FILE_KEEP_COMMENTS); + } catch (const Glib::Error &e) { + fprintf(stderr, "error opening settings KeyFile: %s\n", e.what().c_str()); + m_ok = false; + } -bool SettingsManager::GetSettingBool(const std::string §ion, const std::string &key, bool fallback) const { - return GetSettingString(section, key, fallback ? "true" : "false") != "false"; + if (m_ok) ReadSettings(); +} + +void SettingsManager::ReadSettings() { +#define SMBOOL(section, key, var) \ + try { \ + m_settings.var = m_file.get_boolean(section, key); \ + } catch (...) {} +#define SMSTR(section, key, var) \ + try { \ + m_settings.var = m_file.get_string(section, key); \ + } catch (...) {} +#define SMINT(section, key, var) \ + try { \ + m_settings.var = m_file.get_integer(section, key); \ + } catch (...) {} + + SMSTR("discord", "api_base", APIBaseURL); + SMSTR("discord", "gateway", GatewayURL); + SMSTR("discord", "token", DiscordToken); + SMBOOL("discord", "memory_db", UseMemoryDB); + SMBOOL("discord", "prefetch", Prefetch); + SMSTR("gui", "css", MainCSS); + SMBOOL("gui", "animated_guild_hover_only", AnimatedGuildHoverOnly); + SMBOOL("gui", "animations", ShowAnimations); + SMBOOL("gui", "custom_emojis", ShowCustomEmojis); + SMBOOL("gui", "member_list_discriminator", ShowMemberListDiscriminators); + SMBOOL("gui", "owner_crown", ShowOwnerCrown); + SMBOOL("gui", "save_state", SaveState); + SMBOOL("gui", "stock_emojis", ShowStockEmojis); + SMINT("http", "concurrent", CacheHTTPConcurrency); + SMSTR("http", "user_agent", UserAgent); + SMSTR("style", "expandercolor", ChannelsExpanderColor); + SMSTR("style", "linkcolor", LinkColor); + SMSTR("style", "nsfwchannelcolor", NSFWChannelColor); + +#undef SMBOOL +#undef SMSTR +#undef SMINT + + m_read_settings = m_settings; } bool SettingsManager::IsValid() const { return m_ok; } -void SettingsManager::Close() { - m_ini.SaveFile(m_filename.c_str()); -} - -bool SettingsManager::GetUseMemoryDB() const { - return GetSettingBool("discord", "memory_db", false); -} - -std::string SettingsManager::GetUserAgent() const { - return GetSettingString("http", "user_agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36"); -} - -std::string SettingsManager::GetDiscordToken() const { - return GetSettingString("discord", "token"); -} - -bool SettingsManager::GetShowMemberListDiscriminators() const { - return GetSettingBool("gui", "member_list_discriminator", true); -} - -bool SettingsManager::GetShowStockEmojis() const { -#ifdef _WIN32 - return GetSettingBool("gui", "stock_emojis", false); -#else - return GetSettingBool("gui", "stock_emojis", true); -#endif -} - -bool SettingsManager::GetShowCustomEmojis() const { - return GetSettingBool("gui", "custom_emojis", true); -} - -std::string SettingsManager::GetLinkColor() const { - return GetSettingString("style", "linkcolor", "rgba(40, 200, 180, 255)"); +SettingsManager::Settings &SettingsManager::GetSettings() { + return m_settings; } -std::string SettingsManager::GetChannelsExpanderColor() const { - return GetSettingString("style", "expandercolor", "rgba(255, 83, 112, 255)"); -} - -std::string SettingsManager::GetNSFWChannelColor() const { - return GetSettingString("style", "nsfwchannelcolor", "#ed6666"); -} - -int SettingsManager::GetCacheHTTPConcurrency() const { - return GetSettingInt("http", "concurrent", 20); -} - -bool SettingsManager::GetPrefetch() const { - return GetSettingBool("discord", "prefetch", false); -} - -std::string SettingsManager::GetMainCSS() const { - return GetSettingString("gui", "css", "main.css"); -} - -bool SettingsManager::GetShowAnimations() const { - return GetSettingBool("gui", "animations", true); -} - -bool SettingsManager::GetShowOwnerCrown() const { - return GetSettingBool("gui", "owner_crown", true); -} - -std::string SettingsManager::GetGatewayURL() const { - return GetSettingString("discord", "gateway", "wss://gateway.discord.gg/?v=9&encoding=json&compress=zlib-stream"); -} - -std::string SettingsManager::GetAPIBaseURL() const { - return GetSettingString("discord", "api_base", "https://discord.com/api/v9"); -} - -bool SettingsManager::GetAnimatedGuildHoverOnly() const { - return GetSettingBool("gui", "animated_guild_hover_only", true); -} - -bool SettingsManager::GetSaveState() const { - return GetSettingBool("gui", "save_state", true); +void SettingsManager::Close() { + if (m_ok) { + // save anything that changed + // (futureproofing since only DiscordToken can actually change) +#define SMSTR(section, key, var) \ + if (m_settings.var != m_read_settings.var) \ + m_file.set_string(section, key, m_settings.var); +#define SMBOOL(section, key, var) \ + if (m_settings.var != m_read_settings.var) \ + m_file.set_boolean(section, key, m_settings.var); +#define SMINT(section, key, var) \ + if (m_settings.var != m_read_settings.var) \ + m_file.set_integer(section, key, m_settings.var); + + SMSTR("discord", "api_base", APIBaseURL); + SMSTR("discord", "gateway", GatewayURL); + SMSTR("discord", "token", DiscordToken); + SMBOOL("discord", "memory_db", UseMemoryDB); + SMBOOL("discord", "prefetch", Prefetch); + SMSTR("gui", "css", MainCSS); + SMBOOL("gui", "animated_guild_hover_only", AnimatedGuildHoverOnly); + SMBOOL("gui", "animations", ShowAnimations); + SMBOOL("gui", "custom_emojis", ShowCustomEmojis); + SMBOOL("gui", "member_list_discriminator", ShowMemberListDiscriminators); + SMBOOL("gui", "owner_crown", ShowOwnerCrown); + SMBOOL("gui", "save_state", SaveState); + SMBOOL("gui", "stock_emojis", ShowStockEmojis); + SMINT("http", "concurrent", CacheHTTPConcurrency); + SMSTR("http", "user_agent", UserAgent); + SMSTR("style", "expandercolor", ChannelsExpanderColor); + SMSTR("style", "linkcolor", LinkColor); + SMSTR("style", "nsfwchannelcolor", NSFWChannelColor); + +#undef SMSTR +#undef SMBOOL +#undef SMINT + + try { + if (!m_file.save_to_file(m_filename)) + fputs("failed to save settings KeyFile", stderr); + } catch (const Glib::Error &e) { + fprintf(stderr, "failed to save settings KeyFile: %s\n", e.what().c_str()); + } + } } diff --git a/src/settings.hpp b/src/settings.hpp index 3fff593..1192861 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -1,62 +1,55 @@ #pragma once #include #include -#include +#include class SettingsManager { public: - SettingsManager(std::string filename); - void Reload(); + struct Settings { + // [discord] + std::string APIBaseURL { "https://discord.com/api/v9" }; + std::string GatewayURL { "wss://gateway.discord.gg/?v=9&encoding=json&compress=zlib-stream" }; + std::string DiscordToken; + bool UseMemoryDB { false }; + bool Prefetch { false }; + + // [gui] + std::string MainCSS { "main.css" }; + bool AnimatedGuildHoverOnly { true }; + bool ShowAnimations { true }; + bool ShowCustomEmojis { true }; + bool ShowMemberListDiscriminators { true }; + bool ShowOwnerCrown { true }; + bool SaveState { true }; +#ifdef _WIN32 + bool ShowStockEmojis { false }; +#else + bool ShowStockEmojis { true }; +#endif + + // [http] + int CacheHTTPConcurrency { 20 }; + std::string UserAgent { "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36" }; + + // [style] + // TODO: convert to StyleProperty + std::string LinkColor { "rgba(40, 200, 180, 255)" }; + std::string ChannelsExpanderColor { "rgba(255, 83, 112, 255)" }; + std::string NSFWChannelColor { "#ed6666" }; + }; + + SettingsManager(std::string_view filename); void Close(); - bool GetUseMemoryDB() const; - std::string GetUserAgent() const; - std::string GetDiscordToken() const; - bool GetShowMemberListDiscriminators() const; - bool GetShowStockEmojis() const; - bool GetShowCustomEmojis() const; - int GetCacheHTTPConcurrency() const; - bool GetPrefetch() const; - std::string GetMainCSS() const; - bool GetShowAnimations() const; - bool GetShowOwnerCrown() const; - std::string GetGatewayURL() const; - std::string GetAPIBaseURL() const; - bool GetAnimatedGuildHoverOnly() const; - bool GetSaveState() const; - - // i would like to use Gtk::StyleProperty for this, but it will not work on windows - // #1 it's missing from the project files for the version used by vcpkg - // #2 it's still broken and doesn't function even when added to the solution - // #3 it's a massive pain in the ass to try and bump the version to a functioning version - // because they switch build systems to nmake/meson (took months to get merged in vcpkg) - // #4 c++ build systems sucks - // three options are: use gtk4 with updated vcpkg, try and port it myself, or use msys2 instead of vcpkg - // im leaning towards msys - std::string GetLinkColor() const; - std::string GetChannelsExpanderColor() const; - std::string GetNSFWChannelColor() const; - bool IsValid() const; - - template - void SetSetting(std::string section, std::string key, T value) { - m_ini.SetValue(section.c_str(), key.c_str(), std::to_string(value).c_str()); - m_ini.SaveFile(m_filename.c_str()); - } - - void SetSetting(std::string section, std::string key, std::string value) { - m_ini.SetValue(section.c_str(), key.c_str(), value.c_str()); - m_ini.SaveFile(m_filename.c_str()); - } + Settings &GetSettings(); private: - std::string GetSettingString(const std::string §ion, const std::string &key, std::string fallback = "") const; - int GetSettingInt(const std::string §ion, const std::string &key, int fallback) const; - bool GetSettingBool(const std::string §ion, const std::string &key, bool fallback) const; + void ReadSettings(); -private: bool m_ok; std::string m_filename; - CSimpleIniA m_ini; + Glib::KeyFile m_file; + Settings m_settings; + Settings m_read_settings; }; diff --git a/src/windows/guildsettings/emojispane.cpp b/src/windows/guildsettings/emojispane.cpp index 1f4bfa9..57b697c 100644 --- a/src/windows/guildsettings/emojispane.cpp +++ b/src/windows/guildsettings/emojispane.cpp @@ -130,8 +130,7 @@ void GuildSettingsEmojisPane::AddEmojiRow(const EmojiData &emoji) { 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) { + if (Abaddon::Get().GetSettings().ShowAnimations && emoji.IsAnimated.has_value() && *emoji.IsAnimated) { const auto cb = [this, id = emoji.ID](const Glib::RefPtr &pb) { for (auto &row : m_model->children()) { if (static_cast(row[m_columns.m_col_id]) == id) { diff --git a/src/windows/guildsettings/infopane.cpp b/src/windows/guildsettings/infopane.cpp index b4f75f3..9ef116f 100644 --- a/src/windows/guildsettings/infopane.cpp +++ b/src/windows/guildsettings/infopane.cpp @@ -81,7 +81,7 @@ GuildSettingsInfoPane::GuildSettingsInfoPane(Snowflake id) 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()) { + if (Abaddon::Get().GetSettings().ShowAnimations && guild.HasAnimatedIcon()) { auto cb = [this](const Glib::RefPtr &pixbuf) { m_guild_icon.property_pixbuf_animation() = pixbuf; }; diff --git a/src/windows/guildsettings/memberspane.cpp b/src/windows/guildsettings/memberspane.cpp index 36c5c0b..9dc76d3 100644 --- a/src/windows/guildsettings/memberspane.cpp +++ b/src/windows/guildsettings/memberspane.cpp @@ -99,7 +99,7 @@ GuildSettingsMembersListItem::GuildSettingsMembersListItem(const GuildData &guil auto &discord = Abaddon::Get().GetDiscordClient(); - if (member.User->HasAnimatedAvatar() && Abaddon::Get().GetSettings().GetShowAnimations()) + if (member.User->HasAnimatedAvatar() && Abaddon::Get().GetSettings().ShowAnimations) m_avatar.SetURL(member.User->GetAvatarURL("gif", "32")); else m_avatar.SetURL(member.User->GetAvatarURL("png", "32")); @@ -113,8 +113,7 @@ GuildSettingsMembersListItem::GuildSettingsMembersListItem(const GuildData &guil 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) { + if (Abaddon::Get().GetSettings().ShowOwnerCrown && 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); diff --git a/src/windows/profile/mutualfriendspane.cpp b/src/windows/profile/mutualfriendspane.cpp index 339fd71..ca36e1d 100644 --- a/src/windows/profile/mutualfriendspane.cpp +++ b/src/windows/profile/mutualfriendspane.cpp @@ -9,10 +9,9 @@ MutualFriendItem::MutualFriendItem(const UserData &user) 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) { + if (user.HasAnimatedAvatar() && Abaddon::Get().GetSettings().ShowAnimations) { auto cb = [this](const Glib::RefPtr &pb) { m_avatar.property_pixbuf_animation() = pb; }; diff --git a/src/windows/profile/mutualguildspane.cpp b/src/windows/profile/mutualguildspane.cpp index 6bfdc7b..6c14fc4 100644 --- a/src/windows/profile/mutualguildspane.cpp +++ b/src/windows/profile/mutualguildspane.cpp @@ -13,11 +13,10 @@ MutualGuildItem::MutualGuildItem(const MutualGuildData &guild) // 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) { + if (data->HasAnimatedIcon() && Abaddon::Get().GetSettings().ShowAnimations) { auto cb = [this](const Glib::RefPtr &pb) { m_icon.property_pixbuf_animation() = pb; }; diff --git a/src/windows/profilewindow.cpp b/src/windows/profilewindow.cpp index 4d41f89..9d93564 100644 --- a/src/windows/profilewindow.cpp +++ b/src/windows/profilewindow.cpp @@ -44,7 +44,6 @@ ProfileWindow::ProfileWindow(Snowflake user_id) 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 &pb) { @@ -52,7 +51,7 @@ ProfileWindow::ProfileWindow(Snowflake user_id) }; img.LoadFromURL(user.GetAvatarURL("png", "64"), sigc::track_obj(icon_cb, *this)); - if (show_animations && user.HasAnimatedAvatar()) { + if (Abaddon::Get().GetSettings().ShowAnimations && user.HasAnimatedAvatar()) { auto cb = [this](const Glib::RefPtr &pb) { m_avatar.property_pixbuf_animation() = pb; }; diff --git a/subprojects/simpleini b/subprojects/simpleini deleted file mode 160000 index 67156f6..0000000 --- a/subprojects/simpleini +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 67156f64b3447ce1eb81d6be44d29132fb49b70a -- cgit v1.2.3 From e02107feea8214a045e6faa969f00dcbc0d2b072 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 28 Nov 2021 22:42:55 -0500 Subject: actually retrieve roles for guilds FetchRoles isnt needed anymore cuz full roles are fetched now --- src/discord/discord.cpp | 4 +++- src/discord/guild.cpp | 15 --------------- src/discord/guild.hpp | 3 +-- src/discord/store.cpp | 30 +++++++++++++++++++++++++++--- src/discord/store.hpp | 2 ++ src/windows/guildsettings/memberspane.cpp | 7 ++++--- src/windows/guildsettings/rolespane.cpp | 25 +++++++++++++------------ 7 files changed, 50 insertions(+), 36 deletions(-) (limited to 'src/windows/guildsettings') diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 83db97b..b678de0 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -720,7 +720,9 @@ void DiscordClient::ModifyRoleColor(Snowflake guild_id, Snowflake role_id, Gdk:: } void DiscordClient::ModifyRolePosition(Snowflake guild_id, Snowflake role_id, int position, sigc::slot callback) { - const auto roles = GetGuild(guild_id)->FetchRoles(); + const auto guild = GetGuild(guild_id); + if (!guild.has_value() || !guild->Roles.has_value()) return; + const auto &roles = *guild->Roles; if (static_cast(position) > roles.size()) return; // gay and makes you send every role in between new and old position constexpr auto IDX_MAX = ~size_t { 0 }; diff --git a/src/discord/guild.cpp b/src/discord/guild.cpp index a02b896..966bd44 100644 --- a/src/discord/guild.cpp +++ b/src/discord/guild.cpp @@ -188,21 +188,6 @@ std::vector GuildData::GetSortedChannels(Snowflake ignore) const { return ret; } -std::vector GuildData::FetchRoles() const { - if (!Roles.has_value()) return {}; - std::vector ret; - ret.reserve(Roles->size()); - for (const auto thing : *Roles) { - auto r = Abaddon::Get().GetDiscordClient().GetRole(thing.ID); - if (r.has_value()) - ret.push_back(*r); - } - std::sort(ret.begin(), ret.end(), [](const RoleData &a, const RoleData &b) -> bool { - return a.Position > b.Position; - }); - return ret; -} - void from_json(const nlohmann::json &j, GuildApplicationData &m) { JS_D("user_id", m.UserID); JS_D("guild_id", m.GuildID); diff --git a/src/discord/guild.hpp b/src/discord/guild.hpp index 3c3828d..51b5a01 100644 --- a/src/discord/guild.hpp +++ b/src/discord/guild.hpp @@ -50,7 +50,7 @@ struct GuildData { std::optional VerificationLevel; std::optional DefaultMessageNotifications; std::optional ExplicitContentFilter; - std::optional> Roles; // only access id + std::optional> Roles; std::optional> Emojis; // only access id std::optional> Features; std::optional MFALevel; @@ -96,5 +96,4 @@ struct GuildData { bool HasAnimatedIcon() const; std::string GetIconURL(std::string ext = "png", std::string size = "32") const; std::vector GetSortedChannels(Snowflake ignore = Snowflake::Invalid) const; - std::vector FetchRoles() const; // sorted }; diff --git a/src/discord/store.cpp b/src/discord/store.cpp index 63fb5f7..1cb7231 100644 --- a/src/discord/store.cpp +++ b/src/discord/store.cpp @@ -765,6 +765,16 @@ std::optional Store::GetGuild(Snowflake id) const { s->Reset(); } + { + auto &s = m_stmt_get_guild_roles; + s->Bind(1, id); + r.Roles.emplace(); + while (s->FetchOne()) { + r.Roles->push_back(GetRoleBound(s)); + } + s->Reset(); + } + return r; } @@ -961,9 +971,17 @@ std::optional Store::GetRole(Snowflake id) const { return {}; } + auto role = GetRoleBound(s); + + s->Reset(); + + return role; +} + +RoleData Store::GetRoleBound(std::unique_ptr &s) const { RoleData r; - r.ID = id; + s->Get(0, r.ID); //s->Get(1, guild id); s->Get(2, r.Name); s->Get(3, r.Color); @@ -973,8 +991,6 @@ std::optional Store::GetRole(Snowflake id) const { s->Get(7, r.IsManaged); s->Get(8, r.IsMentionable); - s->Reset(); - return r; } @@ -1726,6 +1742,14 @@ bool Store::CreateStatements() { return false; } + m_stmt_get_guild_roles = std::make_unique(m_db, R"( + SELECT * FROM roles WHERE guild = ? + )"); + if (!m_stmt_get_guild_roles->OK()) { + fprintf(stderr, "failed to prepare get guild roles statement: %s\n", m_db.ErrStr()); + return false; + } + m_stmt_set_emoji = std::make_unique(m_db, R"( REPLACE INTO emojis VALUES ( ?, ?, ?, ?, ?, ?, ? diff --git a/src/discord/store.hpp b/src/discord/store.hpp index 80e2407..715f280 100644 --- a/src/discord/store.hpp +++ b/src/discord/store.hpp @@ -235,6 +235,7 @@ private: }; Message GetMessageBound(std::unique_ptr &stmt) const; + RoleData GetRoleBound(std::unique_ptr &stmt) const; void SetMessageInteractionPair(Snowflake message_id, const MessageInteractionData &interaction); @@ -264,6 +265,7 @@ private: STMT(get_member); STMT(set_role); STMT(get_role); + STMT(get_guild_roles); STMT(set_emoji); STMT(get_emoji); STMT(set_perm); diff --git a/src/windows/guildsettings/memberspane.cpp b/src/windows/guildsettings/memberspane.cpp index 9dc76d3..bda92b4 100644 --- a/src/windows/guildsettings/memberspane.cpp +++ b/src/windows/guildsettings/memberspane.cpp @@ -238,9 +238,10 @@ GuildSettingsMembersPaneRoles::GuildSettingsMembersPaneRoles(Snowflake guild_id) 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); + if (guild.Roles.has_value()) { + for (const auto &role : *guild.Roles) { + CreateRow(can_modify, role, guild.OwnerID == self_id); + } } m_list.set_sort_func([this](Gtk::ListBoxRow *a, Gtk::ListBoxRow *b) -> int { diff --git a/src/windows/guildsettings/rolespane.cpp b/src/windows/guildsettings/rolespane.cpp index 8d355ee..3567e95 100644 --- a/src/windows/guildsettings/rolespane.cpp +++ b/src/windows/guildsettings/rolespane.cpp @@ -79,19 +79,20 @@ GuildSettingsRolesPaneRoles::GuildSettingsRolesPaneRoles(Snowflake guild_id) 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); + if (guild.Roles.has_value()) { + for (const auto &role : *guild.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 { -- cgit v1.2.3