summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorouwou <26526779+ouwou@users.noreply.github.com>2021-01-15 06:37:35 +0000
committerGitHub <noreply@github.com>2021-01-15 06:37:35 +0000
commit462f801af20562f151cd7ad7fa70aa65695b3e22 (patch)
treeec5ab7509bc9478ac82715fe9e74381bbfc4a9df
parentf51ab48009e7fcfafee1d09800764fcbd5d36bf5 (diff)
downloadabaddon-portaudio-462f801af20562f151cd7ad7fa70aa65695b3e22.tar.gz
abaddon-portaudio-462f801af20562f151cd7ad7fa70aa65695b3e22.zip
Guild settings 1 (#19)
* start guild settings window, tweak style
-rw-r--r--.github/workflows/ci.yml2
-rw-r--r--.gitmodules3
-rw-r--r--CMakeLists.txt2
-rw-r--r--abaddon.cpp11
-rw-r--r--abaddon.hpp3
m---------ci/gtk-for-windows0
-rw-r--r--components/channels.cpp20
-rw-r--r--components/channels.hpp8
-rw-r--r--css/main.css30
-rw-r--r--dialogs/confirm.cpp1
-rw-r--r--dialogs/editmessage.cpp1
-rw-r--r--dialogs/joinguild.cpp1
-rw-r--r--dialogs/setstatus.cpp1
-rw-r--r--dialogs/token.cpp1
-rw-r--r--discord/discord.cpp30
-rw-r--r--discord/discord.hpp4
-rw-r--r--discord/guild.cpp7
-rw-r--r--discord/guild.hpp1
-rw-r--r--discord/objects.cpp5
-rw-r--r--discord/objects.hpp8
-rw-r--r--util.cpp11
-rw-r--r--util.hpp1
-rw-r--r--windows/guildsettings/infopane.cpp206
-rw-r--r--windows/guildsettings/infopane.hpp24
-rw-r--r--windows/guildsettingswindow.cpp45
-rw-r--r--windows/guildsettingswindow.hpp18
-rw-r--r--windows/mainwindow.cpp1
27 files changed, 441 insertions, 4 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 84d93e0..0d4870c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -43,6 +43,8 @@ jobs:
del /f /s /q "${{ runner.workspace }}\build\CMakeCache.txt"
xcopy /E /I "${{ github.workspace }}\css" "${{ runner.workspace }}\build\css"
xcopy /E /I "${{ github.workspace }}\res" "${{ runner.workspace }}\build\res"
+ mkdir "${{ runner.workspace }}\build\share"
+ xcopy /E /I "${{ github.workspace }}\ci\gtk-for-windows\gtk-nsis-pack\share\glib-2.0" "${{ runner.workspace }}\build\share\glib-2.0"
copy "${{ github.workspace }}\ci\vcpkg\installed\x64-windows\tools\glib\gspawn-win64-helper.exe" "${{ runner.workspace }}\build\gspawn-win64-helper.exe"
copy "${{ github.workspace }}\ci\vcpkg\installed\x64-windows\tools\glib\gspawn-win64-helper-console.exe" "${{ runner.workspace }}\build\gspawn-win64-helper-console.exe"
diff --git a/.gitmodules b/.gitmodules
index 58279b0..bcbf397 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -10,3 +10,6 @@
[submodule "ci/vcpkg"]
path = ci/vcpkg
url = https://github.com/microsoft/vcpkg
+[submodule "ci/gtk-for-windows"]
+ path = ci/gtk-for-windows
+ url = https://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8a348ad..80e617e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -43,6 +43,8 @@ file(GLOB ABADDON_SOURCES
"components/*.cpp"
"windows/*.hpp"
"windows/*.cpp"
+ "windows/guildsettings/*.hpp"
+ "windows/guildsettings/*.cpp"
"dialogs/*.hpp"
"dialogs/*.cpp"
)
diff --git a/abaddon.cpp b/abaddon.cpp
index 06a9912..2578e8e 100644
--- a/abaddon.cpp
+++ b/abaddon.cpp
@@ -9,6 +9,7 @@
#include "dialogs/confirm.hpp"
#include "dialogs/setstatus.hpp"
#include "abaddon.hpp"
+#include "windows/guildsettingswindow.hpp"
#ifdef _WIN32
#pragma comment(lib, "crypt32.lib")
@@ -105,6 +106,7 @@ int Abaddon::StartGTK() {
m_main_window->GetChannelList()->signal_action_channel_item_select().connect(sigc::mem_fun(*this, &Abaddon::ActionChannelOpened));
m_main_window->GetChannelList()->signal_action_guild_leave().connect(sigc::mem_fun(*this, &Abaddon::ActionLeaveGuild));
+ m_main_window->GetChannelList()->signal_action_guild_settings().connect(sigc::mem_fun(*this, &Abaddon::ActionGuildSettings));
m_main_window->GetChatWindow()->signal_action_message_delete().connect(sigc::mem_fun(*this, &Abaddon::ActionChatDeleteMessage));
m_main_window->GetChatWindow()->signal_action_message_edit().connect(sigc::mem_fun(*this, &Abaddon::ActionChatEditMessage));
@@ -231,6 +233,10 @@ const SettingsManager &Abaddon::GetSettings() const {
return m_settings;
}
+Glib::RefPtr<Gtk::CssProvider> Abaddon::GetStyleProvider() {
+ return m_css_provider;
+}
+
void Abaddon::ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_id) {
m_shown_user_menu_id = id;
m_shown_user_menu_guild_id = guild_id;
@@ -455,6 +461,11 @@ void Abaddon::ActionReactionRemove(Snowflake id, const Glib::ustring &param) {
m_discord.RemoveReaction(id, param);
}
+void Abaddon::ActionGuildSettings(Snowflake id) {
+ auto *window = new GuildSettingsWindow(id);
+ window->show();
+}
+
void Abaddon::ActionReloadSettings() {
m_settings.Reload();
}
diff --git a/abaddon.hpp b/abaddon.hpp
index 649578e..4b409a3 100644
--- a/abaddon.hpp
+++ b/abaddon.hpp
@@ -45,6 +45,7 @@ public:
void ActionSetStatus();
void ActionReactionAdd(Snowflake id, const Glib::ustring &param);
void ActionReactionRemove(Snowflake id, const Glib::ustring &param);
+ void ActionGuildSettings(Snowflake id);
void ActionReloadSettings();
void ActionReloadCSS();
@@ -73,6 +74,8 @@ public:
const SettingsManager &GetSettings() const;
+ Glib::RefPtr<Gtk::CssProvider> GetStyleProvider();
+
protected:
Snowflake m_shown_user_menu_id;
Snowflake m_shown_user_menu_guild_id;
diff --git a/ci/gtk-for-windows b/ci/gtk-for-windows
new file mode 160000
+Subproject 27bc126a0d9f1a3ac46c6cd7ce2e6b9eea81312
diff --git a/components/channels.cpp b/components/channels.cpp
index 64613a4..0819f8c 100644
--- a/components/channels.cpp
+++ b/components/channels.cpp
@@ -117,6 +117,12 @@ ChannelListRowGuild::ChannelListRowGuild(const GuildData *data) {
});
m_menu.append(*m_menu_leave);
+ m_menu_settings = Gtk::manage(new Gtk::MenuItem("Guild _Settings", true));
+ m_menu_settings->signal_activate().connect([this]() {
+ m_signal_settings.emit();
+ });
+ m_menu.append(*m_menu_settings);
+
m_menu.show_all();
const auto show_animations = Abaddon::Get().GetSettings().GetShowAnimations();
@@ -176,6 +182,10 @@ ChannelListRowGuild::type_signal_leave ChannelListRowGuild::signal_leave() {
return m_signal_leave;
}
+ChannelListRowGuild::type_signal_settings ChannelListRowGuild::signal_settings() {
+ return m_signal_settings;
+}
+
ChannelListRowCategory::ChannelListRowCategory(const ChannelData *data) {
ID = data->ID;
m_ev = Gtk::manage(new Gtk::EventBox);
@@ -519,6 +529,7 @@ void ChannelList::UpdateGuild(Snowflake id) {
m_guild_id_to_row[new_row->ID] = new_row;
new_row->signal_leave().connect(sigc::bind(sigc::mem_fun(*this, &ChannelList::OnGuildMenuLeave), new_row->ID));
new_row->signal_copy_id().connect(sigc::bind(sigc::mem_fun(*this, &ChannelList::OnMenuCopyID), new_row->ID));
+ new_row->signal_settings().connect(sigc::bind(sigc::mem_fun(*this, &ChannelList::OnGuildMenuSettings), new_row->ID));
new_row->Children = children;
for (auto child : children)
child->Parent = new_row;
@@ -615,6 +626,7 @@ void ChannelList::InsertGuildAt(Snowflake id, int pos) {
m_guild_id_to_row[guild_row->ID] = guild_row;
guild_row->signal_leave().connect(sigc::bind(sigc::mem_fun(*this, &ChannelList::OnGuildMenuLeave), guild_row->ID));
guild_row->signal_copy_id().connect(sigc::bind(sigc::mem_fun(*this, &ChannelList::OnMenuCopyID), guild_row->ID));
+ guild_row->signal_settings().connect(sigc::bind(sigc::mem_fun(*this, &ChannelList::OnGuildMenuSettings), guild_row->ID));
// add channels with no parent category
for (const auto &[pos, channel] : orphan_channels) {
@@ -726,6 +738,10 @@ void ChannelList::OnGuildMenuLeave(Snowflake id) {
m_signal_action_guild_leave.emit(id);
}
+void ChannelList::OnGuildMenuSettings(Snowflake id) {
+ m_signal_action_guild_settings.emit(id);
+}
+
void ChannelList::CheckBumpDM(Snowflake channel_id) {
auto it = m_dm_id_to_row.find(channel_id);
if (it == m_dm_id_to_row.end()) return;
@@ -756,3 +772,7 @@ ChannelList::type_signal_action_channel_item_select ChannelList::signal_action_c
ChannelList::type_signal_action_guild_leave ChannelList::signal_action_guild_leave() {
return m_signal_action_guild_leave;
}
+
+ChannelList::type_signal_action_guild_settings ChannelList::signal_action_guild_settings() {
+ return m_signal_action_guild_settings;
+}
diff --git a/components/channels.hpp b/components/channels.hpp
index 15632ab..8f73a50 100644
--- a/components/channels.hpp
+++ b/components/channels.hpp
@@ -64,17 +64,21 @@ protected:
Gtk::Menu m_menu;
Gtk::MenuItem *m_menu_copyid;
Gtk::MenuItem *m_menu_leave;
+ Gtk::MenuItem *m_menu_settings;
private:
typedef sigc::signal<void> type_signal_copy_id;
typedef sigc::signal<void> type_signal_leave;
+ typedef sigc::signal<void> type_signal_settings;
type_signal_copy_id m_signal_copy_id;
type_signal_leave m_signal_leave;
+ type_signal_settings m_signal_settings;
public:
type_signal_copy_id signal_copy_id();
type_signal_leave signal_leave();
+ type_signal_settings signal_settings();
};
class ChannelListRowCategory : public ChannelListRow {
@@ -156,6 +160,7 @@ protected:
int m_guild_count;
void OnMenuCopyID(Snowflake id);
void OnGuildMenuLeave(Snowflake id);
+ void OnGuildMenuSettings(Snowflake id);
Gtk::Menu m_channel_menu;
Gtk::MenuItem *m_channel_menu_copyid;
@@ -179,11 +184,14 @@ protected:
public:
typedef sigc::signal<void, Snowflake> type_signal_action_channel_item_select;
typedef sigc::signal<void, Snowflake> type_signal_action_guild_leave;
+ typedef sigc::signal<void, Snowflake> type_signal_action_guild_settings;
type_signal_action_channel_item_select signal_action_channel_item_select();
type_signal_action_guild_leave signal_action_guild_leave();
+ type_signal_action_guild_settings signal_action_guild_settings();
protected:
type_signal_action_channel_item_select m_signal_action_channel_item_select;
type_signal_action_guild_leave m_signal_action_guild_leave;
+ type_signal_action_guild_settings m_signal_action_guild_settings;
};
diff --git a/css/main.css b/css/main.css
index d7d4108..b1f8705 100644
--- a/css/main.css
+++ b/css/main.css
@@ -162,16 +162,40 @@
color: @text_color;
}
-paned separator {
+.app-window label:not(:disabled) {
+ color: @text_color;
+}
+
+.app-window entry {
+ background: @secondary_color;
+ color: @text_color;
+ border: 1px solid #1c2e40;
+}
+
+.app-window button {
+ background: @secondary_color;
+ color: @text_color;
+ border: 1px solid #1c2e40;
+}
+
+.app-window.background {
+ background: @background_color;
+}
+
+.app-window listbox {
+ background: @background_color;
+}
+
+.app-window paned separator {
background: #37474f;
}
-scrollbar {
+.app-window scrollbar {
background: @background_color;
border-left: 1px solid transparent;
}
-menubar, menu {
+.app-window menubar, menu {
background: @background_color;
color: #cccccc;
}
diff --git a/dialogs/confirm.cpp b/dialogs/confirm.cpp
index 0881dd9..68902a5 100644
--- a/dialogs/confirm.cpp
+++ b/dialogs/confirm.cpp
@@ -7,6 +7,7 @@ ConfirmDialog::ConfirmDialog(Gtk::Window &parent)
, m_ok("OK")
, m_cancel("Cancel") {
set_default_size(300, 50);
+ get_style_context()->add_class("app-window");
m_label.set_text("Are you sure?");
diff --git a/dialogs/editmessage.cpp b/dialogs/editmessage.cpp
index a5f583d..23f0fe1 100644
--- a/dialogs/editmessage.cpp
+++ b/dialogs/editmessage.cpp
@@ -7,6 +7,7 @@ EditMessageDialog::EditMessageDialog(Gtk::Window &parent)
, m_ok("OK")
, m_cancel("Cancel") {
set_default_size(300, 50);
+ get_style_context()->add_class("app-window");
m_ok.signal_clicked().connect([&]() {
m_content = m_text.get_buffer()->get_text();
diff --git a/dialogs/joinguild.cpp b/dialogs/joinguild.cpp
index 5fbc6c0..6fd21a8 100644
--- a/dialogs/joinguild.cpp
+++ b/dialogs/joinguild.cpp
@@ -10,6 +10,7 @@ JoinGuildDialog::JoinGuildDialog(Gtk::Window &parent)
, m_cancel("Cancel")
, m_info("Enter code") {
set_default_size(300, 50);
+ get_style_context()->add_class("app-window");
Glib::signal_idle().connect(sigc::mem_fun(*this, &JoinGuildDialog::on_idle_slot));
diff --git a/dialogs/setstatus.cpp b/dialogs/setstatus.cpp
index cbd42ff..4c36325 100644
--- a/dialogs/setstatus.cpp
+++ b/dialogs/setstatus.cpp
@@ -8,6 +8,7 @@ SetStatusDialog::SetStatusDialog(Gtk::Window &parent)
, m_ok("OK")
, m_cancel("Cancel") {
set_default_size(300, 50);
+ get_style_context()->add_class("app-window");
m_text.set_placeholder_text("Status text");
diff --git a/dialogs/token.cpp b/dialogs/token.cpp
index ca016a0..b36bb84 100644
--- a/dialogs/token.cpp
+++ b/dialogs/token.cpp
@@ -7,6 +7,7 @@ TokenDialog::TokenDialog(Gtk::Window &parent)
, m_ok("OK")
, m_cancel("Cancel") {
set_default_size(300, 50);
+ get_style_context()->add_class("app-window");
m_ok.signal_clicked().connect([&]() {
m_token = m_entry.get_text();
diff --git a/discord/discord.cpp b/discord/discord.cpp
index 4e1a35e..a3acd2a 100644
--- a/discord/discord.cpp
+++ b/discord/discord.cpp
@@ -437,6 +437,36 @@ void DiscordClient::RemoveReaction(Snowflake id, Glib::ustring param) {
m_http.MakeDELETE("/channels/" + std::to_string(channel_id) + "/messages/" + std::to_string(id) + "/reactions/" + param + "/@me", [](auto) {});
}
+void DiscordClient::SetGuildName(Snowflake id, const Glib::ustring &name) {
+ SetGuildName(id, name, [](auto) {});
+}
+
+void DiscordClient::SetGuildName(Snowflake id, const Glib::ustring &name, sigc::slot<void(bool success)> callback) {
+ ModifyGuildObject obj;
+ obj.Name = name;
+ sigc::signal<void, bool> signal;
+ signal.connect(callback);
+ m_http.MakePATCH("/guilds/" + std::to_string(id), nlohmann::json(obj).dump(), [this, signal](const cpr::Response &r) {
+ const auto success = r.status_code == 200;
+ signal.emit(success);
+ });
+}
+
+void DiscordClient::SetGuildIcon(Snowflake id, const std::string &data) {
+ SetGuildIcon(id, data, [](auto) {});
+}
+
+void DiscordClient::SetGuildIcon(Snowflake id, const std::string &data, sigc::slot<void(bool success)> callback) {
+ ModifyGuildObject obj;
+ obj.IconData = data;
+ sigc::signal<void, bool> signal;
+ signal.connect(callback);
+ m_http.MakePATCH("/guilds/" + std::to_string(id), nlohmann::json(obj).dump(), [this, signal](const cpr::Response &r) {
+ const auto success = r.status_code == 200;
+ signal.emit(success);
+ });
+}
+
void DiscordClient::UpdateToken(std::string token) {
if (!IsStarted()) {
m_token = token;
diff --git a/discord/discord.hpp b/discord/discord.hpp
index c2b2bef..5b08bef 100644
--- a/discord/discord.hpp
+++ b/discord/discord.hpp
@@ -110,6 +110,10 @@ public:
std::optional<Snowflake> FindDM(Snowflake user_id); // wont find group dms
void AddReaction(Snowflake id, Glib::ustring param);
void RemoveReaction(Snowflake id, Glib::ustring param);
+ void SetGuildName(Snowflake id, const Glib::ustring &name);
+ void SetGuildName(Snowflake id, const Glib::ustring &name, sigc::slot<void(bool success)> callback);
+ void SetGuildIcon(Snowflake id, const std::string &data);
+ void SetGuildIcon(Snowflake id, const std::string &data, sigc::slot<void(bool success)> callback);
void UpdateToken(std::string token);
void SetUserAgent(std::string agent);
diff --git a/discord/guild.cpp b/discord/guild.cpp
index 40762ad..8b8a5c2 100644
--- a/discord/guild.cpp
+++ b/discord/guild.cpp
@@ -118,6 +118,13 @@ void GuildData::update_from_json(const nlohmann::json &j) {
JS_RD("approximate_presence_count", ApproximatePresenceCount);
}
+bool GuildData::HasFeature(const std::string &search_feature) {
+ for (const auto &feature : Features)
+ if (search_feature == feature)
+ return true;
+ return false;
+}
+
bool GuildData::HasIcon() const {
return Icon != "";
}
diff --git a/discord/guild.hpp b/discord/guild.hpp
index c8676b3..eeb62cf 100644
--- a/discord/guild.hpp
+++ b/discord/guild.hpp
@@ -67,6 +67,7 @@ struct GuildData {
friend void from_json(const nlohmann::json &j, GuildData &m);
void update_from_json(const nlohmann::json &j);
+ bool HasFeature(const std::string &feature);
bool HasIcon() const;
bool HasAnimatedIcon() const;
std::string GetIconURL(std::string ext = "png", std::string size = "32") const;
diff --git a/discord/objects.cpp b/discord/objects.cpp
index cf0f504..07e616b 100644
--- a/discord/objects.cpp
+++ b/discord/objects.cpp
@@ -247,3 +247,8 @@ void from_json(const nlohmann::json &j, TypingStartObject &m) {
JS_D("timestamp", m.Timestamp);
JS_O("member", m.Member);
}
+
+void to_json(nlohmann::json &j, const ModifyGuildObject &m) {
+ JS_IF("name", m.Name);
+ JS_IF("icon", m.IconData);
+}
diff --git a/discord/objects.hpp b/discord/objects.hpp
index 9c5e648..89770a5 100644
--- a/discord/objects.hpp
+++ b/discord/objects.hpp
@@ -345,3 +345,11 @@ struct TypingStartObject {
friend void from_json(const nlohmann::json &j, TypingStartObject &m);
};
+
+// implement rest as needed
+struct ModifyGuildObject {
+ std::optional<std::string> Name;
+ std::optional<std::string> IconData;
+
+ friend void to_json(nlohmann::json &j, const ModifyGuildObject &m);
+};
diff --git a/util.cpp b/util.cpp
index bd70a21..38e65c5 100644
--- a/util.cpp
+++ b/util.cpp
@@ -100,8 +100,17 @@ std::string IntToCSSColor(int color) {
}
void AddWidgetMenuHandler(Gtk::Widget *widget, Gtk::Menu &menu) {
- widget->signal_button_press_event().connect([&menu](GdkEventButton *ev) -> bool {
+ AddWidgetMenuHandler(widget, menu, []() {});
+}
+
+// so widgets can modify the menu before it is displayed
+// maybe theres a better way to do this idk
+void AddWidgetMenuHandler(Gtk::Widget *widget, Gtk::Menu &menu, sigc::slot<void()> pre_callback) {
+ sigc::signal<void()> signal;
+ signal.connect(pre_callback);
+ widget->signal_button_press_event().connect([&menu, signal](GdkEventButton *ev) -> bool {
if (ev->type == GDK_BUTTON_PRESS && ev->button == GDK_BUTTON_SECONDARY) {
+ signal.emit();
menu.popup_at_pointer(reinterpret_cast<const GdkEvent *>(ev));
return true;
}
diff --git a/util.hpp b/util.hpp
index f5f9edf..cab3461 100644
--- a/util.hpp
+++ b/util.hpp
@@ -39,6 +39,7 @@ void LaunchBrowser(Glib::ustring url);
void GetImageDimensions(int inw, int inh, int &outw, int &outh, int clampw = 400, int clamph = 300);
std::string IntToCSSColor(int color);
void AddWidgetMenuHandler(Gtk::Widget *widget, Gtk::Menu &menu);
+void AddWidgetMenuHandler(Gtk::Widget *widget, Gtk::Menu &menu, sigc::slot<void()> pre_callback);
std::vector<std::string> StringSplit(const std::string &str, const char *delim);
std::string GetExtension(std::string url);
bool IsURLViewableImage(const std::string &url);
diff --git a/windows/guildsettings/infopane.cpp b/windows/guildsettings/infopane.cpp
new file mode 100644
index 0000000..862c082
--- /dev/null
+++ b/windows/guildsettings/infopane.cpp
@@ -0,0 +1,206 @@
+#include "infopane.hpp"
+#include "../../abaddon.hpp"
+#include <filesystem>
+
+GuildSettingsInfoPane::GuildSettingsInfoPane(Snowflake id)
+ : GuildID(id)
+ , m_guild_name_label("Guild name")
+ , m_guild_icon_label("Guild icon") {
+ 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");
+ set_margin_top(10);
+ set_margin_bottom(10);
+ set_margin_left(10);
+ set_margin_top(10);
+
+ 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 load_icon_cb = [this](const Glib::RefPtr<Gdk::Pixbuf> &pixbuf) {
+ m_guild_icon.property_pixbuf() = pixbuf->scale_simple(64, 64, Gdk::INTERP_BILINEAR);
+ };
+
+ auto guild_update_cb = [this, load_icon_cb](Snowflake id) {
+ if (id != GuildID) return;
+ const auto guild = *Abaddon::Get().GetDiscordClient().GetGuild(id);
+ if (guild.HasIcon())
+ Abaddon::Get().GetImageManager().LoadFromURL(guild.GetIconURL("png", "64"), sigc::track_obj(load_icon_cb, *this));
+ };
+ discord.signal_guild_update().connect(sigc::track_obj(guild_update_cb, *this));
+
+ m_guild_icon.property_pixbuf() = Abaddon::Get().GetImageManager().GetPlaceholder(32);
+ if (guild.HasIcon()) {
+ Abaddon::Get().GetImageManager().LoadFromURL(guild.GetIconURL("png", "64"), sigc::track_obj(load_icon_cb, *this));
+ }
+ m_guild_icon.set_margin_bottom(10);
+ if (can_modify) {
+ m_guild_icon_ev.signal_realize().connect([this]() {
+ auto window = m_guild_icon_ev.get_window();
+ auto display = window->get_display();
+ auto cursor = Gdk::Cursor::create(display, "pointer");
+ window->set_cursor(cursor);
+ });
+
+ 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;
+ });
+ }
+
+ m_guild_icon_ev.set_tooltip_text("Click to choose a file, right click to paste");
+ m_guild_icon.show();
+ m_guild_icon_ev.show();
+ //m_guild_icon_label.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);
+ //attach(m_guild_icon_label, 0, 1, 1, 1);
+ //attach_next_to(m_guild_icon, m_guild_icon_label, Gtk::POS_RIGHT, 1, 1);
+}
+
+void GuildSettingsInfoPane::UpdateGuildName() {
+ auto &discord = Abaddon::Get().GetDiscordClient();
+ if (discord.GetGuild(GuildID)->Name == m_guild_name.get_text()) return;
+
+ auto cb = [this](bool success) {
+ if (!success) {
+ 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.run();
+ }
+ };
+ discord.SetGuildName(GuildID, m_guild_name.get_text(), sigc::track_obj(cb, *this));
+}
+
+void GuildSettingsInfoPane::UpdateGuildIconFromData(const std::vector<uint8_t> &data, const std::string &mime) {
+ auto encoded = "data:" + mime + ";base64," + Glib::Base64::encode(std::string(data.begin(), data.end()));
+ auto &discord = Abaddon::Get().GetDiscordClient();
+
+ auto cb = [this](bool success) {
+ if (!success) {
+ Gtk::MessageDialog dlg("Failed to set guild icon", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
+ dlg.run();
+ }
+ };
+ discord.SetGuildIcon(GuildID, encoded, sigc::track_obj(cb, *this));
+}
+
+void GuildSettingsInfoPane::UpdateGuildIconFromPixbuf(Glib::RefPtr<Gdk::Pixbuf> pixbuf) {
+ int w = pixbuf->get_width();
+ int h = pixbuf->get_height();
+ if (w > 1024 || h > 1024) {
+ GetImageDimensions(w, h, w, h, 1024, 1024);
+ pixbuf = pixbuf->scale_simple(w, h, Gdk::INTERP_BILINEAR);
+ }
+ gchar *buffer;
+ gsize buffer_size;
+ pixbuf->save_to_buffer(buffer, buffer_size, "png");
+ std::vector<uint8_t> data(buffer_size);
+ std::memcpy(data.data(), buffer, buffer_size);
+ UpdateGuildIconFromData(data, "image/png");
+}
+
+void GuildSettingsInfoPane::UpdateGuildIconPicker() {
+ // this picker fucking sucks
+ Gtk::FileChooserDialog dlg("Choose new guild icon", Gtk::FILE_CHOOSER_ACTION_OPEN);
+ dlg.get_style_context()->remove_provider(Abaddon::Get().GetStyleProvider());
+ dlg.set_modal(true);
+ dlg.signal_response().connect([this, &dlg](int response) {
+ if (response == Gtk::RESPONSE_OK) {
+ auto data = ReadWholeFile(dlg.get_filename());
+ if (GetExtension(dlg.get_filename()) == ".gif")
+ UpdateGuildIconFromData(data, "image/gif");
+ else
+ try {
+ auto loader = Gdk::PixbufLoader::create();
+ loader->signal_size_prepared().connect([&loader](int inw, int inh) {
+ int w, h;
+ GetImageDimensions(inw, inh, w, h, 1024, 1024);
+ loader->set_size(w, h);
+ });
+ loader->write(data.data(), data.size());
+ loader->close();
+ UpdateGuildIconFromPixbuf(loader->get_pixbuf());
+ } catch (const std::exception &) {};
+ }
+ });
+
+ dlg.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK);
+ dlg.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+
+ auto filter_images = Gtk::FileFilter::create();
+ if (Abaddon::Get().GetDiscordClient().GetGuild(GuildID)->HasFeature("ANIMATED_ICON")) {
+ filter_images->set_name("Supported images (*.jpg, *.jpeg, *.png, *.gif)");
+ filter_images->add_pattern("*.gif");
+ } else {
+ filter_images->set_name("Supported images (*.jpg, *.jpeg, *.png)");
+ }
+ filter_images->add_pattern("*.jpg");
+ filter_images->add_pattern("*.jpeg");
+ filter_images->add_pattern("*.png");
+ dlg.add_filter(filter_images);
+
+ auto filter_all = Gtk::FileFilter::create();
+ filter_all->set_name("All files (*.*)");
+ filter_all->add_pattern("*.*");
+ dlg.add_filter(filter_all);
+
+ dlg.run();
+}
+
+void GuildSettingsInfoPane::UpdateGuildIconClipboard() {
+ std::vector<uint8_t> icon_data;
+
+ auto cb = Gtk::Clipboard::get();
+ // query for file path then for actual image
+ if (cb->wait_is_text_available()) {
+ auto path = cb->wait_for_text();
+ if (!std::filesystem::exists(path.c_str())) return;
+ auto data = ReadWholeFile(path);
+ try {
+ auto loader = Gdk::PixbufLoader::create();
+ loader->signal_size_prepared().connect([&loader](int inw, int inh) {
+ int w, h;
+ GetImageDimensions(inw, inh, w, h, 1024, 1024);
+ loader->set_size(w, h);
+ });
+ loader->write(data.data(), data.size());
+ loader->close();
+ auto pb = loader->get_pixbuf();
+ UpdateGuildIconFromPixbuf(pb);
+
+ return;
+ } catch (const std::exception &) {};
+ }
+
+ if (cb->wait_is_image_available()) {
+ auto pb = cb->wait_for_image();
+ UpdateGuildIconFromPixbuf(pb);
+ return;
+ }
+}
diff --git a/windows/guildsettings/infopane.hpp b/windows/guildsettings/infopane.hpp
new file mode 100644
index 0000000..8e7be82
--- /dev/null
+++ b/windows/guildsettings/infopane.hpp
@@ -0,0 +1,24 @@
+#pragma once
+#include <gtkmm.h>
+#include "../../discord/snowflake.hpp"
+
+class GuildSettingsInfoPane : public Gtk::Grid {
+public:
+ GuildSettingsInfoPane(Snowflake id);
+
+private:
+ void UpdateGuildName();
+ void UpdateGuildIconFromData(const std::vector<uint8_t> &data, const std::string &mime);
+ void UpdateGuildIconFromPixbuf(Glib::RefPtr<Gdk::Pixbuf> pixbuf);
+ void UpdateGuildIconPicker();
+ void UpdateGuildIconClipboard();
+
+ Gtk::Label m_guild_icon_label;
+ Gtk::EventBox m_guild_icon_ev; // necessary to make custom cursor behave properly
+ Gtk::Image m_guild_icon;
+
+ Gtk::Label m_guild_name_label;
+ Gtk::Entry m_guild_name;
+
+ Snowflake GuildID;
+};
diff --git a/windows/guildsettingswindow.cpp b/windows/guildsettingswindow.cpp
new file mode 100644
index 0000000..618bcf5
--- /dev/null
+++ b/windows/guildsettingswindow.cpp
@@ -0,0 +1,45 @@
+#include "guildsettingswindow.hpp"
+#include "../abaddon.hpp"
+
+GuildSettingsWindow::GuildSettingsWindow(Snowflake id)
+ : m_main(Gtk::ORIENTATION_VERTICAL)
+ , GuildID(id)
+ , m_pane_info(id) {
+ auto &discord = Abaddon::Get().GetDiscordClient();
+ const auto guild = *discord.GetGuild(id);
+
+ auto guild_update_cb = [this](Snowflake id) {
+ if (id != GuildID) return;
+ const auto guild = *Abaddon::Get().GetDiscordClient().GetGuild(id);
+ set_title(guild.Name);
+ if (guild.HasIcon())
+ Abaddon::Get().GetImageManager().LoadFromURL(guild.GetIconURL(), sigc::mem_fun(*this, &GuildSettingsWindow::set_icon));
+ };
+ discord.signal_guild_update().connect(sigc::track_obj(guild_update_cb, *this));
+
+ set_name("guild-settings");
+ set_default_size(800, 600);
+ set_title(guild.Name);
+ set_position(Gtk::WIN_POS_CENTER);
+ get_style_context()->add_class("app-window");
+
+ if (guild.HasIcon()) {
+ Abaddon::Get().GetImageManager().LoadFromURL(guild.GetIconURL(), sigc::mem_fun(*this, &GuildSettingsWindow::set_icon));
+ }
+
+ m_switcher.set_stack(m_stack);
+ m_switcher.set_halign(Gtk::ALIGN_CENTER);
+ m_switcher.set_hexpand(true);
+ m_switcher.set_margin_top(10);
+ m_switcher.show();
+
+ m_pane_info.show();
+
+ m_stack.add(m_pane_info, "info", "Info");
+ m_stack.show();
+
+ m_main.add(m_switcher);
+ m_main.add(m_stack);
+ m_main.show();
+ add(m_main);
+}
diff --git a/windows/guildsettingswindow.hpp b/windows/guildsettingswindow.hpp
new file mode 100644
index 0000000..1904216
--- /dev/null
+++ b/windows/guildsettingswindow.hpp
@@ -0,0 +1,18 @@
+#pragma once
+#include <gtkmm.h>
+#include "../discord/snowflake.hpp"
+#include "guildsettings/infopane.hpp"
+
+class GuildSettingsWindow : public Gtk::Window {
+public:
+ GuildSettingsWindow(Snowflake id);
+
+private:
+ Gtk::Box m_main;
+ Gtk::Stack m_stack;
+ Gtk::StackSwitcher m_switcher;
+
+ GuildSettingsInfoPane m_pane_info;
+
+ Snowflake GuildID;
+};
diff --git a/windows/mainwindow.cpp b/windows/mainwindow.cpp
index 883801f..32625a0 100644
--- a/windows/mainwindow.cpp
+++ b/windows/mainwindow.cpp
@@ -7,6 +7,7 @@ MainWindow::MainWindow()
, m_chan_chat_paned(Gtk::ORIENTATION_HORIZONTAL)
, m_chat_members_paned(Gtk::ORIENTATION_HORIZONTAL) {
set_default_size(1200, 800);
+ get_style_context()->add_class("app-window");
m_menu_discord.set_label("Discord");
m_menu_discord.set_submenu(m_menu_discord_sub);