mirror of
https://github.com/uowuo/abaddon.git
synced 2024-09-20 06:51:51 +00:00
manage emojis
This commit is contained in:
parent
65943b4bd7
commit
60404783bd
|
@ -15,6 +15,7 @@ Current features:
|
|||
* Kick, ban, and unban members
|
||||
* Modify roles and modify members' roles
|
||||
* Manage invites
|
||||
* Manage emojis
|
||||
* View audit log
|
||||
* Emojis<sup>2</sup>
|
||||
* Animated avatars, server icons, emojis (can be turned off)
|
||||
|
|
|
@ -578,6 +578,12 @@ void Abaddon::ActionAddRecipient(Snowflake channel_id) {
|
|||
}
|
||||
}
|
||||
|
||||
bool Abaddon::ShowConfirm(const Glib::ustring &prompt, Gtk::Window *window) {
|
||||
ConfirmDialog dlg(window != nullptr ? *window : *m_main_window);
|
||||
dlg.SetConfirmText(prompt);
|
||||
return dlg.run() == Gtk::RESPONSE_OK;
|
||||
}
|
||||
|
||||
void Abaddon::ActionReloadSettings() {
|
||||
m_settings.Reload();
|
||||
}
|
||||
|
|
|
@ -48,6 +48,8 @@ public:
|
|||
void ActionGuildSettings(Snowflake id);
|
||||
void ActionAddRecipient(Snowflake channel_id);
|
||||
|
||||
bool ShowConfirm(const Glib::ustring &prompt, Gtk::Window *window = nullptr);
|
||||
|
||||
void ActionReloadSettings();
|
||||
void ActionReloadCSS();
|
||||
|
||||
|
|
95
components/cellrendererpixbufanimation.cpp
Normal file
95
components/cellrendererpixbufanimation.cpp
Normal file
|
@ -0,0 +1,95 @@
|
|||
#include "cellrendererpixbufanimation.hpp"
|
||||
|
||||
CellRendererPixbufAnimation::CellRendererPixbufAnimation()
|
||||
: Glib::ObjectBase(typeid(CellRendererPixbufAnimation))
|
||||
, Gtk::CellRenderer()
|
||||
, m_property_pixbuf(*this, "pixbuf")
|
||||
, m_property_pixbuf_animation(*this, "pixbuf-animation") {
|
||||
property_mode() = Gtk::CELL_RENDERER_MODE_ACTIVATABLE;
|
||||
property_xpad() = 2;
|
||||
property_ypad() = 2;
|
||||
}
|
||||
|
||||
CellRendererPixbufAnimation::~CellRendererPixbufAnimation() {}
|
||||
|
||||
Glib::PropertyProxy<Glib::RefPtr<Gdk::Pixbuf>> CellRendererPixbufAnimation::property_pixbuf() {
|
||||
return m_property_pixbuf.get_proxy();
|
||||
}
|
||||
|
||||
Glib::PropertyProxy<Glib::RefPtr<Gdk::PixbufAnimation>> CellRendererPixbufAnimation::property_pixbuf_animation() {
|
||||
return m_property_pixbuf_animation.get_proxy();
|
||||
}
|
||||
|
||||
void CellRendererPixbufAnimation::get_preferred_width_vfunc(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {
|
||||
int width = 0;
|
||||
|
||||
if (auto pixbuf = m_property_pixbuf_animation.get_value())
|
||||
width = pixbuf->get_width();
|
||||
else if (auto pixbuf = m_property_pixbuf.get_value())
|
||||
width = pixbuf->get_width();
|
||||
|
||||
int xpad, ypad;
|
||||
get_padding(xpad, ypad);
|
||||
minimum_width = natural_width = xpad * 2 + width;
|
||||
}
|
||||
|
||||
void CellRendererPixbufAnimation::get_preferred_width_for_height_vfunc(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const {
|
||||
get_preferred_width_vfunc(widget, minimum_width, natural_width);
|
||||
}
|
||||
|
||||
void CellRendererPixbufAnimation::get_preferred_height_vfunc(Gtk::Widget &widget, int &minimum_height, int &natural_height) const {
|
||||
int height = 0;
|
||||
|
||||
if (auto pixbuf = m_property_pixbuf_animation.get_value())
|
||||
height = pixbuf->get_height();
|
||||
else if (auto pixbuf = m_property_pixbuf.get_value())
|
||||
height = pixbuf->get_height();
|
||||
|
||||
int xpad, ypad;
|
||||
get_padding(xpad, ypad);
|
||||
minimum_height = natural_height = ypad * 2 + height;
|
||||
}
|
||||
|
||||
void CellRendererPixbufAnimation::get_preferred_height_for_width_vfunc(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const {
|
||||
get_preferred_height_vfunc(widget, minimum_height, natural_height);
|
||||
}
|
||||
|
||||
void CellRendererPixbufAnimation::render_vfunc(const Cairo::RefPtr<Cairo::Context> &cr,
|
||||
Gtk::Widget &widget,
|
||||
const Gdk::Rectangle &background_area,
|
||||
const Gdk::Rectangle &cell_area,
|
||||
Gtk::CellRendererState flags) {
|
||||
Gtk::Requisition minimum, natural;
|
||||
get_preferred_size(widget, minimum, natural);
|
||||
auto alloc = widget.get_allocation();
|
||||
int xpad, ypad;
|
||||
get_padding(xpad, ypad);
|
||||
int pix_x = cell_area.get_x() + xpad;
|
||||
int pix_y = cell_area.get_y() + ypad;
|
||||
natural.width -= xpad * 2;
|
||||
natural.height -= ypad * 2;
|
||||
|
||||
Gdk::Rectangle pix_rect(pix_x, pix_y, natural.width, natural.height);
|
||||
if (!cell_area.intersects(pix_rect))
|
||||
return;
|
||||
|
||||
if (auto anim = m_property_pixbuf_animation.get_value()) {
|
||||
auto map_iter = m_pixbuf_animation_iters.find(anim);
|
||||
if (map_iter == m_pixbuf_animation_iters.end())
|
||||
m_pixbuf_animation_iters[anim] = anim->get_iter(nullptr);
|
||||
auto pb_iter = m_pixbuf_animation_iters.at(anim);
|
||||
|
||||
const auto cb = [this, &widget, anim] {
|
||||
if (m_pixbuf_animation_iters.at(anim)->advance())
|
||||
widget.queue_draw();
|
||||
};
|
||||
Glib::signal_timeout().connect_once(sigc::track_obj(cb, widget), pb_iter->get_delay_time());
|
||||
Gdk::Cairo::set_source_pixbuf(cr, pb_iter->get_pixbuf(), pix_x, pix_y);
|
||||
cr->rectangle(pix_x, pix_y, natural.width, natural.height);
|
||||
cr->fill();
|
||||
} else if (auto pixbuf = m_property_pixbuf.get_value()) {
|
||||
Gdk::Cairo::set_source_pixbuf(cr, pixbuf, pix_x, pix_y);
|
||||
cr->rectangle(pix_x, pix_y, natural.width, natural.height);
|
||||
cr->fill();
|
||||
}
|
||||
}
|
41
components/cellrendererpixbufanimation.hpp
Normal file
41
components/cellrendererpixbufanimation.hpp
Normal file
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <unordered_map>
|
||||
|
||||
// handles both static and animated
|
||||
class CellRendererPixbufAnimation : public Gtk::CellRenderer {
|
||||
public:
|
||||
CellRendererPixbufAnimation();
|
||||
virtual ~CellRendererPixbufAnimation();
|
||||
|
||||
Glib::PropertyProxy<Glib::RefPtr<Gdk::Pixbuf>> property_pixbuf();
|
||||
Glib::PropertyProxy<Glib::RefPtr<Gdk::PixbufAnimation>> property_pixbuf_animation();
|
||||
|
||||
protected:
|
||||
void get_preferred_width_vfunc(Gtk::Widget &widget, int &minimum_width, int &natural_width) const override;
|
||||
|
||||
void get_preferred_width_for_height_vfunc(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const override;
|
||||
|
||||
void get_preferred_height_vfunc(Gtk::Widget &widget, int &minimum_height, int &natural_height) const override;
|
||||
|
||||
void get_preferred_height_for_width_vfunc(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const override;
|
||||
|
||||
void render_vfunc(const Cairo::RefPtr<Cairo::Context> &cr,
|
||||
Gtk::Widget &widget,
|
||||
const Gdk::Rectangle &background_area,
|
||||
const Gdk::Rectangle &cell_area,
|
||||
Gtk::CellRendererState flag) override;
|
||||
|
||||
private:
|
||||
Glib::Property<Glib::RefPtr<Gdk::Pixbuf>> m_property_pixbuf;
|
||||
Glib::Property<Glib::RefPtr<Gdk::PixbufAnimation>> m_property_pixbuf_animation;
|
||||
/* one cellrenderer is used for every animation and i dont know how to
|
||||
store data per-pixbuf (in this case the iter) so this little map thing will have to do
|
||||
i would try set_data on the pixbuf but i dont know if that will cause memory leaks
|
||||
this would mean if a row's pixbuf animation is changed more than once then it wont be released immediately
|
||||
but thats not a problem for me in this case
|
||||
|
||||
unordered_map doesnt compile cuz theres no hash overload, i guess
|
||||
*/
|
||||
std::map<Glib::RefPtr<Gdk::PixbufAnimation>, Glib::RefPtr<Gdk::PixbufAnimationIter>> m_pixbuf_animation_iters;
|
||||
};
|
|
@ -616,6 +616,21 @@ void DiscordClient::ModifyRolePosition(Snowflake guild_id, Snowflake role_id, in
|
|||
});
|
||||
}
|
||||
|
||||
void DiscordClient::ModifyEmojiName(Snowflake guild_id, Snowflake emoji_id, const Glib::ustring &name, sigc::slot<void(bool success)> callback) {
|
||||
ModifyGuildEmojiObject obj;
|
||||
obj.Name = name;
|
||||
|
||||
m_http.MakePATCH("/guilds/" + std::to_string(guild_id) + "/emojis/" + std::to_string(emoji_id), nlohmann::json(obj).dump(), [this, callback](const http::response_type &response) {
|
||||
callback(CheckCode(response));
|
||||
});
|
||||
}
|
||||
|
||||
void DiscordClient::DeleteEmoji(Snowflake guild_id, Snowflake emoji_id, sigc::slot<void(bool success)> callback) {
|
||||
m_http.MakeDELETE("/guilds/" + std::to_string(guild_id) + "/emojis/" + std::to_string(emoji_id), [this, callback](const http::response_type &response) {
|
||||
callback(CheckCode(response, 204));
|
||||
});
|
||||
}
|
||||
|
||||
bool DiscordClient::CanModifyRole(Snowflake guild_id, Snowflake role_id, Snowflake user_id) const {
|
||||
const auto guild = *GetGuild(guild_id);
|
||||
if (guild.OwnerID == user_id) return true;
|
||||
|
@ -687,6 +702,18 @@ void DiscordClient::FetchAuditLog(Snowflake guild_id, sigc::slot<void(AuditLogDa
|
|||
});
|
||||
}
|
||||
|
||||
void DiscordClient::FetchGuildEmojis(Snowflake guild_id, sigc::slot<void(std::vector<EmojiData>)> callback) {
|
||||
m_http.MakeGET("/guilds/" + std::to_string(guild_id) + "/emojis", [this, callback](const http::response_type &response) {
|
||||
if (!CheckCode(response)) return;
|
||||
auto emojis = nlohmann::json::parse(response.text).get<std::vector<EmojiData>>();
|
||||
m_store.BeginTransaction();
|
||||
for (const auto &emoji : emojis)
|
||||
m_store.SetEmoji(emoji.ID, emoji);
|
||||
m_store.EndTransaction();
|
||||
callback(std::move(emojis));
|
||||
});
|
||||
}
|
||||
|
||||
void DiscordClient::FetchUserProfile(Snowflake user_id, sigc::slot<void(UserProfileData)> callback) {
|
||||
m_http.MakeGET("/users/" + std::to_string(user_id) + "/profile", [this, callback](const http::response_type &response) {
|
||||
if (!CheckCode(response)) return;
|
||||
|
@ -919,6 +946,9 @@ void DiscordClient::HandleGatewayMessage(std::string str) {
|
|||
case GatewayEvent::READY_SUPPLEMENTAL: {
|
||||
HandleGatewayReadySupplemental(m);
|
||||
} break;
|
||||
case GatewayEvent::GUILD_EMOJIS_UPDATE: {
|
||||
HandleGatewayGuildEmojisUpdate(m);
|
||||
} break;
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
|
@ -1317,6 +1347,20 @@ void DiscordClient::HandleGatewayUserNoteUpdate(const GatewayMessage &msg) {
|
|||
m_signal_note_update.emit(data.ID, data.Note);
|
||||
}
|
||||
|
||||
void DiscordClient::HandleGatewayGuildEmojisUpdate(const GatewayMessage &msg) {
|
||||
// like the real client, the emoji data sent in this message is ignored
|
||||
// we just use it as a signal to re-request all emojis
|
||||
GuildEmojisUpdateObject data = msg.Data;
|
||||
const auto cb = [this, id = data.GuildID](const std::vector<EmojiData> &emojis) {
|
||||
m_store.BeginTransaction();
|
||||
for (const auto &emoji : emojis)
|
||||
m_store.SetEmoji(emoji.ID, emoji);
|
||||
m_store.EndTransaction();
|
||||
m_signal_guild_emojis_update.emit(id, emojis);
|
||||
};
|
||||
FetchGuildEmojis(data.GuildID, cb);
|
||||
}
|
||||
|
||||
void DiscordClient::HandleGatewayReadySupplemental(const GatewayMessage &msg) {
|
||||
ReadySupplementalData data = msg.Data;
|
||||
for (const auto &p : data.MergedPresences.Friends) {
|
||||
|
@ -1637,6 +1681,7 @@ void DiscordClient::LoadEventMap() {
|
|||
m_event_map["INVITE_DELETE"] = GatewayEvent::INVITE_DELETE;
|
||||
m_event_map["USER_NOTE_UPDATE"] = GatewayEvent::USER_NOTE_UPDATE;
|
||||
m_event_map["READY_SUPPLEMENTAL"] = GatewayEvent::READY_SUPPLEMENTAL;
|
||||
m_event_map["GUILD_EMOJIS_UPDATE"] = GatewayEvent::GUILD_EMOJIS_UPDATE;
|
||||
}
|
||||
|
||||
DiscordClient::type_signal_gateway_ready DiscordClient::signal_gateway_ready() {
|
||||
|
@ -1742,3 +1787,7 @@ DiscordClient::type_signal_presence_update DiscordClient::signal_presence_update
|
|||
DiscordClient::type_signal_note_update DiscordClient::signal_note_update() {
|
||||
return m_signal_note_update;
|
||||
}
|
||||
|
||||
DiscordClient::type_signal_guild_emojis_update DiscordClient::signal_guild_emojis_update() {
|
||||
return m_signal_guild_emojis_update;
|
||||
}
|
||||
|
|
|
@ -129,6 +129,8 @@ public:
|
|||
void ModifyRoleColor(Snowflake guild_id, Snowflake role_id, uint32_t color, sigc::slot<void(bool success)> callback);
|
||||
void ModifyRoleColor(Snowflake guild_id, Snowflake role_id, Gdk::RGBA color, sigc::slot<void(bool success)> callback);
|
||||
void ModifyRolePosition(Snowflake guild_id, Snowflake role_id, int position, sigc::slot<void(bool success)> callback);
|
||||
void ModifyEmojiName(Snowflake guild_id, Snowflake emoji_id, const Glib::ustring &name, sigc::slot<void(bool success)> callback);
|
||||
void DeleteEmoji(Snowflake guild_id, Snowflake emoji_id, sigc::slot<void(bool success)> callback);
|
||||
|
||||
bool CanModifyRole(Snowflake guild_id, Snowflake role_id) const;
|
||||
bool CanModifyRole(Snowflake guild_id, Snowflake role_id, Snowflake user_id) const;
|
||||
|
@ -153,6 +155,8 @@ public:
|
|||
|
||||
void FetchAuditLog(Snowflake guild_id, sigc::slot<void(AuditLogData)> callback);
|
||||
|
||||
void FetchGuildEmojis(Snowflake guild_id, sigc::slot<void(std::vector<EmojiData>)> callback);
|
||||
|
||||
void FetchUserProfile(Snowflake user_id, sigc::slot<void(UserProfileData)> callback);
|
||||
void FetchUserNote(Snowflake user_id, sigc::slot<void(std::string note)> callback);
|
||||
void SetUserNote(Snowflake user_id, std::string note);
|
||||
|
@ -204,6 +208,7 @@ private:
|
|||
void HandleGatewayInviteCreate(const GatewayMessage &msg);
|
||||
void HandleGatewayInviteDelete(const GatewayMessage &msg);
|
||||
void HandleGatewayUserNoteUpdate(const GatewayMessage &msg);
|
||||
void HandleGatewayGuildEmojisUpdate(const GatewayMessage &msg);
|
||||
void HandleGatewayReadySupplemental(const GatewayMessage &msg);
|
||||
void HandleGatewayReconnect(const GatewayMessage &msg);
|
||||
void HandleGatewayInvalidSession(const GatewayMessage &msg);
|
||||
|
@ -289,7 +294,8 @@ public:
|
|||
typedef sigc::signal<void, InviteDeleteObject> type_signal_invite_delete;
|
||||
typedef sigc::signal<void, Snowflake, PresenceStatus> type_signal_presence_update;
|
||||
typedef sigc::signal<void, Snowflake, std::string> type_signal_note_update;
|
||||
typedef sigc::signal<void, bool, GatewayCloseCode> type_signal_disconnected; // bool true if reconnecting
|
||||
typedef sigc::signal<void, Snowflake, std::vector<EmojiData>> type_signal_guild_emojis_update; // guild id
|
||||
typedef sigc::signal<void, bool, GatewayCloseCode> type_signal_disconnected; // bool true if reconnecting
|
||||
typedef sigc::signal<void> type_signal_connected;
|
||||
|
||||
type_signal_gateway_ready signal_gateway_ready();
|
||||
|
@ -316,6 +322,7 @@ public:
|
|||
type_signal_invite_delete signal_invite_delete(); // safe to assume guild id is set
|
||||
type_signal_presence_update signal_presence_update();
|
||||
type_signal_note_update signal_note_update();
|
||||
type_signal_guild_emojis_update signal_guild_emojis_update();
|
||||
type_signal_disconnected signal_disconnected();
|
||||
type_signal_connected signal_connected();
|
||||
|
||||
|
@ -344,6 +351,7 @@ protected:
|
|||
type_signal_invite_delete m_signal_invite_delete;
|
||||
type_signal_presence_update m_signal_presence_update;
|
||||
type_signal_note_update m_signal_note_update;
|
||||
type_signal_guild_emojis_update m_signal_guild_emojis_update;
|
||||
type_signal_disconnected m_signal_disconnected;
|
||||
type_signal_connected m_signal_connected;
|
||||
};
|
||||
|
|
|
@ -28,10 +28,24 @@ void to_json(nlohmann::json &j, const EmojiData &m) {
|
|||
JS_IF("available", m.IsAvailable);
|
||||
}
|
||||
|
||||
std::string EmojiData::GetURL() const {
|
||||
return "https://cdn.discordapp.com/emojis/" + std::to_string(ID) + ".png";
|
||||
std::string EmojiData::GetURL(const char *ext, const char *size) const {
|
||||
if (size != nullptr)
|
||||
return "https://cdn.discordapp.com/emojis/" + std::to_string(ID) + "." + ext + "?size=" + size;
|
||||
else
|
||||
return "https://cdn.discordapp.com/emojis/" + std::to_string(ID) + "." + ext;
|
||||
}
|
||||
|
||||
std::string EmojiData::URLFromID(std::string emoji_id, std::string ext) {
|
||||
return "https://cdn.discordapp.com/emojis/" + emoji_id + "." + ext;
|
||||
std::string EmojiData::URLFromID(const std::string &emoji_id, const char *ext, const char *size) {
|
||||
if (size != nullptr)
|
||||
return "https://cdn.discordapp.com/emojis/" + emoji_id + "." + ext + "?size=" + size;
|
||||
else
|
||||
return "https://cdn.discordapp.com/emojis/" + emoji_id + "." + ext;
|
||||
}
|
||||
|
||||
std::string EmojiData::URLFromID(Snowflake emoji_id, const char *ext, const char *size) {
|
||||
return URLFromID(std::to_string(emoji_id), ext, size);
|
||||
}
|
||||
|
||||
std::string EmojiData::URLFromID(const Glib::ustring &emoji_id, const char *ext, const char *size) {
|
||||
return URLFromID(emoji_id.raw(), ext, size);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ struct EmojiData {
|
|||
friend void from_json(const nlohmann::json &j, EmojiData &m);
|
||||
friend void to_json(nlohmann::json &j, const EmojiData &m);
|
||||
|
||||
std::string GetURL() const;
|
||||
static std::string URLFromID(std::string emoji_id, std::string ext = "png");
|
||||
std::string GetURL(const char *ext = "png", const char *size = nullptr) const;
|
||||
static std::string URLFromID(const std::string &emoji_id, const char *ext = "png", const char *size = nullptr);
|
||||
static std::string URLFromID(Snowflake emoji_id, const char *ext = "png", const char *size = nullptr);
|
||||
static std::string URLFromID(const Glib::ustring &emoji_id, const char *ext = "png", const char *size = nullptr);
|
||||
};
|
||||
|
|
|
@ -386,3 +386,11 @@ void to_json(nlohmann::json &j, const ModifyGuildRolePositionsObject::PositionPa
|
|||
void to_json(nlohmann::json &j, const ModifyGuildRolePositionsObject &m) {
|
||||
j = m.Positions;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, GuildEmojisUpdateObject &m) {
|
||||
JS_D("guild_id", m.GuildID);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const ModifyGuildEmojiObject &m) {
|
||||
JS_IF("name", m.Name);
|
||||
}
|
||||
|
|
|
@ -64,6 +64,7 @@ enum class GatewayEvent : int {
|
|||
INVITE_DELETE,
|
||||
USER_NOTE_UPDATE,
|
||||
READY_SUPPLEMENTAL,
|
||||
GUILD_EMOJIS_UPDATE,
|
||||
};
|
||||
|
||||
enum class GatewayCloseCode : uint16_t {
|
||||
|
@ -539,3 +540,18 @@ struct ModifyGuildRolePositionsObject {
|
|||
|
||||
friend void to_json(nlohmann::json &j, const ModifyGuildRolePositionsObject &m);
|
||||
};
|
||||
|
||||
struct GuildEmojisUpdateObject {
|
||||
Snowflake GuildID;
|
||||
// std::vector<EmojiData> Emojis;
|
||||
// GuildHashes, undocumented
|
||||
|
||||
friend void from_json(const nlohmann::json &j, GuildEmojisUpdateObject &m);
|
||||
};
|
||||
|
||||
struct ModifyGuildEmojiObject {
|
||||
std::optional<std::string> Name;
|
||||
// std::optional<std::vector<Snowflake>> Roles;
|
||||
|
||||
friend void to_json(nlohmann::json &j, const ModifyGuildEmojiObject &m);
|
||||
};
|
||||
|
|
255
windows/guildsettings/emojispane.cpp
Normal file
255
windows/guildsettings/emojispane.cpp
Normal file
|
@ -0,0 +1,255 @@
|
|||
#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_copy_id("Copy ID")
|
||||
, m_menu_delete("Delete")
|
||||
, m_menu_copy_emoji_url("Copy Emoji URL")
|
||||
, m_menu_show_emoji("Open in Browser") {
|
||||
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();
|
||||
auto &img = Abaddon::Get().GetImageManager();
|
||||
|
||||
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::on_switched_to() {
|
||||
m_view.grab_focus();
|
||||
|
||||
if (m_requested) return;
|
||||
m_requested = true;
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto self_id = discord.GetUserData().ID;
|
||||
const bool can_manage = discord.HasGuildPermission(self_id, GuildID, Permission::MANAGE_EMOJIS);
|
||||
m_menu_delete.set_sensitive(can_manage);
|
||||
|
||||
discord.FetchGuildEmojis(GuildID, sigc::mem_fun(*this, &GuildSettingsEmojisPane::OnFetchEmojis));
|
||||
}
|
||||
|
||||
void GuildSettingsEmojisPane::AddEmojiRow(const EmojiData &emoji) {
|
||||
auto &img = Abaddon::Get().GetImageManager();
|
||||
|
||||
auto &row = *m_model->append();
|
||||
|
||||
row[m_columns.m_col_id] = emoji.ID;
|
||||
row[m_columns.m_col_pixbuf] = img.GetPlaceholder(32);
|
||||
row[m_columns.m_col_name] = emoji.Name;
|
||||
if (emoji.Creator.has_value())
|
||||
row[m_columns.m_col_creator] = emoji.Creator->Username + "#" + emoji.Creator->Discriminator;
|
||||
if (emoji.IsAnimated.has_value())
|
||||
row[m_columns.m_col_animated] = *emoji.IsAnimated ? "Yes" : "No";
|
||||
else
|
||||
row[m_columns.m_col_animated] = "No";
|
||||
if (emoji.IsAvailable.has_value())
|
||||
row[m_columns.m_col_available] = *emoji.IsAvailable ? "Yes" : "No";
|
||||
else
|
||||
row[m_columns.m_col_available] = "Yes";
|
||||
|
||||
static bool show_animations = Abaddon::Get().GetSettings().GetShowAnimations();
|
||||
if (show_animations && emoji.IsAnimated.has_value() && *emoji.IsAnimated) {
|
||||
const auto cb = [this, id = emoji.ID](const Glib::RefPtr<Gdk::PixbufAnimation> &pb) {
|
||||
for (auto &row : m_model->children()) {
|
||||
if (static_cast<Snowflake>(row[m_columns.m_col_id]) == id) {
|
||||
row[m_columns.m_col_pixbuf_animation] = pb;
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
img.LoadAnimationFromURL(emoji.GetURL("gif"), 32, 32, sigc::track_obj(cb, *this));
|
||||
} else {
|
||||
const auto cb = [this, id = emoji.ID](const Glib::RefPtr<Gdk::Pixbuf> &pb) {
|
||||
for (auto &row : m_model->children()) {
|
||||
if (static_cast<Snowflake>(row[m_columns.m_col_id]) == id) {
|
||||
row[m_columns.m_col_pixbuf] = pb->scale_simple(32, 32, Gdk::INTERP_BILINEAR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
img.LoadFromURL(emoji.GetURL(), sigc::track_obj(cb, *this));
|
||||
}
|
||||
}
|
||||
|
||||
void GuildSettingsEmojisPane::OnFetchEmojis(std::vector<EmojiData> emojis) {
|
||||
m_model->clear();
|
||||
|
||||
// put animated emojis at the end then sort alphabetically
|
||||
std::sort(emojis.begin(), emojis.end(), [&](const EmojiData &a, const EmojiData &b) {
|
||||
const bool a_is_animated = a.IsAnimated.has_value() && *a.IsAnimated;
|
||||
const bool b_is_animated = b.IsAnimated.has_value() && *b.IsAnimated;
|
||||
if (a_is_animated == b_is_animated)
|
||||
return a.Name < b.Name;
|
||||
else if (a_is_animated && !b_is_animated)
|
||||
return false;
|
||||
else if (!a_is_animated && b_is_animated)
|
||||
return true;
|
||||
return false; // this wont happen please be quiet compiler
|
||||
});
|
||||
|
||||
for (const auto &emoji : emojis)
|
||||
AddEmojiRow(emoji);
|
||||
}
|
||||
|
||||
void GuildSettingsEmojisPane::OnEditName(Snowflake id, const std::string &name) {
|
||||
const auto cb = [this](bool success) {
|
||||
if (!success) {
|
||||
Gtk::MessageDialog dlg("Failed to set emoji name", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK);
|
||||
dlg.run();
|
||||
}
|
||||
};
|
||||
Abaddon::Get().GetDiscordClient().ModifyEmojiName(GuildID, id, name, cb);
|
||||
}
|
||||
|
||||
void GuildSettingsEmojisPane::OnMenuCopyID() {
|
||||
if (auto selected_row = *m_view.get_selection()->get_selected()) {
|
||||
const auto id = static_cast<Snowflake>(selected_row[m_columns.m_col_id]);
|
||||
Gtk::Clipboard::get()->set_text(std::to_string(id));
|
||||
}
|
||||
}
|
||||
|
||||
void GuildSettingsEmojisPane::OnMenuDelete() {
|
||||
if (auto selected_row = *m_view.get_selection()->get_selected()) {
|
||||
const auto name = static_cast<Glib::ustring>(selected_row[m_columns.m_col_name]);
|
||||
const auto id = static_cast<Snowflake>(selected_row[m_columns.m_col_id]);
|
||||
if (auto *window = dynamic_cast<Gtk::Window *>(get_toplevel()))
|
||||
if (Abaddon::Get().ShowConfirm("Are you sure you want to delete " + name + "?", window)) {
|
||||
const auto cb = [this](bool success) {
|
||||
if (!success) {
|
||||
Gtk::MessageDialog dlg("Failed to delete emoji", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK);
|
||||
dlg.run();
|
||||
}
|
||||
};
|
||||
Abaddon::Get().GetDiscordClient().DeleteEmoji(GuildID, id, cb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GuildSettingsEmojisPane::OnMenuCopyEmojiURL() {
|
||||
if (auto selected_row = *m_view.get_selection()->get_selected()) {
|
||||
const auto id = static_cast<Snowflake>(selected_row[m_columns.m_col_id]);
|
||||
const bool is_animated = static_cast<Glib::ustring>(selected_row[m_columns.m_col_animated]) == "Yes";
|
||||
Gtk::Clipboard::get()->set_text(EmojiData::URLFromID(id, is_animated ? "gif" : "png", "256"));
|
||||
}
|
||||
}
|
||||
|
||||
void GuildSettingsEmojisPane::OnMenuShowEmoji() {
|
||||
if (auto selected_row = *m_view.get_selection()->get_selected()) {
|
||||
const auto id = static_cast<Snowflake>(selected_row[m_columns.m_col_id]);
|
||||
const bool is_animated = static_cast<Glib::ustring>(selected_row[m_columns.m_col_animated]) == "Yes";
|
||||
LaunchBrowser(EmojiData::URLFromID(id, is_animated ? "gif" : "png", "256"));
|
||||
}
|
||||
}
|
||||
|
||||
bool GuildSettingsEmojisPane::OnTreeButtonPress(GdkEventButton *event) {
|
||||
if (event->button == GDK_BUTTON_SECONDARY) {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto self_id = discord.GetUserData().ID;
|
||||
const bool can_manage = discord.HasGuildPermission(self_id, GuildID, Permission::MANAGE_EMOJIS);
|
||||
m_menu_delete.set_sensitive(can_manage);
|
||||
|
||||
auto selection = m_view.get_selection();
|
||||
Gtk::TreeModel::Path path;
|
||||
if (m_view.get_path_at_pos(event->x, event->y, path)) {
|
||||
m_view.get_selection()->select(path);
|
||||
m_menu.popup_at_pointer(reinterpret_cast<GdkEvent *>(event));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
GuildSettingsEmojisPane::ModelColumns::ModelColumns() {
|
||||
add(m_col_id);
|
||||
add(m_col_pixbuf);
|
||||
add(m_col_pixbuf_animation);
|
||||
add(m_col_name);
|
||||
add(m_col_creator);
|
||||
add(m_col_animated);
|
||||
add(m_col_available);
|
||||
}
|
55
windows/guildsettings/emojispane.hpp
Normal file
55
windows/guildsettings/emojispane.hpp
Normal file
|
@ -0,0 +1,55 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "../../components/inotifyswitched.hpp"
|
||||
#include "../../discord/emoji.hpp"
|
||||
|
||||
class GuildSettingsEmojisPane : public Gtk::Box
|
||||
, public INotifySwitched {
|
||||
public:
|
||||
GuildSettingsEmojisPane(Snowflake guild_id);
|
||||
|
||||
private:
|
||||
void on_switched_to() override;
|
||||
|
||||
bool m_requested = false;
|
||||
|
||||
void AddEmojiRow(const EmojiData &emoji);
|
||||
|
||||
void OnFetchEmojis(std::vector<EmojiData> emojis);
|
||||
|
||||
void OnEditName(Snowflake id, const std::string &name);
|
||||
void OnMenuCopyID();
|
||||
void OnMenuDelete();
|
||||
void OnMenuCopyEmojiURL();
|
||||
void OnMenuShowEmoji();
|
||||
bool OnTreeButtonPress(GdkEventButton *event);
|
||||
|
||||
Snowflake GuildID;
|
||||
|
||||
Gtk::Entry m_search;
|
||||
Gtk::ScrolledWindow m_view_scroll;
|
||||
Gtk::TreeView m_view;
|
||||
|
||||
class ModelColumns : public Gtk::TreeModel::ColumnRecord {
|
||||
public:
|
||||
ModelColumns();
|
||||
|
||||
Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf>> m_col_pixbuf;
|
||||
Gtk::TreeModelColumn<Glib::RefPtr<Gdk::PixbufAnimation>> m_col_pixbuf_animation;
|
||||
Gtk::TreeModelColumn<Glib::ustring> m_col_name;
|
||||
Gtk::TreeModelColumn<Glib::ustring> m_col_creator;
|
||||
Gtk::TreeModelColumn<Glib::ustring> m_col_animated;
|
||||
Gtk::TreeModelColumn<Glib::ustring> m_col_available;
|
||||
Gtk::TreeModelColumn<Snowflake> m_col_id;
|
||||
};
|
||||
|
||||
ModelColumns m_columns;
|
||||
Glib::RefPtr<Gtk::ListStore> m_model;
|
||||
Glib::RefPtr<Gtk::TreeModelFilter> m_filter;
|
||||
|
||||
Gtk::Menu m_menu;
|
||||
Gtk::MenuItem m_menu_delete;
|
||||
Gtk::MenuItem m_menu_copy_id;
|
||||
Gtk::MenuItem m_menu_copy_emoji_url;
|
||||
Gtk::MenuItem m_menu_show_emoji;
|
||||
};
|
|
@ -10,7 +10,8 @@ GuildSettingsWindow::GuildSettingsWindow(Snowflake id)
|
|||
, m_pane_invites(id)
|
||||
, m_pane_audit_log(id)
|
||||
, m_pane_members(id)
|
||||
, m_pane_roles(id) {
|
||||
, m_pane_roles(id)
|
||||
, m_pane_emojis(id) {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto guild = *discord.GetGuild(id);
|
||||
|
||||
|
@ -51,6 +52,7 @@ GuildSettingsWindow::GuildSettingsWindow(Snowflake id)
|
|||
m_pane_roles.show();
|
||||
m_pane_bans.show();
|
||||
m_pane_invites.show();
|
||||
m_pane_emojis.show();
|
||||
m_pane_audit_log.show();
|
||||
|
||||
m_stack.set_transition_duration(100);
|
||||
|
@ -68,6 +70,7 @@ GuildSettingsWindow::GuildSettingsWindow(Snowflake id)
|
|||
m_stack.add(m_pane_bans, "bans", "Bans");
|
||||
if (discord.HasGuildPermission(self_id, GuildID, Permission::MANAGE_GUILD))
|
||||
m_stack.add(m_pane_invites, "invites", "Invites");
|
||||
m_stack.add(m_pane_emojis, "emojis", "Emojis");
|
||||
if (discord.HasGuildPermission(self_id, GuildID, Permission::VIEW_AUDIT_LOG))
|
||||
m_stack.add(m_pane_audit_log, "audit-log", "Audit Log");
|
||||
m_stack.show();
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "guildsettings/auditlogpane.hpp"
|
||||
#include "guildsettings/memberspane.hpp"
|
||||
#include "guildsettings/rolespane.hpp"
|
||||
#include "guildsettings/emojispane.hpp"
|
||||
|
||||
class GuildSettingsWindow : public Gtk::Window {
|
||||
public:
|
||||
|
@ -22,6 +23,7 @@ private:
|
|||
GuildSettingsRolesPane m_pane_roles;
|
||||
GuildSettingsBansPane m_pane_bans;
|
||||
GuildSettingsInvitesPane m_pane_invites;
|
||||
GuildSettingsEmojisPane m_pane_emojis;
|
||||
GuildSettingsAuditLogPane m_pane_audit_log;
|
||||
|
||||
Snowflake GuildID;
|
||||
|
|
Loading…
Reference in New Issue
Block a user