forked from OpenGamers/abaddon
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9864a24ed | ||
|
|
738d50dd43 | ||
|
|
7d49f934bc | ||
|
|
fbb5522861 | ||
|
|
0ce509f80e | ||
|
|
b6b215ee6f | ||
|
|
d7f3ee9f98 | ||
|
|
2328c8bafe | ||
|
|
dfd642bb82 | ||
|
|
6c9bf4ff81 | ||
|
|
604f2ffe3d | ||
|
|
4e0b22375f | ||
|
|
9d0c7691d8 | ||
|
|
cef28e94ea | ||
|
|
40106ddeb1 | ||
|
|
8695562cb4 | ||
|
|
5338eab3a5 | ||
|
|
d7bb6049e1 | ||
|
|
ea7464722b | ||
|
|
d6da646d87 | ||
|
|
17c1f913df | ||
|
|
6c94e75513 | ||
|
|
801894abc6 | ||
|
|
207c004228 | ||
|
|
36f73a6106 | ||
|
|
41d80af128 | ||
|
|
145504bdd6 | ||
|
|
9fd0d404a1 | ||
|
|
b75599e55d | ||
|
|
67062d6ed8 | ||
|
|
c43d49ed54 | ||
|
|
e9867173c9 | ||
|
|
f580535d35 | ||
|
|
1d7529e609 | ||
|
|
1fb7ca0007 | ||
|
|
b576bd0fcc | ||
|
|
a5332efcfb | ||
|
|
15954830e2 | ||
|
|
46ab760a56 | ||
|
|
0b0135268e | ||
|
|
511fb445d1 | ||
|
|
bcfb2146cd | ||
|
|
a1b662a325 | ||
|
|
14b5bf7d0d | ||
|
|
d288989386 | ||
|
|
d63941797f | ||
|
|
1ea2811713 | ||
|
|
af56784797 | ||
|
|
2461406887 | ||
|
|
8e11dd97e9 | ||
|
|
2690febf20 | ||
|
|
af3d278825 | ||
|
|
e02107feea | ||
|
|
192b043e7a |
@@ -12,6 +12,7 @@ Current features:
|
||||
* Completely styleable/customizable with CSS (if you have a system GTK theme it won't really use it though)
|
||||
* Identifies to Discord as the web client unlike other clients so less likely to be falsely flagged as spam<sup>1</sup>
|
||||
* Set status
|
||||
* Unread and mention indicators
|
||||
* Start new DMs and group DMs
|
||||
* View user profiles (notes, mutual servers, mutual friends)
|
||||
* Kick, ban, and unban members
|
||||
@@ -79,7 +80,6 @@ On Linux, `css` and `res` can also be loaded from `~/.local/share/abaddon` or `/
|
||||
|
||||
### TODO:
|
||||
* Voice support
|
||||
* Unread indicators
|
||||
* User activities
|
||||
* Nicknames
|
||||
* More server management stuff
|
||||
@@ -204,11 +204,15 @@ 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
|
||||
* unreads (true or false, default true) - show unread indicators and mention badges
|
||||
|
||||
#### style
|
||||
* linkcolor (string) - color to use for links in messages
|
||||
* expandercolor (string) - color to use for the expander in the channel list
|
||||
* nsfwchannelcolor (string) - color to use for NSFW channels in the channel list
|
||||
* channelcolor (string) - color to use for SFW channels in the channel list
|
||||
* mentionbadgecolor (string) - background color for mention badges
|
||||
* mentionbadgetextcolor (string) - color to use for number displayed on mention badges
|
||||
|
||||
### Environment variables
|
||||
|
||||
|
||||
2
ci/vcpkg
2
ci/vcpkg
Submodule ci/vcpkg updated: 50ea8c0ab7...a9b27ed5df
@@ -388,7 +388,11 @@ void Abaddon::SaveState() {
|
||||
}
|
||||
|
||||
void Abaddon::LoadState() {
|
||||
if (!GetSettings().SaveState) return;
|
||||
if (!GetSettings().SaveState) {
|
||||
// call with empty data to purge the temporary table
|
||||
m_main_window->GetChannelList()->UseExpansionState({});
|
||||
return;
|
||||
}
|
||||
|
||||
const auto data = ReadWholeFile(GetStateCachePath("/state.json"));
|
||||
if (data.empty()) return;
|
||||
@@ -512,6 +516,9 @@ void Abaddon::ActionChannelOpened(Snowflake id) {
|
||||
|
||||
const auto channel = m_discord.GetChannel(id);
|
||||
if (!channel.has_value()) return;
|
||||
|
||||
const bool can_access = channel->IsDM() || m_discord.HasChannelPermission(m_discord.GetUserData().ID, id, Permission::VIEW_CHANNEL);
|
||||
|
||||
if (channel->Type == ChannelType::GUILD_TEXT || channel->Type == ChannelType::GUILD_NEWS)
|
||||
m_main_window->set_title(std::string(APP_TITLE) + " - #" + *channel->Name);
|
||||
else {
|
||||
@@ -527,23 +534,28 @@ void Abaddon::ActionChannelOpened(Snowflake id) {
|
||||
}
|
||||
m_main_window->UpdateChatActiveChannel(id);
|
||||
if (m_channels_requested.find(id) == m_channels_requested.end()) {
|
||||
m_discord.FetchMessagesInChannel(id, [this, id](const std::vector<Message> &msgs) {
|
||||
m_main_window->UpdateChatWindowContents();
|
||||
m_channels_requested.insert(id);
|
||||
});
|
||||
// dont fire requests we know will fail
|
||||
if (can_access) {
|
||||
m_discord.FetchMessagesInChannel(id, [this, id](const std::vector<Message> &msgs) {
|
||||
m_main_window->UpdateChatWindowContents();
|
||||
m_channels_requested.insert(id);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
m_main_window->UpdateChatWindowContents();
|
||||
}
|
||||
|
||||
if (channel->IsThread()) {
|
||||
m_discord.SendThreadLazyLoad(id);
|
||||
if (channel->ThreadMetadata->IsArchived)
|
||||
m_main_window->GetChatWindow()->SetTopic("This thread is archived. Sending a message will unarchive it");
|
||||
} else if (channel->Type != ChannelType::DM && channel->Type != ChannelType::GROUP_DM && channel->GuildID.has_value()) {
|
||||
m_discord.SendLazyLoad(id);
|
||||
if (can_access) {
|
||||
if (channel->IsThread()) {
|
||||
m_discord.SendThreadLazyLoad(id);
|
||||
if (channel->ThreadMetadata->IsArchived)
|
||||
m_main_window->GetChatWindow()->SetTopic("This thread is archived. Sending a message will unarchive it");
|
||||
} else if (channel->Type != ChannelType::DM && channel->Type != ChannelType::GROUP_DM && channel->GuildID.has_value()) {
|
||||
m_discord.SendLazyLoad(id);
|
||||
|
||||
if (m_discord.IsVerificationRequired(*channel->GuildID))
|
||||
ShowGuildVerificationGateDialog(*channel->GuildID);
|
||||
if (m_discord.IsVerificationRequired(*channel->GuildID))
|
||||
ShowGuildVerificationGateDialog(*channel->GuildID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +1,32 @@
|
||||
#include "abaddon.hpp"
|
||||
#include "channels.hpp"
|
||||
#include "imgmanager.hpp"
|
||||
#include "statusindicator.hpp"
|
||||
#include "util.hpp"
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include "abaddon.hpp"
|
||||
#include "imgmanager.hpp"
|
||||
#include "util.hpp"
|
||||
#include "statusindicator.hpp"
|
||||
|
||||
ChannelList::ChannelList()
|
||||
: Glib::ObjectBase(typeid(ChannelList))
|
||||
, Gtk::ScrolledWindow()
|
||||
, m_model(Gtk::TreeStore::create(m_columns))
|
||||
, m_menu_guild_copy_id("_Copy ID", true)
|
||||
, m_menu_guild_settings("View _Settings", true)
|
||||
, m_menu_guild_leave("_Leave", true)
|
||||
, m_menu_guild_mark_as_read("Mark as _Read", true)
|
||||
, m_menu_category_copy_id("_Copy ID", true)
|
||||
, m_menu_channel_copy_id("_Copy ID", true)
|
||||
, m_menu_channel_mark_as_read("Mark as _Read", true)
|
||||
, m_menu_dm_copy_id("_Copy ID", true)
|
||||
, m_menu_dm_close("") // changes depending on if group or not
|
||||
, m_menu_thread_copy_id("_Copy ID", true)
|
||||
, m_menu_thread_leave("_Leave", true)
|
||||
, m_menu_thread_archive("_Archive", true)
|
||||
, m_menu_thread_unarchive("_Unarchive", true) {
|
||||
, m_menu_thread_unarchive("_Unarchive", true)
|
||||
, m_menu_thread_mark_as_read("Mark as _Read", true) {
|
||||
get_style_context()->add_class("channel-list");
|
||||
|
||||
// todo: move to method
|
||||
const auto cb = [this](const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *column) {
|
||||
auto row = *m_model->get_iter(path);
|
||||
const auto type = row[m_columns.m_type];
|
||||
@@ -40,7 +43,9 @@ ChannelList::ChannelList()
|
||||
}
|
||||
|
||||
if (type == RenderType::TextChannel || type == RenderType::DM || type == RenderType::Thread) {
|
||||
m_signal_action_channel_item_select.emit(static_cast<Snowflake>(row[m_columns.m_id]));
|
||||
const auto id = static_cast<Snowflake>(row[m_columns.m_id]);
|
||||
m_signal_action_channel_item_select.emit(id);
|
||||
Abaddon::Get().GetDiscordClient().MarkChannelAsRead(id, [](...) {});
|
||||
}
|
||||
};
|
||||
m_view.signal_row_activated().connect(cb, false);
|
||||
@@ -77,6 +82,7 @@ ChannelList::ChannelList()
|
||||
column->add_attribute(renderer->property_icon(), m_columns.m_icon);
|
||||
column->add_attribute(renderer->property_icon_animation(), m_columns.m_icon_anim);
|
||||
column->add_attribute(renderer->property_name(), m_columns.m_name);
|
||||
column->add_attribute(renderer->property_id(), m_columns.m_id);
|
||||
column->add_attribute(renderer->property_expanded(), m_columns.m_expanded);
|
||||
column->add_attribute(renderer->property_nsfw(), m_columns.m_nsfw);
|
||||
m_view.append_column(*column);
|
||||
@@ -90,20 +96,55 @@ ChannelList::ChannelList()
|
||||
m_menu_guild_leave.signal_activate().connect([this] {
|
||||
m_signal_action_guild_leave.emit(static_cast<Snowflake>((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]));
|
||||
});
|
||||
m_menu_guild.append(m_menu_guild_copy_id);
|
||||
m_menu_guild_mark_as_read.signal_activate().connect([this] {
|
||||
Abaddon::Get().GetDiscordClient().MarkGuildAsRead(static_cast<Snowflake>((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]), [](...) {});
|
||||
});
|
||||
m_menu_guild_toggle_mute.signal_activate().connect([this] {
|
||||
const auto id = static_cast<Snowflake>((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]);
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
if (discord.IsGuildMuted(id))
|
||||
discord.UnmuteGuild(id, NOOP_CALLBACK);
|
||||
else
|
||||
discord.MuteGuild(id, NOOP_CALLBACK);
|
||||
});
|
||||
m_menu_guild.append(m_menu_guild_mark_as_read);
|
||||
m_menu_guild.append(m_menu_guild_settings);
|
||||
m_menu_guild.append(m_menu_guild_leave);
|
||||
m_menu_guild.append(m_menu_guild_toggle_mute);
|
||||
m_menu_guild.append(m_menu_guild_copy_id);
|
||||
m_menu_guild.show_all();
|
||||
|
||||
m_menu_category_copy_id.signal_activate().connect([this] {
|
||||
Gtk::Clipboard::get()->set_text(std::to_string((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]));
|
||||
});
|
||||
m_menu_category_toggle_mute.signal_activate().connect([this] {
|
||||
const auto id = static_cast<Snowflake>((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]);
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
if (discord.IsChannelMuted(id))
|
||||
discord.UnmuteChannel(id, NOOP_CALLBACK);
|
||||
else
|
||||
discord.MuteChannel(id, NOOP_CALLBACK);
|
||||
});
|
||||
m_menu_category.append(m_menu_category_toggle_mute);
|
||||
m_menu_category.append(m_menu_category_copy_id);
|
||||
m_menu_category.show_all();
|
||||
|
||||
m_menu_channel_copy_id.signal_activate().connect([this] {
|
||||
Gtk::Clipboard::get()->set_text(std::to_string((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]));
|
||||
});
|
||||
m_menu_channel_mark_as_read.signal_activate().connect([this] {
|
||||
Abaddon::Get().GetDiscordClient().MarkChannelAsRead(static_cast<Snowflake>((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]), [](...) {});
|
||||
});
|
||||
m_menu_channel_toggle_mute.signal_activate().connect([this] {
|
||||
const auto id = static_cast<Snowflake>((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]);
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
if (discord.IsChannelMuted(id))
|
||||
discord.UnmuteChannel(id, NOOP_CALLBACK);
|
||||
else
|
||||
discord.MuteChannel(id, NOOP_CALLBACK);
|
||||
});
|
||||
m_menu_channel.append(m_menu_channel_mark_as_read);
|
||||
m_menu_channel.append(m_menu_channel_toggle_mute);
|
||||
m_menu_channel.append(m_menu_channel_copy_id);
|
||||
m_menu_channel.show_all();
|
||||
|
||||
@@ -121,8 +162,17 @@ ChannelList::ChannelList()
|
||||
else if (Abaddon::Get().ShowConfirm("Are you sure you want to leave this group DM?"))
|
||||
Abaddon::Get().GetDiscordClient().CloseDM(id);
|
||||
});
|
||||
m_menu_dm.append(m_menu_dm_copy_id);
|
||||
m_menu_dm_toggle_mute.signal_activate().connect([this] {
|
||||
const auto id = static_cast<Snowflake>((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]);
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
if (discord.IsChannelMuted(id))
|
||||
discord.UnmuteChannel(id, NOOP_CALLBACK);
|
||||
else
|
||||
discord.MuteChannel(id, NOOP_CALLBACK);
|
||||
});
|
||||
m_menu_dm.append(m_menu_dm_toggle_mute);
|
||||
m_menu_dm.append(m_menu_dm_close);
|
||||
m_menu_dm.append(m_menu_dm_copy_id);
|
||||
m_menu_dm.show_all();
|
||||
|
||||
m_menu_thread_copy_id.signal_activate().connect([this] {
|
||||
@@ -138,12 +188,29 @@ ChannelList::ChannelList()
|
||||
m_menu_thread_unarchive.signal_activate().connect([this] {
|
||||
Abaddon::Get().GetDiscordClient().UnArchiveThread(static_cast<Snowflake>((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]), [](...) {});
|
||||
});
|
||||
m_menu_thread.append(m_menu_thread_copy_id);
|
||||
m_menu_thread_mark_as_read.signal_activate().connect([this] {
|
||||
Abaddon::Get().GetDiscordClient().MarkChannelAsRead(static_cast<Snowflake>((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]), NOOP_CALLBACK);
|
||||
});
|
||||
m_menu_thread_toggle_mute.signal_activate().connect([this] {
|
||||
const auto id = static_cast<Snowflake>((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]);
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
if (discord.IsChannelMuted(id))
|
||||
discord.UnmuteThread(id, NOOP_CALLBACK);
|
||||
else
|
||||
discord.MuteThread(id, NOOP_CALLBACK);
|
||||
});
|
||||
m_menu_thread.append(m_menu_thread_mark_as_read);
|
||||
m_menu_thread.append(m_menu_thread_toggle_mute);
|
||||
m_menu_thread.append(m_menu_thread_leave);
|
||||
m_menu_thread.append(m_menu_thread_archive);
|
||||
m_menu_thread.append(m_menu_thread_unarchive);
|
||||
m_menu_thread.append(m_menu_thread_copy_id);
|
||||
m_menu_thread.show_all();
|
||||
|
||||
m_menu_guild.signal_popped_up().connect(sigc::mem_fun(*this, &ChannelList::OnGuildSubmenuPopup));
|
||||
m_menu_category.signal_popped_up().connect(sigc::mem_fun(*this, &ChannelList::OnCategorySubmenuPopup));
|
||||
m_menu_channel.signal_popped_up().connect(sigc::mem_fun(*this, &ChannelList::OnChannelSubmenuPopup));
|
||||
m_menu_dm.signal_popped_up().connect(sigc::mem_fun(*this, &ChannelList::OnDMSubmenuPopup));
|
||||
m_menu_thread.signal_popped_up().connect(sigc::mem_fun(*this, &ChannelList::OnThreadSubmenuPopup));
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
@@ -159,6 +226,19 @@ ChannelList::ChannelList()
|
||||
discord.signal_added_to_thread().connect(sigc::mem_fun(*this, &ChannelList::OnThreadJoined));
|
||||
discord.signal_removed_from_thread().connect(sigc::mem_fun(*this, &ChannelList::OnThreadRemoved));
|
||||
discord.signal_guild_update().connect(sigc::mem_fun(*this, &ChannelList::UpdateGuild));
|
||||
discord.signal_message_ack().connect(sigc::mem_fun(*this, &ChannelList::OnMessageAck));
|
||||
discord.signal_channel_muted().connect(sigc::mem_fun(*this, &ChannelList::OnChannelMute));
|
||||
discord.signal_channel_unmuted().connect(sigc::mem_fun(*this, &ChannelList::OnChannelUnmute));
|
||||
discord.signal_guild_muted().connect(sigc::mem_fun(*this, &ChannelList::OnGuildMute));
|
||||
discord.signal_guild_unmuted().connect(sigc::mem_fun(*this, &ChannelList::OnGuildUnmute));
|
||||
}
|
||||
|
||||
void ChannelList::UsePanedHack(Gtk::Paned &paned) {
|
||||
paned.property_position().signal_changed().connect(sigc::mem_fun(*this, &ChannelList::OnPanedPositionChanged));
|
||||
}
|
||||
|
||||
void ChannelList::OnPanedPositionChanged() {
|
||||
m_view.queue_draw();
|
||||
}
|
||||
|
||||
void ChannelList::UpdateListing() {
|
||||
@@ -231,7 +311,6 @@ void ChannelList::UpdateChannel(Snowflake id) {
|
||||
}
|
||||
|
||||
void ChannelList::UpdateCreateChannel(const ChannelData &channel) {
|
||||
;
|
||||
if (channel.Type == ChannelType::GUILD_CATEGORY) return (void)UpdateCreateChannelCategory(channel);
|
||||
if (channel.Type == ChannelType::DM || channel.Type == ChannelType::GROUP_DM) return UpdateCreateDMChannel(channel);
|
||||
if (channel.Type != ChannelType::GUILD_TEXT && channel.Type != ChannelType::GUILD_NEWS) return;
|
||||
@@ -347,9 +426,35 @@ void ChannelList::DeleteThreadRow(Snowflake id) {
|
||||
m_model->erase(iter);
|
||||
}
|
||||
|
||||
void ChannelList::OnChannelMute(Snowflake id) {
|
||||
if (auto iter = GetIteratorForChannelFromID(id))
|
||||
m_model->row_changed(m_model->get_path(iter), iter);
|
||||
}
|
||||
|
||||
void ChannelList::OnChannelUnmute(Snowflake id) {
|
||||
if (auto iter = GetIteratorForChannelFromID(id))
|
||||
m_model->row_changed(m_model->get_path(iter), iter);
|
||||
}
|
||||
|
||||
void ChannelList::OnGuildMute(Snowflake id) {
|
||||
if (auto iter = GetIteratorForGuildFromID(id))
|
||||
m_model->row_changed(m_model->get_path(iter), iter);
|
||||
}
|
||||
|
||||
void ChannelList::OnGuildUnmute(Snowflake id) {
|
||||
if (auto iter = GetIteratorForGuildFromID(id))
|
||||
m_model->row_changed(m_model->get_path(iter), iter);
|
||||
}
|
||||
|
||||
// create a temporary channel row for non-joined threads
|
||||
// and delete them when the active channel switches off of them if still not joined
|
||||
void ChannelList::SetActiveChannel(Snowflake id) {
|
||||
// mark channel as read when switching off
|
||||
if (m_active_channel.IsValid())
|
||||
Abaddon::Get().GetDiscordClient().MarkChannelAsRead(m_active_channel, [](...) {});
|
||||
|
||||
m_active_channel = id;
|
||||
|
||||
if (m_temporary_thread_row) {
|
||||
const auto thread_id = static_cast<Snowflake>((*m_temporary_thread_row)[m_columns.m_id]);
|
||||
const auto thread = Abaddon::Get().GetDiscordClient().GetChannel(thread_id);
|
||||
@@ -378,11 +483,11 @@ void ChannelList::UseExpansionState(const ExpansionStateRoot &root) {
|
||||
auto recurse = [this](auto &self, const ExpansionStateRoot &root) -> void {
|
||||
// and these are only channels
|
||||
for (const auto &[id, state] : root.Children) {
|
||||
if (const auto iter = GetIteratorForChannelFromID(id)) {
|
||||
if (const auto iter = m_tmp_channel_map.find(id); iter != m_tmp_channel_map.end()) {
|
||||
if (state.IsExpanded)
|
||||
m_view.expand_row(m_model->get_path(iter), false);
|
||||
m_view.expand_row(m_model->get_path(iter->second), false);
|
||||
else
|
||||
m_view.collapse_row(m_model->get_path(iter));
|
||||
m_view.collapse_row(m_model->get_path(iter->second));
|
||||
}
|
||||
|
||||
self(self, state.Children);
|
||||
@@ -400,6 +505,8 @@ void ChannelList::UseExpansionState(const ExpansionStateRoot &root) {
|
||||
|
||||
recurse(recurse, state.Children);
|
||||
}
|
||||
|
||||
m_tmp_channel_map.clear();
|
||||
}
|
||||
|
||||
ExpansionStateRoot ChannelList::GetExpansionState() const {
|
||||
@@ -480,7 +587,7 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) {
|
||||
if (it == threads.end()) return;
|
||||
|
||||
for (const auto &thread : it->second)
|
||||
CreateThreadRow(row.children(), thread);
|
||||
m_tmp_channel_map[thread.ID] = CreateThreadRow(row.children(), thread);
|
||||
};
|
||||
|
||||
for (const auto &channel : orphan_channels) {
|
||||
@@ -491,6 +598,7 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) {
|
||||
channel_row[m_columns.m_sort] = *channel.Position + OrphanChannelSortOffset;
|
||||
channel_row[m_columns.m_nsfw] = channel.NSFW();
|
||||
add_threads(channel, channel_row);
|
||||
m_tmp_channel_map[channel.ID] = channel_row;
|
||||
}
|
||||
|
||||
for (const auto &[category_id, channels] : categories) {
|
||||
@@ -502,6 +610,7 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) {
|
||||
cat_row[m_columns.m_name] = Glib::Markup::escape_text(*category->Name);
|
||||
cat_row[m_columns.m_sort] = *category->Position;
|
||||
cat_row[m_columns.m_expanded] = true;
|
||||
m_tmp_channel_map[category_id] = cat_row;
|
||||
// m_view.expand_row wont work because it might not have channels
|
||||
|
||||
for (const auto &channel : channels) {
|
||||
@@ -512,6 +621,7 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) {
|
||||
channel_row[m_columns.m_sort] = *channel.Position;
|
||||
channel_row[m_columns.m_nsfw] = channel.NSFW();
|
||||
add_threads(channel, channel_row);
|
||||
m_tmp_channel_map[channel.ID] = channel_row;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -658,7 +768,7 @@ void ChannelList::UpdateCreateDMChannel(const ChannelData &dm) {
|
||||
|
||||
std::optional<UserData> top_recipient;
|
||||
const auto recipients = dm.GetDMRecipients();
|
||||
if (recipients.size() > 0)
|
||||
if (!recipients.empty())
|
||||
top_recipient = recipients[0];
|
||||
|
||||
auto iter = m_model->append(header_row->children());
|
||||
@@ -682,13 +792,30 @@ void ChannelList::UpdateCreateDMChannel(const ChannelData &dm) {
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelList::OnMessageAck(const MessageAckData &data) {
|
||||
// trick renderer into redrawing
|
||||
m_model->row_changed(Gtk::TreeModel::Path("0"), m_model->get_iter("0")); // 0 is always path for dm header
|
||||
auto iter = GetIteratorForChannelFromID(data.ChannelID);
|
||||
if (iter) m_model->row_changed(m_model->get_path(iter), iter);
|
||||
auto channel = Abaddon::Get().GetDiscordClient().GetChannel(data.ChannelID);
|
||||
if (channel.has_value() && channel->GuildID.has_value()) {
|
||||
iter = GetIteratorForGuildFromID(*channel->GuildID);
|
||||
if (iter) m_model->row_changed(m_model->get_path(iter), iter);
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelList::OnMessageCreate(const Message &msg) {
|
||||
auto iter = GetIteratorForChannelFromID(msg.ChannelID);
|
||||
if (iter) m_model->row_changed(m_model->get_path(iter), iter); // redraw
|
||||
const auto channel = Abaddon::Get().GetDiscordClient().GetChannel(msg.ChannelID);
|
||||
if (!channel.has_value()) return;
|
||||
if (channel->Type != ChannelType::DM && channel->Type != ChannelType::GROUP_DM) return;
|
||||
auto iter = GetIteratorForChannelFromID(msg.ChannelID);
|
||||
if (iter)
|
||||
(*iter)[m_columns.m_sort] = -msg.ID;
|
||||
if (channel->Type == ChannelType::DM || channel->Type == ChannelType::GROUP_DM) {
|
||||
if (iter)
|
||||
(*iter)[m_columns.m_sort] = -msg.ID;
|
||||
}
|
||||
if (channel->GuildID.has_value())
|
||||
if ((iter = GetIteratorForGuildFromID(*channel->GuildID)))
|
||||
m_model->row_changed(m_model->get_path(iter), iter);
|
||||
}
|
||||
|
||||
bool ChannelList::OnButtonPressEvent(GdkEventButton *ev) {
|
||||
@@ -754,6 +881,46 @@ void ChannelList::MoveRow(const Gtk::TreeModel::iterator &iter, const Gtk::TreeM
|
||||
m_model->erase(iter);
|
||||
}
|
||||
|
||||
void ChannelList::OnGuildSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y) {
|
||||
const auto iter = m_model->get_iter(m_path_for_menu);
|
||||
if (!iter) return;
|
||||
const auto id = static_cast<Snowflake>((*iter)[m_columns.m_id]);
|
||||
if (Abaddon::Get().GetDiscordClient().IsGuildMuted(id))
|
||||
m_menu_guild_toggle_mute.set_label("Unmute");
|
||||
else
|
||||
m_menu_guild_toggle_mute.set_label("Mute");
|
||||
}
|
||||
|
||||
void ChannelList::OnCategorySubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y) {
|
||||
const auto iter = m_model->get_iter(m_path_for_menu);
|
||||
if (!iter) return;
|
||||
const auto id = static_cast<Snowflake>((*iter)[m_columns.m_id]);
|
||||
if (Abaddon::Get().GetDiscordClient().IsChannelMuted(id))
|
||||
m_menu_category_toggle_mute.set_label("Unmute");
|
||||
else
|
||||
m_menu_category_toggle_mute.set_label("Mute");
|
||||
}
|
||||
|
||||
void ChannelList::OnChannelSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y) {
|
||||
const auto iter = m_model->get_iter(m_path_for_menu);
|
||||
if (!iter) return;
|
||||
const auto id = static_cast<Snowflake>((*iter)[m_columns.m_id]);
|
||||
if (Abaddon::Get().GetDiscordClient().IsChannelMuted(id))
|
||||
m_menu_channel_toggle_mute.set_label("Unmute");
|
||||
else
|
||||
m_menu_channel_toggle_mute.set_label("Mute");
|
||||
}
|
||||
|
||||
void ChannelList::OnDMSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y) {
|
||||
auto iter = m_model->get_iter(m_path_for_menu);
|
||||
if (!iter) return;
|
||||
const auto id = static_cast<Snowflake>((*iter)[m_columns.m_id]);
|
||||
if (Abaddon::Get().GetDiscordClient().IsChannelMuted(id))
|
||||
m_menu_dm_toggle_mute.set_label("Unmute");
|
||||
else
|
||||
m_menu_dm_toggle_mute.set_label("Mute");
|
||||
}
|
||||
|
||||
void ChannelList::OnThreadSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y) {
|
||||
m_menu_thread_archive.set_visible(false);
|
||||
m_menu_thread_unarchive.set_visible(false);
|
||||
@@ -761,7 +928,14 @@ void ChannelList::OnThreadSubmenuPopup(const Gdk::Rectangle *flipped_rect, const
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
auto iter = m_model->get_iter(m_path_for_menu);
|
||||
if (!iter) return;
|
||||
auto channel = discord.GetChannel(static_cast<Snowflake>((*iter)[m_columns.m_id]));
|
||||
const auto id = static_cast<Snowflake>((*iter)[m_columns.m_id]);
|
||||
|
||||
if (discord.IsChannelMuted(id))
|
||||
m_menu_thread_toggle_mute.set_label("Unmute");
|
||||
else
|
||||
m_menu_thread_toggle_mute.set_label("Mute");
|
||||
|
||||
auto channel = discord.GetChannel(id);
|
||||
if (!channel.has_value() || !channel->ThreadMetadata.has_value()) return;
|
||||
if (!discord.HasGuildPermission(discord.GetUserData().ID, *channel->GuildID, Permission::MANAGE_THREADS)) return;
|
||||
|
||||
@@ -791,454 +965,3 @@ ChannelList::ModelColumns::ModelColumns() {
|
||||
add(m_nsfw);
|
||||
add(m_expanded);
|
||||
}
|
||||
|
||||
CellRendererChannels::CellRendererChannels()
|
||||
: Glib::ObjectBase(typeid(CellRendererChannels))
|
||||
, Gtk::CellRenderer()
|
||||
, m_property_type(*this, "render-type")
|
||||
, m_property_name(*this, "name")
|
||||
, m_property_pixbuf(*this, "pixbuf")
|
||||
, m_property_pixbuf_animation(*this, "pixbuf-animation")
|
||||
, m_property_expanded(*this, "expanded")
|
||||
, m_property_nsfw(*this, "nsfw") {
|
||||
property_mode() = Gtk::CELL_RENDERER_MODE_ACTIVATABLE;
|
||||
property_xpad() = 2;
|
||||
property_ypad() = 2;
|
||||
m_property_name.get_proxy().signal_changed().connect([this] {
|
||||
m_renderer_text.property_markup() = m_property_name;
|
||||
});
|
||||
}
|
||||
|
||||
CellRendererChannels::~CellRendererChannels() {
|
||||
}
|
||||
|
||||
Glib::PropertyProxy<RenderType> CellRendererChannels::property_type() {
|
||||
return m_property_type.get_proxy();
|
||||
}
|
||||
|
||||
Glib::PropertyProxy<Glib::ustring> CellRendererChannels::property_name() {
|
||||
return m_property_name.get_proxy();
|
||||
}
|
||||
|
||||
Glib::PropertyProxy<Glib::RefPtr<Gdk::Pixbuf>> CellRendererChannels::property_icon() {
|
||||
return m_property_pixbuf.get_proxy();
|
||||
}
|
||||
|
||||
Glib::PropertyProxy<Glib::RefPtr<Gdk::PixbufAnimation>> CellRendererChannels::property_icon_animation() {
|
||||
return m_property_pixbuf_animation.get_proxy();
|
||||
}
|
||||
|
||||
Glib::PropertyProxy<bool> CellRendererChannels::property_expanded() {
|
||||
return m_property_expanded.get_proxy();
|
||||
}
|
||||
|
||||
Glib::PropertyProxy<bool> CellRendererChannels::property_nsfw() {
|
||||
return m_property_nsfw.get_proxy();
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_width_vfunc(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {
|
||||
switch (m_property_type.get_value()) {
|
||||
case RenderType::Guild:
|
||||
return get_preferred_width_vfunc_guild(widget, minimum_width, natural_width);
|
||||
case RenderType::Category:
|
||||
return get_preferred_width_vfunc_category(widget, minimum_width, natural_width);
|
||||
case RenderType::TextChannel:
|
||||
return get_preferred_width_vfunc_channel(widget, minimum_width, natural_width);
|
||||
case RenderType::Thread:
|
||||
return get_preferred_width_vfunc_thread(widget, minimum_width, natural_width);
|
||||
case RenderType::DMHeader:
|
||||
return get_preferred_width_vfunc_dmheader(widget, minimum_width, natural_width);
|
||||
case RenderType::DM:
|
||||
return get_preferred_width_vfunc_dm(widget, minimum_width, natural_width);
|
||||
}
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_width_for_height_vfunc(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const {
|
||||
switch (m_property_type.get_value()) {
|
||||
case RenderType::Guild:
|
||||
return get_preferred_width_for_height_vfunc_guild(widget, height, minimum_width, natural_width);
|
||||
case RenderType::Category:
|
||||
return get_preferred_width_for_height_vfunc_category(widget, height, minimum_width, natural_width);
|
||||
case RenderType::TextChannel:
|
||||
return get_preferred_width_for_height_vfunc_channel(widget, height, minimum_width, natural_width);
|
||||
case RenderType::Thread:
|
||||
return get_preferred_width_for_height_vfunc_thread(widget, height, minimum_width, natural_width);
|
||||
case RenderType::DMHeader:
|
||||
return get_preferred_width_for_height_vfunc_dmheader(widget, height, minimum_width, natural_width);
|
||||
case RenderType::DM:
|
||||
return get_preferred_width_for_height_vfunc_dm(widget, height, minimum_width, natural_width);
|
||||
}
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_vfunc(Gtk::Widget &widget, int &minimum_height, int &natural_height) const {
|
||||
switch (m_property_type.get_value()) {
|
||||
case RenderType::Guild:
|
||||
return get_preferred_height_vfunc_guild(widget, minimum_height, natural_height);
|
||||
case RenderType::Category:
|
||||
return get_preferred_height_vfunc_category(widget, minimum_height, natural_height);
|
||||
case RenderType::TextChannel:
|
||||
return get_preferred_height_vfunc_channel(widget, minimum_height, natural_height);
|
||||
case RenderType::Thread:
|
||||
return get_preferred_height_vfunc_thread(widget, minimum_height, natural_height);
|
||||
case RenderType::DMHeader:
|
||||
return get_preferred_height_vfunc_dmheader(widget, minimum_height, natural_height);
|
||||
case RenderType::DM:
|
||||
return get_preferred_height_vfunc_dm(widget, minimum_height, natural_height);
|
||||
}
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_for_width_vfunc(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const {
|
||||
switch (m_property_type.get_value()) {
|
||||
case RenderType::Guild:
|
||||
return get_preferred_height_for_width_vfunc_guild(widget, width, minimum_height, natural_height);
|
||||
case RenderType::Category:
|
||||
return get_preferred_height_for_width_vfunc_category(widget, width, minimum_height, natural_height);
|
||||
case RenderType::TextChannel:
|
||||
return get_preferred_height_for_width_vfunc_channel(widget, width, minimum_height, natural_height);
|
||||
case RenderType::Thread:
|
||||
return get_preferred_height_for_width_vfunc_thread(widget, width, minimum_height, natural_height);
|
||||
case RenderType::DMHeader:
|
||||
return get_preferred_height_for_width_vfunc_dmheader(widget, width, minimum_height, natural_height);
|
||||
case RenderType::DM:
|
||||
return get_preferred_height_for_width_vfunc_dm(widget, width, minimum_height, natural_height);
|
||||
}
|
||||
}
|
||||
|
||||
void CellRendererChannels::render_vfunc(const Cairo::RefPtr<Cairo::Context> &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags) {
|
||||
switch (m_property_type.get_value()) {
|
||||
case RenderType::Guild:
|
||||
return render_vfunc_guild(cr, widget, background_area, cell_area, flags);
|
||||
case RenderType::Category:
|
||||
return render_vfunc_category(cr, widget, background_area, cell_area, flags);
|
||||
case RenderType::TextChannel:
|
||||
return render_vfunc_channel(cr, widget, background_area, cell_area, flags);
|
||||
case RenderType::Thread:
|
||||
return render_vfunc_thread(cr, widget, background_area, cell_area, flags);
|
||||
case RenderType::DMHeader:
|
||||
return render_vfunc_dmheader(cr, widget, background_area, cell_area, flags);
|
||||
case RenderType::DM:
|
||||
return render_vfunc_dm(cr, widget, background_area, cell_area, flags);
|
||||
}
|
||||
}
|
||||
|
||||
// guild functions
|
||||
|
||||
void CellRendererChannels::get_preferred_width_vfunc_guild(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {
|
||||
int pixbuf_width = 0;
|
||||
|
||||
if (auto pixbuf = m_property_pixbuf_animation.get_value())
|
||||
pixbuf_width = pixbuf->get_width();
|
||||
else if (auto pixbuf = m_property_pixbuf.get_value())
|
||||
pixbuf_width = pixbuf->get_width();
|
||||
|
||||
int text_min, text_nat;
|
||||
m_renderer_text.get_preferred_width(widget, text_min, text_nat);
|
||||
|
||||
int xpad, ypad;
|
||||
get_padding(xpad, ypad);
|
||||
minimum_width = std::max(text_min, pixbuf_width) + xpad * 2;
|
||||
natural_width = std::max(text_nat, pixbuf_width) + xpad * 2;
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_width_for_height_vfunc_guild(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const {
|
||||
get_preferred_width_vfunc_guild(widget, minimum_width, natural_width);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_vfunc_guild(Gtk::Widget &widget, int &minimum_height, int &natural_height) const {
|
||||
int pixbuf_height = 0;
|
||||
if (auto pixbuf = m_property_pixbuf_animation.get_value())
|
||||
pixbuf_height = pixbuf->get_height();
|
||||
else if (auto pixbuf = m_property_pixbuf.get_value())
|
||||
pixbuf_height = pixbuf->get_height();
|
||||
|
||||
int text_min, text_nat;
|
||||
m_renderer_text.get_preferred_height(widget, text_min, text_nat);
|
||||
|
||||
int xpad, ypad;
|
||||
get_padding(xpad, ypad);
|
||||
minimum_height = std::max(text_min, pixbuf_height) + ypad * 2;
|
||||
natural_height = std::max(text_nat, pixbuf_height) + ypad * 2;
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_for_width_vfunc_guild(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const {
|
||||
get_preferred_height_vfunc_guild(widget, minimum_height, natural_height);
|
||||
}
|
||||
|
||||
void CellRendererChannels::render_vfunc_guild(const Cairo::RefPtr<Cairo::Context> &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags) {
|
||||
Gtk::Requisition text_minimum, text_natural;
|
||||
m_renderer_text.get_preferred_size(widget, text_minimum, text_natural);
|
||||
|
||||
Gtk::Requisition minimum, natural;
|
||||
get_preferred_size(widget, minimum, natural);
|
||||
|
||||
int pixbuf_w, pixbuf_h = 0;
|
||||
if (auto pixbuf = m_property_pixbuf_animation.get_value()) {
|
||||
pixbuf_w = pixbuf->get_width();
|
||||
pixbuf_h = pixbuf->get_height();
|
||||
} else if (auto pixbuf = m_property_pixbuf.get_value()) {
|
||||
pixbuf_w = pixbuf->get_width();
|
||||
pixbuf_h = pixbuf->get_height();
|
||||
}
|
||||
|
||||
const double icon_w = pixbuf_w;
|
||||
const double icon_h = pixbuf_h;
|
||||
const double icon_x = background_area.get_x();
|
||||
const double icon_y = background_area.get_y() + background_area.get_height() / 2.0 - icon_h / 2.0;
|
||||
|
||||
const double text_x = icon_x + icon_w + 5.0;
|
||||
const double text_y = background_area.get_y() + background_area.get_height() / 2.0 - text_natural.height / 2.0;
|
||||
const double text_w = text_natural.width;
|
||||
const double text_h = text_natural.height;
|
||||
|
||||
Gdk::Rectangle text_cell_area(text_x, text_y, text_w, text_h);
|
||||
|
||||
m_renderer_text.render(cr, widget, background_area, text_cell_area, flags);
|
||||
|
||||
const bool hover_only = Abaddon::Get().GetSettings().AnimatedGuildHoverOnly;
|
||||
const bool is_hovered = flags & Gtk::CELL_RENDERER_PRELIT;
|
||||
auto anim = m_property_pixbuf_animation.get_value();
|
||||
|
||||
// kinda gross
|
||||
if (anim) {
|
||||
auto map_iter = m_pixbuf_anim_iters.find(anim);
|
||||
if (map_iter == m_pixbuf_anim_iters.end())
|
||||
m_pixbuf_anim_iters[anim] = anim->get_iter(nullptr);
|
||||
auto pb_iter = m_pixbuf_anim_iters.at(anim);
|
||||
|
||||
const auto cb = [this, &widget, anim, icon_x, icon_y, icon_w, icon_h] {
|
||||
if (m_pixbuf_anim_iters.at(anim)->advance())
|
||||
widget.queue_draw_area(icon_x, icon_y, icon_w, icon_h);
|
||||
};
|
||||
|
||||
if ((hover_only && is_hovered) || !hover_only)
|
||||
Glib::signal_timeout().connect_once(sigc::track_obj(cb, widget), pb_iter->get_delay_time());
|
||||
if (hover_only && !is_hovered)
|
||||
m_pixbuf_anim_iters[anim] = anim->get_iter(nullptr);
|
||||
|
||||
Gdk::Cairo::set_source_pixbuf(cr, pb_iter->get_pixbuf(), icon_x, icon_y);
|
||||
cr->rectangle(icon_x, icon_y, icon_w, icon_h);
|
||||
cr->fill();
|
||||
} else if (auto pixbuf = m_property_pixbuf.get_value()) {
|
||||
Gdk::Cairo::set_source_pixbuf(cr, pixbuf, icon_x, icon_y);
|
||||
cr->rectangle(icon_x, icon_y, icon_w, icon_h);
|
||||
cr->fill();
|
||||
}
|
||||
}
|
||||
|
||||
// category
|
||||
|
||||
void CellRendererChannels::get_preferred_width_vfunc_category(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {
|
||||
m_renderer_text.get_preferred_width(widget, minimum_width, natural_width);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_width_for_height_vfunc_category(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const {
|
||||
m_renderer_text.get_preferred_width_for_height(widget, height, minimum_width, natural_width);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_vfunc_category(Gtk::Widget &widget, int &minimum_height, int &natural_height) const {
|
||||
m_renderer_text.get_preferred_height(widget, minimum_height, natural_height);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_for_width_vfunc_category(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const {
|
||||
m_renderer_text.get_preferred_height_for_width(widget, width, minimum_height, natural_height);
|
||||
}
|
||||
|
||||
void CellRendererChannels::render_vfunc_category(const Cairo::RefPtr<Cairo::Context> &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags) {
|
||||
// todo: figure out how Gtk::Arrow is rendered because i like it better :^)
|
||||
constexpr static int len = 5;
|
||||
int x1, y1, x2, y2, x3, y3;
|
||||
if (property_expanded()) {
|
||||
x1 = background_area.get_x() + 7;
|
||||
y1 = background_area.get_y() + background_area.get_height() / 2 - len;
|
||||
x2 = background_area.get_x() + 7 + len;
|
||||
y2 = background_area.get_y() + background_area.get_height() / 2 + len;
|
||||
x3 = background_area.get_x() + 7 + len * 2;
|
||||
y3 = background_area.get_y() + background_area.get_height() / 2 - len;
|
||||
} else {
|
||||
x1 = background_area.get_x() + 7;
|
||||
y1 = background_area.get_y() + background_area.get_height() / 2 - len;
|
||||
x2 = background_area.get_x() + 7 + len * 2;
|
||||
y2 = background_area.get_y() + background_area.get_height() / 2;
|
||||
x3 = background_area.get_x() + 7;
|
||||
y3 = background_area.get_y() + background_area.get_height() / 2 + len;
|
||||
}
|
||||
cr->move_to(x1, y1);
|
||||
cr->line_to(x2, y2);
|
||||
cr->line_to(x3, y3);
|
||||
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();
|
||||
|
||||
Gtk::Requisition text_minimum, text_natural;
|
||||
m_renderer_text.get_preferred_size(widget, text_minimum, text_natural);
|
||||
|
||||
const int text_x = background_area.get_x() + 22;
|
||||
const int text_y = background_area.get_y() + background_area.get_height() / 2 - text_natural.height / 2;
|
||||
const int text_w = text_natural.width;
|
||||
const int text_h = text_natural.height;
|
||||
|
||||
Gdk::Rectangle text_cell_area(text_x, text_y, text_w, text_h);
|
||||
|
||||
m_renderer_text.render(cr, widget, background_area, text_cell_area, flags);
|
||||
}
|
||||
|
||||
// text channel
|
||||
|
||||
void CellRendererChannels::get_preferred_width_vfunc_channel(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {
|
||||
m_renderer_text.get_preferred_width(widget, minimum_width, natural_width);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_width_for_height_vfunc_channel(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const {
|
||||
m_renderer_text.get_preferred_width_for_height(widget, height, minimum_width, natural_width);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_vfunc_channel(Gtk::Widget &widget, int &minimum_height, int &natural_height) const {
|
||||
m_renderer_text.get_preferred_height(widget, minimum_height, natural_height);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_for_width_vfunc_channel(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const {
|
||||
m_renderer_text.get_preferred_height_for_width(widget, width, minimum_height, natural_height);
|
||||
}
|
||||
|
||||
void CellRendererChannels::render_vfunc_channel(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_size, natural_size;
|
||||
m_renderer_text.get_preferred_size(widget, minimum_size, natural_size);
|
||||
|
||||
const int text_x = background_area.get_x() + 21;
|
||||
const int text_y = background_area.get_y() + background_area.get_height() / 2 - natural_size.height / 2;
|
||||
const int text_w = natural_size.width;
|
||||
const int text_h = natural_size.height;
|
||||
|
||||
Gdk::Rectangle text_cell_area(text_x, text_y, text_w, text_h);
|
||||
|
||||
const auto nsfw_color = Gdk::RGBA(Abaddon::Get().GetSettings().NSFWChannelColor);
|
||||
if (m_property_nsfw.get_value())
|
||||
m_renderer_text.property_foreground_rgba() = nsfw_color;
|
||||
m_renderer_text.render(cr, widget, background_area, text_cell_area, flags);
|
||||
// setting property_foreground_rgba() sets this to true which makes non-nsfw cells use the property too which is bad
|
||||
// so unset it
|
||||
m_renderer_text.property_foreground_set() = false;
|
||||
}
|
||||
|
||||
// thread
|
||||
|
||||
void CellRendererChannels::get_preferred_width_vfunc_thread(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {
|
||||
m_renderer_text.get_preferred_width(widget, minimum_width, natural_width);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_width_for_height_vfunc_thread(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const {
|
||||
get_preferred_width_vfunc_thread(widget, minimum_width, natural_width);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_vfunc_thread(Gtk::Widget &widget, int &minimum_height, int &natural_height) const {
|
||||
m_renderer_text.get_preferred_height(widget, minimum_height, natural_height);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_for_width_vfunc_thread(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const {
|
||||
get_preferred_height_vfunc_thread(widget, minimum_height, natural_height);
|
||||
}
|
||||
|
||||
void CellRendererChannels::render_vfunc_thread(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_size, natural_size;
|
||||
m_renderer_text.get_preferred_size(widget, minimum_size, natural_size);
|
||||
|
||||
const int text_x = background_area.get_x() + 26;
|
||||
const int text_y = background_area.get_y() + background_area.get_height() / 2 - natural_size.height / 2;
|
||||
const int text_w = natural_size.width;
|
||||
const int text_h = natural_size.height;
|
||||
|
||||
Gdk::Rectangle text_cell_area(text_x, text_y, text_w, text_h);
|
||||
m_renderer_text.render(cr, widget, background_area, text_cell_area, flags);
|
||||
}
|
||||
|
||||
// dm header
|
||||
|
||||
void CellRendererChannels::get_preferred_width_vfunc_dmheader(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {
|
||||
m_renderer_text.get_preferred_width(widget, minimum_width, natural_width);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_width_for_height_vfunc_dmheader(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const {
|
||||
m_renderer_text.get_preferred_width_for_height(widget, height, minimum_width, natural_width);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_vfunc_dmheader(Gtk::Widget &widget, int &minimum_height, int &natural_height) const {
|
||||
m_renderer_text.get_preferred_height(widget, minimum_height, natural_height);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_for_width_vfunc_dmheader(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const {
|
||||
m_renderer_text.get_preferred_height_for_width(widget, width, minimum_height, natural_height);
|
||||
}
|
||||
|
||||
void CellRendererChannels::render_vfunc_dmheader(const Cairo::RefPtr<Cairo::Context> &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags) {
|
||||
// gdk::rectangle more like gdk::stupid
|
||||
Gdk::Rectangle text_cell_area(
|
||||
cell_area.get_x() + 9, cell_area.get_y(), // maybe theres a better way to align this ?
|
||||
cell_area.get_width(), cell_area.get_height());
|
||||
m_renderer_text.render(cr, widget, background_area, text_cell_area, flags);
|
||||
}
|
||||
|
||||
// dm (basically the same thing as guild)
|
||||
|
||||
void CellRendererChannels::get_preferred_width_vfunc_dm(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {
|
||||
int pixbuf_width = 0;
|
||||
if (auto pixbuf = m_property_pixbuf.get_value())
|
||||
pixbuf_width = pixbuf->get_width();
|
||||
|
||||
int text_min, text_nat;
|
||||
m_renderer_text.get_preferred_width(widget, text_min, text_nat);
|
||||
|
||||
int xpad, ypad;
|
||||
get_padding(xpad, ypad);
|
||||
minimum_width = std::max(text_min, pixbuf_width) + xpad * 2;
|
||||
natural_width = std::max(text_nat, pixbuf_width) + xpad * 2;
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_width_for_height_vfunc_dm(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const {
|
||||
get_preferred_width_vfunc_guild(widget, minimum_width, natural_width);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_vfunc_dm(Gtk::Widget &widget, int &minimum_height, int &natural_height) const {
|
||||
int pixbuf_height = 0;
|
||||
if (auto pixbuf = m_property_pixbuf.get_value())
|
||||
pixbuf_height = pixbuf->get_height();
|
||||
|
||||
int text_min, text_nat;
|
||||
m_renderer_text.get_preferred_height(widget, text_min, text_nat);
|
||||
|
||||
int xpad, ypad;
|
||||
get_padding(xpad, ypad);
|
||||
minimum_height = std::max(text_min, pixbuf_height) + ypad * 2;
|
||||
natural_height = std::max(text_nat, pixbuf_height) + ypad * 2;
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_for_width_vfunc_dm(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const {
|
||||
get_preferred_height_vfunc_guild(widget, minimum_height, natural_height);
|
||||
}
|
||||
|
||||
void CellRendererChannels::render_vfunc_dm(const Cairo::RefPtr<Cairo::Context> &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags) {
|
||||
Gtk::Requisition text_minimum, text_natural;
|
||||
m_renderer_text.get_preferred_size(widget, text_minimum, text_natural);
|
||||
|
||||
Gtk::Requisition minimum, natural;
|
||||
get_preferred_size(widget, minimum, natural);
|
||||
|
||||
auto pixbuf = m_property_pixbuf.get_value();
|
||||
|
||||
const double icon_w = pixbuf->get_width();
|
||||
const double icon_h = pixbuf->get_height();
|
||||
const double icon_x = background_area.get_x() + 2;
|
||||
const double icon_y = background_area.get_y() + background_area.get_height() / 2.0 - icon_h / 2.0;
|
||||
|
||||
const double text_x = icon_x + icon_w + 5.0;
|
||||
const double text_y = background_area.get_y() + background_area.get_height() / 2.0 - text_natural.height / 2.0;
|
||||
const double text_w = text_natural.width;
|
||||
const double text_h = text_natural.height;
|
||||
|
||||
Gdk::Rectangle text_cell_area(text_x, text_y, text_w, text_h);
|
||||
|
||||
m_renderer_text.render(cr, widget, background_area, text_cell_area, flags);
|
||||
|
||||
Gdk::Cairo::set_source_pixbuf(cr, m_property_pixbuf.get_value(), icon_x, icon_y);
|
||||
cr->rectangle(icon_x, icon_y, icon_w, icon_h);
|
||||
cr->fill();
|
||||
}
|
||||
|
||||
@@ -8,126 +8,12 @@
|
||||
#include <sigc++/sigc++.h>
|
||||
#include "discord/discord.hpp"
|
||||
#include "state.hpp"
|
||||
#include "channelscellrenderer.hpp"
|
||||
|
||||
constexpr static int GuildIconSize = 24;
|
||||
constexpr static int DMIconSize = 20;
|
||||
constexpr static int OrphanChannelSortOffset = -100; // forces orphan channels to the top of the list
|
||||
|
||||
enum class RenderType : uint8_t {
|
||||
Guild,
|
||||
Category,
|
||||
TextChannel,
|
||||
Thread,
|
||||
|
||||
DMHeader,
|
||||
DM,
|
||||
};
|
||||
|
||||
class CellRendererChannels : public Gtk::CellRenderer {
|
||||
public:
|
||||
CellRendererChannels();
|
||||
virtual ~CellRendererChannels();
|
||||
|
||||
Glib::PropertyProxy<RenderType> property_type();
|
||||
Glib::PropertyProxy<Glib::ustring> property_name();
|
||||
Glib::PropertyProxy<Glib::RefPtr<Gdk::Pixbuf>> property_icon();
|
||||
Glib::PropertyProxy<Glib::RefPtr<Gdk::PixbufAnimation>> property_icon_animation();
|
||||
Glib::PropertyProxy<bool> property_expanded();
|
||||
Glib::PropertyProxy<bool> property_nsfw();
|
||||
|
||||
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 flags) override;
|
||||
|
||||
// guild functions
|
||||
void get_preferred_width_vfunc_guild(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_width_for_height_vfunc_guild(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_height_vfunc_guild(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
|
||||
void get_preferred_height_for_width_vfunc_guild(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
|
||||
void render_vfunc_guild(const Cairo::RefPtr<Cairo::Context> &cr,
|
||||
Gtk::Widget &widget,
|
||||
const Gdk::Rectangle &background_area,
|
||||
const Gdk::Rectangle &cell_area,
|
||||
Gtk::CellRendererState flags);
|
||||
|
||||
// category
|
||||
void get_preferred_width_vfunc_category(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_width_for_height_vfunc_category(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_height_vfunc_category(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
|
||||
void get_preferred_height_for_width_vfunc_category(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
|
||||
void render_vfunc_category(const Cairo::RefPtr<Cairo::Context> &cr,
|
||||
Gtk::Widget &widget,
|
||||
const Gdk::Rectangle &background_area,
|
||||
const Gdk::Rectangle &cell_area,
|
||||
Gtk::CellRendererState flags);
|
||||
|
||||
// text channel
|
||||
void get_preferred_width_vfunc_channel(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_width_for_height_vfunc_channel(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_height_vfunc_channel(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
|
||||
void get_preferred_height_for_width_vfunc_channel(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
|
||||
void render_vfunc_channel(const Cairo::RefPtr<Cairo::Context> &cr,
|
||||
Gtk::Widget &widget,
|
||||
const Gdk::Rectangle &background_area,
|
||||
const Gdk::Rectangle &cell_area,
|
||||
Gtk::CellRendererState flags);
|
||||
|
||||
// thread
|
||||
void get_preferred_width_vfunc_thread(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_width_for_height_vfunc_thread(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_height_vfunc_thread(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
|
||||
void get_preferred_height_for_width_vfunc_thread(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
|
||||
void render_vfunc_thread(const Cairo::RefPtr<Cairo::Context> &cr,
|
||||
Gtk::Widget &widget,
|
||||
const Gdk::Rectangle &background_area,
|
||||
const Gdk::Rectangle &cell_area,
|
||||
Gtk::CellRendererState flags);
|
||||
|
||||
// dm header
|
||||
void get_preferred_width_vfunc_dmheader(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_width_for_height_vfunc_dmheader(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_height_vfunc_dmheader(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
|
||||
void get_preferred_height_for_width_vfunc_dmheader(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
|
||||
void render_vfunc_dmheader(const Cairo::RefPtr<Cairo::Context> &cr,
|
||||
Gtk::Widget &widget,
|
||||
const Gdk::Rectangle &background_area,
|
||||
const Gdk::Rectangle &cell_area,
|
||||
Gtk::CellRendererState flags);
|
||||
|
||||
// dm
|
||||
void get_preferred_width_vfunc_dm(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_width_for_height_vfunc_dm(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_height_vfunc_dm(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
|
||||
void get_preferred_height_for_width_vfunc_dm(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
|
||||
void render_vfunc_dm(const Cairo::RefPtr<Cairo::Context> &cr,
|
||||
Gtk::Widget &widget,
|
||||
const Gdk::Rectangle &background_area,
|
||||
const Gdk::Rectangle &cell_area,
|
||||
Gtk::CellRendererState flags);
|
||||
|
||||
private:
|
||||
Gtk::CellRendererText m_renderer_text;
|
||||
|
||||
Glib::Property<RenderType> m_property_type; // all
|
||||
Glib::Property<Glib::ustring> m_property_name; // all
|
||||
Glib::Property<Glib::RefPtr<Gdk::Pixbuf>> m_property_pixbuf; // guild, dm
|
||||
Glib::Property<Glib::RefPtr<Gdk::PixbufAnimation>> m_property_pixbuf_animation; // guild
|
||||
Glib::Property<bool> m_property_expanded; // category
|
||||
Glib::Property<bool> m_property_nsfw; // channel
|
||||
|
||||
// same pitfalls as in https://github.com/uowuo/abaddon/blob/60404783bd4ce9be26233fe66fc3a74475d9eaa3/components/cellrendererpixbufanimation.hpp#L32-L39
|
||||
// this will manifest though since guild icons can change
|
||||
// an animation or two wont be the end of the world though
|
||||
std::map<Glib::RefPtr<Gdk::PixbufAnimation>, Glib::RefPtr<Gdk::PixbufAnimationIter>> m_pixbuf_anim_iters;
|
||||
};
|
||||
|
||||
class ChannelList : public Gtk::ScrolledWindow {
|
||||
public:
|
||||
ChannelList();
|
||||
@@ -139,7 +25,11 @@ public:
|
||||
void UseExpansionState(const ExpansionStateRoot &state);
|
||||
ExpansionStateRoot GetExpansionState() const;
|
||||
|
||||
void UsePanedHack(Gtk::Paned &paned);
|
||||
|
||||
protected:
|
||||
void OnPanedPositionChanged();
|
||||
|
||||
void UpdateNewGuild(const GuildData &guild);
|
||||
void UpdateRemoveGuild(Snowflake id);
|
||||
void UpdateRemoveChannel(Snowflake id);
|
||||
@@ -147,6 +37,10 @@ protected:
|
||||
void UpdateCreateChannel(const ChannelData &channel);
|
||||
void UpdateGuild(Snowflake id);
|
||||
void DeleteThreadRow(Snowflake id);
|
||||
void OnChannelMute(Snowflake id);
|
||||
void OnChannelUnmute(Snowflake id);
|
||||
void OnGuildMute(Snowflake id);
|
||||
void OnGuildUnmute(Snowflake id);
|
||||
|
||||
void OnThreadJoined(Snowflake id);
|
||||
void OnThreadRemoved(Snowflake id);
|
||||
@@ -203,6 +97,8 @@ protected:
|
||||
void AddPrivateChannels();
|
||||
void UpdateCreateDMChannel(const ChannelData &channel);
|
||||
|
||||
void OnMessageAck(const MessageAckData &data);
|
||||
|
||||
void OnMessageCreate(const Message &msg);
|
||||
Gtk::TreeModel::Path m_path_for_menu;
|
||||
|
||||
@@ -213,27 +109,45 @@ protected:
|
||||
Gtk::MenuItem m_menu_guild_copy_id;
|
||||
Gtk::MenuItem m_menu_guild_settings;
|
||||
Gtk::MenuItem m_menu_guild_leave;
|
||||
Gtk::MenuItem m_menu_guild_mark_as_read;
|
||||
Gtk::MenuItem m_menu_guild_toggle_mute;
|
||||
|
||||
Gtk::Menu m_menu_category;
|
||||
Gtk::MenuItem m_menu_category_copy_id;
|
||||
Gtk::MenuItem m_menu_category_toggle_mute;
|
||||
|
||||
Gtk::Menu m_menu_channel;
|
||||
Gtk::MenuItem m_menu_channel_copy_id;
|
||||
Gtk::MenuItem m_menu_channel_mark_as_read;
|
||||
Gtk::MenuItem m_menu_channel_toggle_mute;
|
||||
|
||||
Gtk::Menu m_menu_dm;
|
||||
Gtk::MenuItem m_menu_dm_copy_id;
|
||||
Gtk::MenuItem m_menu_dm_close;
|
||||
Gtk::MenuItem m_menu_dm_toggle_mute;
|
||||
|
||||
Gtk::Menu m_menu_thread;
|
||||
Gtk::MenuItem m_menu_thread_copy_id;
|
||||
Gtk::MenuItem m_menu_thread_leave;
|
||||
Gtk::MenuItem m_menu_thread_archive;
|
||||
Gtk::MenuItem m_menu_thread_unarchive;
|
||||
Gtk::MenuItem m_menu_thread_mark_as_read;
|
||||
Gtk::MenuItem m_menu_thread_toggle_mute;
|
||||
|
||||
void OnGuildSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y);
|
||||
void OnCategorySubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y);
|
||||
void OnChannelSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y);
|
||||
void OnDMSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y);
|
||||
void OnThreadSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y);
|
||||
|
||||
bool m_updating_listing = false;
|
||||
|
||||
Snowflake m_active_channel;
|
||||
|
||||
// (GetIteratorForChannelFromID is rather slow)
|
||||
// only temporary since i dont want to worry about maintaining this map
|
||||
std::unordered_map<Snowflake, Gtk::TreeModel::iterator> m_tmp_channel_map;
|
||||
|
||||
public:
|
||||
typedef sigc::signal<void, Snowflake> type_signal_action_channel_item_select;
|
||||
typedef sigc::signal<void, Snowflake> type_signal_action_guild_leave;
|
||||
|
||||
662
src/components/channelscellrenderer.cpp
Normal file
662
src/components/channelscellrenderer.cpp
Normal file
@@ -0,0 +1,662 @@
|
||||
#include "abaddon.hpp"
|
||||
#include "channelscellrenderer.hpp"
|
||||
#include <gtkmm.h>
|
||||
|
||||
constexpr static int MentionsRightPad = 7;
|
||||
#ifndef M_PI
|
||||
constexpr static double M_PI = 3.14159265358979;
|
||||
#endif
|
||||
constexpr static double M_PI_H = M_PI / 2.0;
|
||||
constexpr static double M_PI_3_2 = M_PI * 3.0 / 2.0;
|
||||
|
||||
CellRendererChannels::CellRendererChannels()
|
||||
: Glib::ObjectBase(typeid(CellRendererChannels))
|
||||
, Gtk::CellRenderer()
|
||||
, m_property_type(*this, "render-type")
|
||||
, m_property_id(*this, "id")
|
||||
, m_property_name(*this, "name")
|
||||
, m_property_pixbuf(*this, "pixbuf")
|
||||
, m_property_pixbuf_animation(*this, "pixbuf-animation")
|
||||
, m_property_expanded(*this, "expanded")
|
||||
, m_property_nsfw(*this, "nsfw") {
|
||||
property_mode() = Gtk::CELL_RENDERER_MODE_ACTIVATABLE;
|
||||
property_xpad() = 2;
|
||||
property_ypad() = 2;
|
||||
m_property_name.get_proxy().signal_changed().connect([this] {
|
||||
m_renderer_text.property_markup() = m_property_name;
|
||||
});
|
||||
}
|
||||
|
||||
CellRendererChannels::~CellRendererChannels() {
|
||||
}
|
||||
|
||||
Glib::PropertyProxy<RenderType> CellRendererChannels::property_type() {
|
||||
return m_property_type.get_proxy();
|
||||
}
|
||||
|
||||
Glib::PropertyProxy<uint64_t> CellRendererChannels::property_id() {
|
||||
return m_property_id.get_proxy();
|
||||
}
|
||||
|
||||
Glib::PropertyProxy<Glib::ustring> CellRendererChannels::property_name() {
|
||||
return m_property_name.get_proxy();
|
||||
}
|
||||
|
||||
Glib::PropertyProxy<Glib::RefPtr<Gdk::Pixbuf>> CellRendererChannels::property_icon() {
|
||||
return m_property_pixbuf.get_proxy();
|
||||
}
|
||||
|
||||
Glib::PropertyProxy<Glib::RefPtr<Gdk::PixbufAnimation>> CellRendererChannels::property_icon_animation() {
|
||||
return m_property_pixbuf_animation.get_proxy();
|
||||
}
|
||||
|
||||
Glib::PropertyProxy<bool> CellRendererChannels::property_expanded() {
|
||||
return m_property_expanded.get_proxy();
|
||||
}
|
||||
|
||||
Glib::PropertyProxy<bool> CellRendererChannels::property_nsfw() {
|
||||
return m_property_nsfw.get_proxy();
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_width_vfunc(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {
|
||||
switch (m_property_type.get_value()) {
|
||||
case RenderType::Guild:
|
||||
return get_preferred_width_vfunc_guild(widget, minimum_width, natural_width);
|
||||
case RenderType::Category:
|
||||
return get_preferred_width_vfunc_category(widget, minimum_width, natural_width);
|
||||
case RenderType::TextChannel:
|
||||
return get_preferred_width_vfunc_channel(widget, minimum_width, natural_width);
|
||||
case RenderType::Thread:
|
||||
return get_preferred_width_vfunc_thread(widget, minimum_width, natural_width);
|
||||
case RenderType::DMHeader:
|
||||
return get_preferred_width_vfunc_dmheader(widget, minimum_width, natural_width);
|
||||
case RenderType::DM:
|
||||
return get_preferred_width_vfunc_dm(widget, minimum_width, natural_width);
|
||||
}
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_width_for_height_vfunc(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const {
|
||||
switch (m_property_type.get_value()) {
|
||||
case RenderType::Guild:
|
||||
return get_preferred_width_for_height_vfunc_guild(widget, height, minimum_width, natural_width);
|
||||
case RenderType::Category:
|
||||
return get_preferred_width_for_height_vfunc_category(widget, height, minimum_width, natural_width);
|
||||
case RenderType::TextChannel:
|
||||
return get_preferred_width_for_height_vfunc_channel(widget, height, minimum_width, natural_width);
|
||||
case RenderType::Thread:
|
||||
return get_preferred_width_for_height_vfunc_thread(widget, height, minimum_width, natural_width);
|
||||
case RenderType::DMHeader:
|
||||
return get_preferred_width_for_height_vfunc_dmheader(widget, height, minimum_width, natural_width);
|
||||
case RenderType::DM:
|
||||
return get_preferred_width_for_height_vfunc_dm(widget, height, minimum_width, natural_width);
|
||||
}
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_vfunc(Gtk::Widget &widget, int &minimum_height, int &natural_height) const {
|
||||
switch (m_property_type.get_value()) {
|
||||
case RenderType::Guild:
|
||||
return get_preferred_height_vfunc_guild(widget, minimum_height, natural_height);
|
||||
case RenderType::Category:
|
||||
return get_preferred_height_vfunc_category(widget, minimum_height, natural_height);
|
||||
case RenderType::TextChannel:
|
||||
return get_preferred_height_vfunc_channel(widget, minimum_height, natural_height);
|
||||
case RenderType::Thread:
|
||||
return get_preferred_height_vfunc_thread(widget, minimum_height, natural_height);
|
||||
case RenderType::DMHeader:
|
||||
return get_preferred_height_vfunc_dmheader(widget, minimum_height, natural_height);
|
||||
case RenderType::DM:
|
||||
return get_preferred_height_vfunc_dm(widget, minimum_height, natural_height);
|
||||
}
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_for_width_vfunc(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const {
|
||||
switch (m_property_type.get_value()) {
|
||||
case RenderType::Guild:
|
||||
return get_preferred_height_for_width_vfunc_guild(widget, width, minimum_height, natural_height);
|
||||
case RenderType::Category:
|
||||
return get_preferred_height_for_width_vfunc_category(widget, width, minimum_height, natural_height);
|
||||
case RenderType::TextChannel:
|
||||
return get_preferred_height_for_width_vfunc_channel(widget, width, minimum_height, natural_height);
|
||||
case RenderType::Thread:
|
||||
return get_preferred_height_for_width_vfunc_thread(widget, width, minimum_height, natural_height);
|
||||
case RenderType::DMHeader:
|
||||
return get_preferred_height_for_width_vfunc_dmheader(widget, width, minimum_height, natural_height);
|
||||
case RenderType::DM:
|
||||
return get_preferred_height_for_width_vfunc_dm(widget, width, minimum_height, natural_height);
|
||||
}
|
||||
}
|
||||
|
||||
void CellRendererChannels::render_vfunc(const Cairo::RefPtr<Cairo::Context> &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags) {
|
||||
switch (m_property_type.get_value()) {
|
||||
case RenderType::Guild:
|
||||
return render_vfunc_guild(cr, widget, background_area, cell_area, flags);
|
||||
case RenderType::Category:
|
||||
return render_vfunc_category(cr, widget, background_area, cell_area, flags);
|
||||
case RenderType::TextChannel:
|
||||
return render_vfunc_channel(cr, widget, background_area, cell_area, flags);
|
||||
case RenderType::Thread:
|
||||
return render_vfunc_thread(cr, widget, background_area, cell_area, flags);
|
||||
case RenderType::DMHeader:
|
||||
return render_vfunc_dmheader(cr, widget, background_area, cell_area, flags);
|
||||
case RenderType::DM:
|
||||
return render_vfunc_dm(cr, widget, background_area, cell_area, flags);
|
||||
}
|
||||
}
|
||||
|
||||
// guild functions
|
||||
|
||||
void CellRendererChannels::get_preferred_width_vfunc_guild(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {
|
||||
int pixbuf_width = 0;
|
||||
|
||||
if (auto pixbuf = m_property_pixbuf_animation.get_value())
|
||||
pixbuf_width = pixbuf->get_width();
|
||||
else if (auto pixbuf = m_property_pixbuf.get_value())
|
||||
pixbuf_width = pixbuf->get_width();
|
||||
|
||||
int text_min, text_nat;
|
||||
m_renderer_text.get_preferred_width(widget, text_min, text_nat);
|
||||
|
||||
int xpad, ypad;
|
||||
get_padding(xpad, ypad);
|
||||
minimum_width = std::max(text_min, pixbuf_width) + xpad * 2;
|
||||
natural_width = std::max(text_nat, pixbuf_width) + xpad * 2;
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_width_for_height_vfunc_guild(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const {
|
||||
get_preferred_width_vfunc_guild(widget, minimum_width, natural_width);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_vfunc_guild(Gtk::Widget &widget, int &minimum_height, int &natural_height) const {
|
||||
int pixbuf_height = 0;
|
||||
if (auto pixbuf = m_property_pixbuf_animation.get_value())
|
||||
pixbuf_height = pixbuf->get_height();
|
||||
else if (auto pixbuf = m_property_pixbuf.get_value())
|
||||
pixbuf_height = pixbuf->get_height();
|
||||
|
||||
int text_min, text_nat;
|
||||
m_renderer_text.get_preferred_height(widget, text_min, text_nat);
|
||||
|
||||
int xpad, ypad;
|
||||
get_padding(xpad, ypad);
|
||||
minimum_height = std::max(text_min, pixbuf_height) + ypad * 2;
|
||||
natural_height = std::max(text_nat, pixbuf_height) + ypad * 2;
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_for_width_vfunc_guild(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const {
|
||||
get_preferred_height_vfunc_guild(widget, minimum_height, natural_height);
|
||||
}
|
||||
|
||||
void CellRendererChannels::render_vfunc_guild(const Cairo::RefPtr<Cairo::Context> &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags) {
|
||||
Gtk::Requisition text_minimum, text_natural;
|
||||
m_renderer_text.get_preferred_size(widget, text_minimum, text_natural);
|
||||
|
||||
Gtk::Requisition minimum, natural;
|
||||
get_preferred_size(widget, minimum, natural);
|
||||
|
||||
int pixbuf_w, pixbuf_h = 0;
|
||||
if (auto pixbuf = m_property_pixbuf_animation.get_value()) {
|
||||
pixbuf_w = pixbuf->get_width();
|
||||
pixbuf_h = pixbuf->get_height();
|
||||
} else if (auto pixbuf = m_property_pixbuf.get_value()) {
|
||||
pixbuf_w = pixbuf->get_width();
|
||||
pixbuf_h = pixbuf->get_height();
|
||||
}
|
||||
|
||||
const double icon_w = pixbuf_w;
|
||||
const double icon_h = pixbuf_h;
|
||||
const double icon_x = background_area.get_x() + 3;
|
||||
const double icon_y = background_area.get_y() + background_area.get_height() / 2.0 - icon_h / 2.0;
|
||||
|
||||
const double text_x = icon_x + icon_w + 5.0;
|
||||
const double text_y = background_area.get_y() + background_area.get_height() / 2.0 - text_natural.height / 2.0;
|
||||
const double text_w = text_natural.width;
|
||||
const double text_h = text_natural.height;
|
||||
|
||||
Gdk::Rectangle text_cell_area(text_x, text_y, text_w, text_h);
|
||||
|
||||
static const auto color = Gdk::RGBA(Abaddon::Get().GetSettings().ChannelColor);
|
||||
m_renderer_text.property_foreground_rgba() = color;
|
||||
m_renderer_text.render(cr, widget, background_area, text_cell_area, flags);
|
||||
|
||||
const bool hover_only = Abaddon::Get().GetSettings().AnimatedGuildHoverOnly;
|
||||
const bool is_hovered = flags & Gtk::CELL_RENDERER_PRELIT;
|
||||
auto anim = m_property_pixbuf_animation.get_value();
|
||||
|
||||
// kinda gross
|
||||
if (anim) {
|
||||
auto map_iter = m_pixbuf_anim_iters.find(anim);
|
||||
if (map_iter == m_pixbuf_anim_iters.end())
|
||||
m_pixbuf_anim_iters[anim] = anim->get_iter(nullptr);
|
||||
auto pb_iter = m_pixbuf_anim_iters.at(anim);
|
||||
|
||||
const auto cb = [this, &widget, anim, icon_x, icon_y, icon_w, icon_h] {
|
||||
if (m_pixbuf_anim_iters.at(anim)->advance())
|
||||
widget.queue_draw_area(icon_x, icon_y, icon_w, icon_h);
|
||||
};
|
||||
|
||||
if ((hover_only && is_hovered) || !hover_only)
|
||||
Glib::signal_timeout().connect_once(sigc::track_obj(cb, widget), pb_iter->get_delay_time());
|
||||
if (hover_only && !is_hovered)
|
||||
m_pixbuf_anim_iters[anim] = anim->get_iter(nullptr);
|
||||
|
||||
Gdk::Cairo::set_source_pixbuf(cr, pb_iter->get_pixbuf(), icon_x, icon_y);
|
||||
cr->rectangle(icon_x, icon_y, icon_w, icon_h);
|
||||
cr->fill();
|
||||
} else if (auto pixbuf = m_property_pixbuf.get_value()) {
|
||||
Gdk::Cairo::set_source_pixbuf(cr, pixbuf, icon_x, icon_y);
|
||||
cr->rectangle(icon_x, icon_y, icon_w, icon_h);
|
||||
cr->fill();
|
||||
}
|
||||
|
||||
// unread
|
||||
if (!Abaddon::Get().GetSettings().Unreads) return;
|
||||
|
||||
const auto id = m_property_id.get_value();
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
int total_mentions;
|
||||
const auto has_unread = discord.GetUnreadStateForGuild(id, total_mentions);
|
||||
|
||||
if (has_unread && !discord.IsGuildMuted(id)) {
|
||||
cr->set_source_rgb(1.0, 1.0, 1.0);
|
||||
const auto x = background_area.get_x();
|
||||
const auto y = background_area.get_y();
|
||||
const auto w = background_area.get_width();
|
||||
const auto h = background_area.get_height();
|
||||
cr->rectangle(x, y + h / 2 - 24 / 2, 3, 24);
|
||||
cr->fill();
|
||||
}
|
||||
|
||||
if (total_mentions < 1) return;
|
||||
auto *paned = static_cast<Gtk::Paned *>(widget.get_ancestor(Gtk::Paned::get_type()));
|
||||
if (paned != nullptr) {
|
||||
const auto edge = std::min(paned->get_position(), background_area.get_width());
|
||||
|
||||
unread_render_mentions(cr, widget, total_mentions, edge, background_area);
|
||||
}
|
||||
}
|
||||
|
||||
// category
|
||||
|
||||
void CellRendererChannels::get_preferred_width_vfunc_category(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {
|
||||
m_renderer_text.get_preferred_width(widget, minimum_width, natural_width);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_width_for_height_vfunc_category(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const {
|
||||
m_renderer_text.get_preferred_width_for_height(widget, height, minimum_width, natural_width);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_vfunc_category(Gtk::Widget &widget, int &minimum_height, int &natural_height) const {
|
||||
m_renderer_text.get_preferred_height(widget, minimum_height, natural_height);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_for_width_vfunc_category(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const {
|
||||
m_renderer_text.get_preferred_height_for_width(widget, width, minimum_height, natural_height);
|
||||
}
|
||||
|
||||
void CellRendererChannels::render_vfunc_category(const Cairo::RefPtr<Cairo::Context> &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags) {
|
||||
// todo: figure out how Gtk::Arrow is rendered because i like it better :^)
|
||||
constexpr static int len = 5;
|
||||
int x1, y1, x2, y2, x3, y3;
|
||||
if (property_expanded()) {
|
||||
x1 = background_area.get_x() + 7;
|
||||
y1 = background_area.get_y() + background_area.get_height() / 2 - len;
|
||||
x2 = background_area.get_x() + 7 + len;
|
||||
y2 = background_area.get_y() + background_area.get_height() / 2 + len;
|
||||
x3 = background_area.get_x() + 7 + len * 2;
|
||||
y3 = background_area.get_y() + background_area.get_height() / 2 - len;
|
||||
} else {
|
||||
x1 = background_area.get_x() + 7;
|
||||
y1 = background_area.get_y() + background_area.get_height() / 2 - len;
|
||||
x2 = background_area.get_x() + 7 + len * 2;
|
||||
y2 = background_area.get_y() + background_area.get_height() / 2;
|
||||
x3 = background_area.get_x() + 7;
|
||||
y3 = background_area.get_y() + background_area.get_height() / 2 + len;
|
||||
}
|
||||
cr->move_to(x1, y1);
|
||||
cr->line_to(x2, y2);
|
||||
cr->line_to(x3, y3);
|
||||
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();
|
||||
|
||||
Gtk::Requisition text_minimum, text_natural;
|
||||
m_renderer_text.get_preferred_size(widget, text_minimum, text_natural);
|
||||
|
||||
const int text_x = background_area.get_x() + 22;
|
||||
const int text_y = background_area.get_y() + background_area.get_height() / 2 - text_natural.height / 2;
|
||||
const int text_w = text_natural.width;
|
||||
const int text_h = text_natural.height;
|
||||
|
||||
Gdk::Rectangle text_cell_area(text_x, text_y, text_w, text_h);
|
||||
|
||||
static const auto color = Gdk::RGBA(Abaddon::Get().GetSettings().ChannelColor);
|
||||
if (Abaddon::Get().GetDiscordClient().IsChannelMuted(m_property_id.get_value())) {
|
||||
auto muted = color;
|
||||
muted.set_red(muted.get_red() * 0.5);
|
||||
muted.set_green(muted.get_green() * 0.5);
|
||||
muted.set_blue(muted.get_blue() * 0.5);
|
||||
m_renderer_text.property_foreground_rgba() = muted;
|
||||
} else {
|
||||
m_renderer_text.property_foreground_rgba() = color;
|
||||
}
|
||||
m_renderer_text.render(cr, widget, background_area, text_cell_area, flags);
|
||||
m_renderer_text.property_foreground_set() = false;
|
||||
}
|
||||
|
||||
// text channel
|
||||
|
||||
void CellRendererChannels::get_preferred_width_vfunc_channel(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {
|
||||
m_renderer_text.get_preferred_width(widget, minimum_width, natural_width);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_width_for_height_vfunc_channel(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const {
|
||||
m_renderer_text.get_preferred_width_for_height(widget, height, minimum_width, natural_width);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_vfunc_channel(Gtk::Widget &widget, int &minimum_height, int &natural_height) const {
|
||||
m_renderer_text.get_preferred_height(widget, minimum_height, natural_height);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_for_width_vfunc_channel(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const {
|
||||
m_renderer_text.get_preferred_height_for_width(widget, width, minimum_height, natural_height);
|
||||
}
|
||||
|
||||
void CellRendererChannels::render_vfunc_channel(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_size, natural_size;
|
||||
m_renderer_text.get_preferred_size(widget, minimum_size, natural_size);
|
||||
|
||||
const int text_x = background_area.get_x() + 21;
|
||||
const int text_y = background_area.get_y() + background_area.get_height() / 2 - natural_size.height / 2;
|
||||
const int text_w = natural_size.width;
|
||||
const int text_h = natural_size.height;
|
||||
|
||||
Gdk::Rectangle text_cell_area(text_x, text_y, text_w, text_h);
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto id = m_property_id.get_value();
|
||||
const bool is_muted = discord.IsChannelMuted(id);
|
||||
|
||||
static const auto sfw_unmuted = Gdk::RGBA(Abaddon::Get().GetSettings().ChannelColor);
|
||||
|
||||
m_renderer_text.property_sensitive() = false;
|
||||
static const auto nsfw_color = Gdk::RGBA(Abaddon::Get().GetSettings().NSFWChannelColor);
|
||||
if (m_property_nsfw.get_value())
|
||||
m_renderer_text.property_foreground_rgba() = nsfw_color;
|
||||
else
|
||||
m_renderer_text.property_foreground_rgba() = sfw_unmuted;
|
||||
if (is_muted) {
|
||||
auto col = m_renderer_text.property_foreground_rgba().get_value();
|
||||
col.set_red(col.get_red() * 0.5);
|
||||
col.set_green(col.get_green() * 0.5);
|
||||
col.set_blue(col.get_blue() * 0.5);
|
||||
m_renderer_text.property_foreground_rgba() = col;
|
||||
}
|
||||
m_renderer_text.render(cr, widget, background_area, text_cell_area, flags);
|
||||
// unset foreground to default so properties dont bleed
|
||||
m_renderer_text.property_foreground_set() = false;
|
||||
|
||||
// unread
|
||||
if (!Abaddon::Get().GetSettings().Unreads) return;
|
||||
|
||||
const auto unread_state = discord.GetUnreadStateForChannel(id);
|
||||
if (unread_state < 0) return;
|
||||
|
||||
if (!is_muted) {
|
||||
cr->set_source_rgb(1.0, 1.0, 1.0);
|
||||
const auto x = background_area.get_x();
|
||||
const auto y = background_area.get_y();
|
||||
const auto w = background_area.get_width();
|
||||
const auto h = background_area.get_height();
|
||||
cr->rectangle(x, y, 3, h);
|
||||
cr->fill();
|
||||
}
|
||||
|
||||
if (unread_state < 1) return;
|
||||
auto *paned = static_cast<Gtk::Paned *>(widget.get_ancestor(Gtk::Paned::get_type()));
|
||||
if (paned != nullptr) {
|
||||
const auto edge = std::min(paned->get_position(), cell_area.get_width());
|
||||
|
||||
unread_render_mentions(cr, widget, unread_state, edge, cell_area);
|
||||
}
|
||||
}
|
||||
|
||||
// thread
|
||||
|
||||
void CellRendererChannels::get_preferred_width_vfunc_thread(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {
|
||||
m_renderer_text.get_preferred_width(widget, minimum_width, natural_width);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_width_for_height_vfunc_thread(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const {
|
||||
get_preferred_width_vfunc_thread(widget, minimum_width, natural_width);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_vfunc_thread(Gtk::Widget &widget, int &minimum_height, int &natural_height) const {
|
||||
m_renderer_text.get_preferred_height(widget, minimum_height, natural_height);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_for_width_vfunc_thread(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const {
|
||||
get_preferred_height_vfunc_thread(widget, minimum_height, natural_height);
|
||||
}
|
||||
|
||||
void CellRendererChannels::render_vfunc_thread(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_size, natural_size;
|
||||
m_renderer_text.get_preferred_size(widget, minimum_size, natural_size);
|
||||
|
||||
const int text_x = background_area.get_x() + 26;
|
||||
const int text_y = background_area.get_y() + background_area.get_height() / 2 - natural_size.height / 2;
|
||||
const int text_w = natural_size.width;
|
||||
const int text_h = natural_size.height;
|
||||
|
||||
Gdk::Rectangle text_cell_area(text_x, text_y, text_w, text_h);
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto id = m_property_id.get_value();
|
||||
const bool is_muted = discord.IsChannelMuted(id);
|
||||
|
||||
static const auto color = Gdk::RGBA(Abaddon::Get().GetSettings().ChannelColor);
|
||||
if (Abaddon::Get().GetDiscordClient().IsChannelMuted(m_property_id.get_value())) {
|
||||
auto muted = color;
|
||||
muted.set_red(muted.get_red() * 0.5);
|
||||
muted.set_green(muted.get_green() * 0.5);
|
||||
muted.set_blue(muted.get_blue() * 0.5);
|
||||
m_renderer_text.property_foreground_rgba() = muted;
|
||||
} else {
|
||||
m_renderer_text.property_foreground_rgba() = color;
|
||||
}
|
||||
m_renderer_text.render(cr, widget, background_area, text_cell_area, flags);
|
||||
m_renderer_text.property_foreground_set() = false;
|
||||
|
||||
// unread
|
||||
if (!Abaddon::Get().GetSettings().Unreads) return;
|
||||
|
||||
const auto unread_state = discord.GetUnreadStateForChannel(id);
|
||||
if (unread_state < 0) return;
|
||||
|
||||
if (!is_muted) {
|
||||
cr->set_source_rgb(1.0, 1.0, 1.0);
|
||||
const auto x = background_area.get_x();
|
||||
const auto y = background_area.get_y();
|
||||
const auto w = background_area.get_width();
|
||||
const auto h = background_area.get_height();
|
||||
cr->rectangle(x, y, 3, h);
|
||||
cr->fill();
|
||||
}
|
||||
|
||||
if (unread_state < 1) return;
|
||||
auto *paned = static_cast<Gtk::Paned *>(widget.get_ancestor(Gtk::Paned::get_type()));
|
||||
if (paned != nullptr) {
|
||||
const auto edge = std::min(paned->get_position(), cell_area.get_width());
|
||||
|
||||
unread_render_mentions(cr, widget, unread_state, edge, cell_area);
|
||||
}
|
||||
}
|
||||
|
||||
// dm header
|
||||
|
||||
void CellRendererChannels::get_preferred_width_vfunc_dmheader(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {
|
||||
m_renderer_text.get_preferred_width(widget, minimum_width, natural_width);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_width_for_height_vfunc_dmheader(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const {
|
||||
m_renderer_text.get_preferred_width_for_height(widget, height, minimum_width, natural_width);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_vfunc_dmheader(Gtk::Widget &widget, int &minimum_height, int &natural_height) const {
|
||||
m_renderer_text.get_preferred_height(widget, minimum_height, natural_height);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_for_width_vfunc_dmheader(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const {
|
||||
m_renderer_text.get_preferred_height_for_width(widget, width, minimum_height, natural_height);
|
||||
}
|
||||
|
||||
void CellRendererChannels::render_vfunc_dmheader(const Cairo::RefPtr<Cairo::Context> &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags) {
|
||||
// gdk::rectangle more like gdk::stupid
|
||||
Gdk::Rectangle text_cell_area(
|
||||
cell_area.get_x() + 9, cell_area.get_y(), // maybe theres a better way to align this ?
|
||||
cell_area.get_width(), cell_area.get_height());
|
||||
m_renderer_text.render(cr, widget, background_area, text_cell_area, flags);
|
||||
|
||||
if (!Abaddon::Get().GetSettings().Unreads) return;
|
||||
|
||||
auto *paned = static_cast<Gtk::Paned *>(widget.get_ancestor(Gtk::Paned::get_type()));
|
||||
if (paned != nullptr) {
|
||||
const auto edge = std::min(paned->get_position(), background_area.get_width());
|
||||
if (const auto unread = Abaddon::Get().GetDiscordClient().GetUnreadDMsCount(); unread > 0)
|
||||
unread_render_mentions(cr, widget, unread, edge, background_area);
|
||||
}
|
||||
}
|
||||
|
||||
// dm (basically the same thing as guild)
|
||||
|
||||
void CellRendererChannels::get_preferred_width_vfunc_dm(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {
|
||||
int pixbuf_width = 0;
|
||||
if (auto pixbuf = m_property_pixbuf.get_value())
|
||||
pixbuf_width = pixbuf->get_width();
|
||||
|
||||
int text_min, text_nat;
|
||||
m_renderer_text.get_preferred_width(widget, text_min, text_nat);
|
||||
|
||||
int xpad, ypad;
|
||||
get_padding(xpad, ypad);
|
||||
minimum_width = std::max(text_min, pixbuf_width) + xpad * 2;
|
||||
natural_width = std::max(text_nat, pixbuf_width) + xpad * 2;
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_width_for_height_vfunc_dm(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const {
|
||||
get_preferred_width_vfunc_guild(widget, minimum_width, natural_width);
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_vfunc_dm(Gtk::Widget &widget, int &minimum_height, int &natural_height) const {
|
||||
int pixbuf_height = 0;
|
||||
if (auto pixbuf = m_property_pixbuf.get_value())
|
||||
pixbuf_height = pixbuf->get_height();
|
||||
|
||||
int text_min, text_nat;
|
||||
m_renderer_text.get_preferred_height(widget, text_min, text_nat);
|
||||
|
||||
int xpad, ypad;
|
||||
get_padding(xpad, ypad);
|
||||
minimum_height = std::max(text_min, pixbuf_height) + ypad * 2;
|
||||
natural_height = std::max(text_nat, pixbuf_height) + ypad * 2;
|
||||
}
|
||||
|
||||
void CellRendererChannels::get_preferred_height_for_width_vfunc_dm(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const {
|
||||
get_preferred_height_vfunc_guild(widget, minimum_height, natural_height);
|
||||
}
|
||||
|
||||
void CellRendererChannels::render_vfunc_dm(const Cairo::RefPtr<Cairo::Context> &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags) {
|
||||
Gtk::Requisition text_minimum, text_natural;
|
||||
m_renderer_text.get_preferred_size(widget, text_minimum, text_natural);
|
||||
|
||||
Gtk::Requisition minimum, natural;
|
||||
get_preferred_size(widget, minimum, natural);
|
||||
|
||||
auto pixbuf = m_property_pixbuf.get_value();
|
||||
|
||||
const double icon_w = pixbuf->get_width();
|
||||
const double icon_h = pixbuf->get_height();
|
||||
const double icon_x = background_area.get_x() + 3;
|
||||
const double icon_y = background_area.get_y() + background_area.get_height() / 2.0 - icon_h / 2.0;
|
||||
|
||||
const double text_x = icon_x + icon_w + 6.0;
|
||||
const double text_y = background_area.get_y() + background_area.get_height() / 2.0 - text_natural.height / 2.0;
|
||||
const double text_w = text_natural.width;
|
||||
const double text_h = text_natural.height;
|
||||
|
||||
Gdk::Rectangle text_cell_area(text_x, text_y, text_w, text_h);
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto id = m_property_id.get_value();
|
||||
const bool is_muted = discord.IsChannelMuted(id);
|
||||
|
||||
static const auto color = Gdk::RGBA(Abaddon::Get().GetSettings().ChannelColor);
|
||||
if (Abaddon::Get().GetDiscordClient().IsChannelMuted(m_property_id.get_value())) {
|
||||
auto muted = color;
|
||||
muted.set_red(muted.get_red() * 0.5);
|
||||
muted.set_green(muted.get_green() * 0.5);
|
||||
muted.set_blue(muted.get_blue() * 0.5);
|
||||
m_renderer_text.property_foreground_rgba() = muted;
|
||||
} else {
|
||||
m_renderer_text.property_foreground_rgba() = color;
|
||||
}
|
||||
m_renderer_text.render(cr, widget, background_area, text_cell_area, flags);
|
||||
m_renderer_text.property_foreground_set() = false;
|
||||
|
||||
Gdk::Cairo::set_source_pixbuf(cr, m_property_pixbuf.get_value(), icon_x, icon_y);
|
||||
cr->rectangle(icon_x, icon_y, icon_w, icon_h);
|
||||
cr->fill();
|
||||
|
||||
// unread
|
||||
if (!Abaddon::Get().GetSettings().Unreads) return;
|
||||
|
||||
const auto unread_state = discord.GetUnreadStateForChannel(id);
|
||||
if (unread_state < 0) return;
|
||||
|
||||
if (!is_muted) {
|
||||
cr->set_source_rgb(1.0, 1.0, 1.0);
|
||||
const auto x = background_area.get_x();
|
||||
const auto y = background_area.get_y();
|
||||
const auto w = background_area.get_width();
|
||||
const auto h = background_area.get_height();
|
||||
cr->rectangle(x, y, 3, h);
|
||||
cr->fill();
|
||||
}
|
||||
}
|
||||
|
||||
void CellRendererChannels::cairo_path_rounded_rect(const Cairo::RefPtr<Cairo::Context> &cr, double x, double y, double w, double h, double r) {
|
||||
const double degrees = M_PI / 180.0;
|
||||
|
||||
cr->begin_new_sub_path();
|
||||
cr->arc(x + w - r, y + r, r, -M_PI_H, 0);
|
||||
cr->arc(x + w - r, y + h - r, r, 0, M_PI_H);
|
||||
cr->arc(x + r, y + h - r, r, M_PI_H, M_PI);
|
||||
cr->arc(x + r, y + r, r, M_PI, M_PI_3_2);
|
||||
cr->close_path();
|
||||
}
|
||||
|
||||
void CellRendererChannels::unread_render_mentions(const Cairo::RefPtr<Cairo::Context> &cr, Gtk::Widget &widget, int mentions, int edge, const Gdk::Rectangle &cell_area) {
|
||||
Pango::FontDescription font;
|
||||
font.set_family("sans 14");
|
||||
//font.set_weight(Pango::WEIGHT_BOLD);
|
||||
|
||||
auto layout = widget.create_pango_layout(std::to_string(mentions));
|
||||
layout->set_font_description(font);
|
||||
layout->set_alignment(Pango::ALIGN_RIGHT);
|
||||
|
||||
int width, height;
|
||||
layout->get_pixel_size(width, height);
|
||||
{
|
||||
static const auto bg = Gdk::RGBA(Abaddon::Get().GetSettings().MentionBadgeColor);
|
||||
static const auto text = Gdk::RGBA(Abaddon::Get().GetSettings().MentionBadgeTextColor);
|
||||
|
||||
const auto x = cell_area.get_x() + edge - width - MentionsRightPad;
|
||||
const auto y = cell_area.get_y() + cell_area.get_height() / 2.0 - height / 2.0 - 1;
|
||||
cairo_path_rounded_rect(cr, x - 4, y + 2, width + 8, height, 5);
|
||||
cr->set_source_rgb(bg.get_red(), bg.get_green(), bg.get_blue());
|
||||
cr->fill();
|
||||
cr->set_source_rgb(text.get_red(), text.get_green(), text.get_blue());
|
||||
cr->move_to(x, y);
|
||||
layout->show_in_cairo_context(cr);
|
||||
}
|
||||
}
|
||||
126
src/components/channelscellrenderer.hpp
Normal file
126
src/components/channelscellrenderer.hpp
Normal file
@@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
#include <gtkmm/cellrenderertext.h>
|
||||
#include <gdkmm/pixbufanimation.h>
|
||||
#include <glibmm/property.h>
|
||||
#include <map>
|
||||
#include "discord/snowflake.hpp"
|
||||
|
||||
enum class RenderType : uint8_t {
|
||||
Guild,
|
||||
Category,
|
||||
TextChannel,
|
||||
Thread,
|
||||
|
||||
DMHeader,
|
||||
DM,
|
||||
};
|
||||
|
||||
class CellRendererChannels : public Gtk::CellRenderer {
|
||||
public:
|
||||
CellRendererChannels();
|
||||
virtual ~CellRendererChannels();
|
||||
|
||||
Glib::PropertyProxy<RenderType> property_type();
|
||||
Glib::PropertyProxy<uint64_t> property_id();
|
||||
Glib::PropertyProxy<Glib::ustring> property_name();
|
||||
Glib::PropertyProxy<Glib::RefPtr<Gdk::Pixbuf>> property_icon();
|
||||
Glib::PropertyProxy<Glib::RefPtr<Gdk::PixbufAnimation>> property_icon_animation();
|
||||
Glib::PropertyProxy<bool> property_expanded();
|
||||
Glib::PropertyProxy<bool> property_nsfw();
|
||||
|
||||
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 flags) override;
|
||||
|
||||
// guild functions
|
||||
void get_preferred_width_vfunc_guild(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_width_for_height_vfunc_guild(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_height_vfunc_guild(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
|
||||
void get_preferred_height_for_width_vfunc_guild(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
|
||||
void render_vfunc_guild(const Cairo::RefPtr<Cairo::Context> &cr,
|
||||
Gtk::Widget &widget,
|
||||
const Gdk::Rectangle &background_area,
|
||||
const Gdk::Rectangle &cell_area,
|
||||
Gtk::CellRendererState flags);
|
||||
|
||||
// category
|
||||
void get_preferred_width_vfunc_category(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_width_for_height_vfunc_category(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_height_vfunc_category(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
|
||||
void get_preferred_height_for_width_vfunc_category(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
|
||||
void render_vfunc_category(const Cairo::RefPtr<Cairo::Context> &cr,
|
||||
Gtk::Widget &widget,
|
||||
const Gdk::Rectangle &background_area,
|
||||
const Gdk::Rectangle &cell_area,
|
||||
Gtk::CellRendererState flags);
|
||||
|
||||
// text channel
|
||||
void get_preferred_width_vfunc_channel(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_width_for_height_vfunc_channel(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_height_vfunc_channel(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
|
||||
void get_preferred_height_for_width_vfunc_channel(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
|
||||
void render_vfunc_channel(const Cairo::RefPtr<Cairo::Context> &cr,
|
||||
Gtk::Widget &widget,
|
||||
const Gdk::Rectangle &background_area,
|
||||
const Gdk::Rectangle &cell_area,
|
||||
Gtk::CellRendererState flags);
|
||||
|
||||
// thread
|
||||
void get_preferred_width_vfunc_thread(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_width_for_height_vfunc_thread(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_height_vfunc_thread(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
|
||||
void get_preferred_height_for_width_vfunc_thread(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
|
||||
void render_vfunc_thread(const Cairo::RefPtr<Cairo::Context> &cr,
|
||||
Gtk::Widget &widget,
|
||||
const Gdk::Rectangle &background_area,
|
||||
const Gdk::Rectangle &cell_area,
|
||||
Gtk::CellRendererState flags);
|
||||
|
||||
// dm header
|
||||
void get_preferred_width_vfunc_dmheader(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_width_for_height_vfunc_dmheader(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_height_vfunc_dmheader(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
|
||||
void get_preferred_height_for_width_vfunc_dmheader(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
|
||||
void render_vfunc_dmheader(const Cairo::RefPtr<Cairo::Context> &cr,
|
||||
Gtk::Widget &widget,
|
||||
const Gdk::Rectangle &background_area,
|
||||
const Gdk::Rectangle &cell_area,
|
||||
Gtk::CellRendererState flags);
|
||||
|
||||
// dm
|
||||
void get_preferred_width_vfunc_dm(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_width_for_height_vfunc_dm(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_height_vfunc_dm(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
|
||||
void get_preferred_height_for_width_vfunc_dm(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
|
||||
void render_vfunc_dm(const Cairo::RefPtr<Cairo::Context> &cr,
|
||||
Gtk::Widget &widget,
|
||||
const Gdk::Rectangle &background_area,
|
||||
const Gdk::Rectangle &cell_area,
|
||||
Gtk::CellRendererState flags);
|
||||
|
||||
static void cairo_path_rounded_rect(const Cairo::RefPtr<Cairo::Context> &cr, double x, double y, double w, double h, double r);
|
||||
void unread_render_mentions(const Cairo::RefPtr<Cairo::Context> &cr, Gtk::Widget &widget, int mentions, int edge, const Gdk::Rectangle &cell_area);
|
||||
|
||||
private:
|
||||
Gtk::CellRendererText m_renderer_text;
|
||||
|
||||
Glib::Property<RenderType> m_property_type; // all
|
||||
Glib::Property<Glib::ustring> m_property_name; // all
|
||||
Glib::Property<uint64_t> m_property_id;
|
||||
Glib::Property<Glib::RefPtr<Gdk::Pixbuf>> m_property_pixbuf; // guild, dm
|
||||
Glib::Property<Glib::RefPtr<Gdk::PixbufAnimation>> m_property_pixbuf_animation; // guild
|
||||
Glib::Property<bool> m_property_expanded; // category
|
||||
Glib::Property<bool> m_property_nsfw; // channel
|
||||
|
||||
// same pitfalls as in https://github.com/uowuo/abaddon/blob/60404783bd4ce9be26233fe66fc3a74475d9eaa3/components/cellrendererpixbufanimation.hpp#L32-L39
|
||||
// this will manifest though since guild icons can change
|
||||
// an animation or two wont be the end of the world though
|
||||
std::map<Glib::RefPtr<Gdk::PixbufAnimation>, Glib::RefPtr<Gdk::PixbufAnimationIter>> m_pixbuf_anim_iters;
|
||||
};
|
||||
@@ -834,7 +834,9 @@ void ChatMessageItemContainer::HandleCustomEmojis(Gtk::TextView &tv) {
|
||||
buf->delete_mark(mark_start);
|
||||
buf->delete_mark(mark_end);
|
||||
auto it = buf->erase(start_it, end_it);
|
||||
buf->insert_pixbuf(it, pixbuf->scale_simple(EmojiSize, EmojiSize, Gdk::INTERP_BILINEAR));
|
||||
int width, height;
|
||||
GetImageDimensions(pixbuf->get_width(), pixbuf->get_height(), width, height, EmojiSize, EmojiSize);
|
||||
buf->insert_pixbuf(it, pixbuf->scale_simple(width, height, Gdk::INTERP_BILINEAR));
|
||||
};
|
||||
img.LoadFromURL(EmojiData::URLFromID(match.fetch(2)), sigc::track_obj(cb, tv));
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ void from_json(const nlohmann::json &j, ThreadMemberObject &m) {
|
||||
JS_O("user_id", m.UserID);
|
||||
JS_D("join_timestamp", m.JoinTimestamp);
|
||||
JS_D("flags", m.Flags);
|
||||
JS_O("muted", m.IsMuted);
|
||||
JS_ON("mute_config", m.MuteConfig);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, ChannelData &m) {
|
||||
@@ -63,6 +65,11 @@ bool ChannelData::NSFW() const {
|
||||
return IsNSFW.has_value() && *IsNSFW;
|
||||
}
|
||||
|
||||
bool ChannelData::IsDM() const noexcept {
|
||||
return Type == ChannelType::DM ||
|
||||
Type == ChannelType::GROUP_DM;
|
||||
}
|
||||
|
||||
bool ChannelData::IsThread() const noexcept {
|
||||
return Type == ChannelType::GUILD_PUBLIC_THREAD ||
|
||||
Type == ChannelType::GUILD_PRIVATE_THREAD ||
|
||||
@@ -73,6 +80,14 @@ bool ChannelData::IsJoinedThread() const {
|
||||
return Abaddon::Get().GetDiscordClient().IsThreadJoined(ID);
|
||||
}
|
||||
|
||||
bool ChannelData::IsCategory() const noexcept {
|
||||
return Type == ChannelType::GUILD_CATEGORY;
|
||||
}
|
||||
|
||||
std::vector<Snowflake> ChannelData::GetChildIDs() const {
|
||||
return Abaddon::Get().GetDiscordClient().GetChildChannelIDs(ID);
|
||||
}
|
||||
|
||||
std::optional<PermissionOverwrite> ChannelData::GetOverwrite(Snowflake id) const {
|
||||
return Abaddon::Get().GetDiscordClient().GetPermissionOverwrite(ID, id);
|
||||
}
|
||||
|
||||
@@ -49,9 +49,19 @@ struct ThreadMetadataData {
|
||||
friend void from_json(const nlohmann::json &j, ThreadMetadataData &m);
|
||||
};
|
||||
|
||||
struct MuteConfigData {
|
||||
std::optional<std::string> EndTime; // nullopt is encoded as null
|
||||
int SelectedTimeWindow;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, MuteConfigData &m);
|
||||
friend void to_json(nlohmann::json &j, const MuteConfigData &m);
|
||||
};
|
||||
|
||||
struct ThreadMemberObject {
|
||||
std::optional<Snowflake> ThreadID;
|
||||
std::optional<Snowflake> UserID;
|
||||
std::optional<bool> IsMuted;
|
||||
std::optional<MuteConfigData> MuteConfig;
|
||||
std::string JoinTimestamp;
|
||||
int Flags;
|
||||
|
||||
@@ -85,8 +95,11 @@ struct ChannelData {
|
||||
void update_from_json(const nlohmann::json &j);
|
||||
|
||||
bool NSFW() const;
|
||||
bool IsDM() const noexcept;
|
||||
bool IsThread() const noexcept;
|
||||
bool IsJoinedThread() const;
|
||||
bool IsCategory() const noexcept;
|
||||
std::vector<Snowflake> GetChildIDs() const;
|
||||
std::optional<PermissionOverwrite> GetOverwrite(Snowflake id) const;
|
||||
std::vector<UserData> GetDMRecipients() const;
|
||||
};
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include "abaddon.hpp"
|
||||
#include "discord.hpp"
|
||||
#include "util.hpp"
|
||||
#include <cassert>
|
||||
#include <cinttypes>
|
||||
#include "util.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
DiscordClient::DiscordClient(bool mem_store)
|
||||
: m_decompress_buf(InflateChunkSize)
|
||||
@@ -305,6 +307,10 @@ void DiscordClient::GetArchivedPrivateThreads(Snowflake channel_id, sigc::slot<v
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<Snowflake> DiscordClient::GetChildChannelIDs(Snowflake parent_id) const {
|
||||
return m_store.GetChannelIDsWithParentID(parent_id);
|
||||
}
|
||||
|
||||
bool DiscordClient::IsThreadJoined(Snowflake thread_id) const {
|
||||
return std::find(m_joined_threads.begin(), m_joined_threads.end(), thread_id) != m_joined_threads.end();
|
||||
}
|
||||
@@ -325,6 +331,7 @@ bool DiscordClient::HasAnyChannelPermission(Snowflake user_id, Snowflake channel
|
||||
bool DiscordClient::HasChannelPermission(Snowflake user_id, Snowflake channel_id, Permission perm) const {
|
||||
const auto channel = m_store.GetChannel(channel_id);
|
||||
if (!channel.has_value()) return false;
|
||||
if (channel->IsDM()) return true;
|
||||
const auto base = ComputePermissions(user_id, *channel->GuildID);
|
||||
const auto overwrites = ComputeOverwrites(base, user_id, channel_id);
|
||||
return (overwrites & perm) == perm;
|
||||
@@ -720,7 +727,9 @@ void DiscordClient::ModifyRoleColor(Snowflake guild_id, Snowflake role_id, Gdk::
|
||||
}
|
||||
|
||||
void DiscordClient::ModifyRolePosition(Snowflake guild_id, Snowflake role_id, int position, sigc::slot<void(DiscordError code)> 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<size_t>(position) > roles.size()) return;
|
||||
// gay and makes you send every role in between new and old position
|
||||
constexpr auto IDX_MAX = ~size_t { 0 };
|
||||
@@ -872,6 +881,126 @@ void DiscordClient::UnArchiveThread(Snowflake channel_id, sigc::slot<void(Discor
|
||||
});
|
||||
}
|
||||
|
||||
void DiscordClient::MarkChannelAsRead(Snowflake channel_id, sigc::slot<void(DiscordError code)> callback) {
|
||||
if (m_unread.find(channel_id) == m_unread.end()) return;
|
||||
const auto iter = m_last_message_id.find(channel_id);
|
||||
if (iter == m_last_message_id.end()) return;
|
||||
m_http.MakePOST("/channels/" + std::to_string(channel_id) + "/messages/" + std::to_string(iter->second) + "/ack", "{\"token\":null}", [this, callback](const http::response_type &response) {
|
||||
if (CheckCode(response))
|
||||
callback(DiscordError::NONE);
|
||||
else
|
||||
callback(GetCodeFromResponse(response));
|
||||
});
|
||||
}
|
||||
|
||||
void DiscordClient::MarkGuildAsRead(Snowflake guild_id, sigc::slot<void(DiscordError code)> callback) {
|
||||
AckBulkData data;
|
||||
const auto channels = GetChannelsInGuild(guild_id);
|
||||
for (const auto &[unread, mention_count] : m_unread) {
|
||||
if (channels.find(unread) == channels.end()) continue;
|
||||
|
||||
const auto iter = m_last_message_id.find(unread);
|
||||
if (iter == m_last_message_id.end()) continue;
|
||||
auto &e = data.ReadStates.emplace_back();
|
||||
e.ID = unread;
|
||||
e.LastMessageID = iter->second;
|
||||
}
|
||||
|
||||
if (data.ReadStates.empty()) return;
|
||||
|
||||
m_http.MakePOST("/read-states/ack-bulk", nlohmann::json(data).dump(), [this, callback](const http::response_type &response) {
|
||||
if (CheckCode(response))
|
||||
callback(DiscordError::NONE);
|
||||
else
|
||||
callback(GetCodeFromResponse(response));
|
||||
});
|
||||
}
|
||||
|
||||
void DiscordClient::MuteChannel(Snowflake channel_id, sigc::slot<void(DiscordError code)> callback) {
|
||||
const auto channel = GetChannel(channel_id);
|
||||
if (!channel.has_value()) return;
|
||||
const auto guild_id_path = channel->GuildID.has_value() ? std::to_string(*channel->GuildID) : "@me"s;
|
||||
nlohmann::json j;
|
||||
j["channel_overrides"][std::to_string(channel_id)]["mute_config"] = MuteConfigData { std::nullopt, -1 };
|
||||
j["channel_overrides"][std::to_string(channel_id)]["muted"] = true;
|
||||
m_http.MakePATCH("/users/@me/guilds/" + guild_id_path + "/settings", j.dump(), [this, callback](const http::response_type &response) {
|
||||
if (CheckCode(response))
|
||||
callback(DiscordError::NONE);
|
||||
else
|
||||
callback(GetCodeFromResponse(response));
|
||||
});
|
||||
}
|
||||
|
||||
void DiscordClient::UnmuteChannel(Snowflake channel_id, sigc::slot<void(DiscordError code)> callback) {
|
||||
const auto channel = GetChannel(channel_id);
|
||||
if (!channel.has_value()) return;
|
||||
const auto guild_id_path = channel->GuildID.has_value() ? std::to_string(*channel->GuildID) : "@me"s;
|
||||
nlohmann::json j;
|
||||
j["channel_overrides"][std::to_string(channel_id)]["muted"] = false;
|
||||
m_http.MakePATCH("/users/@me/guilds/" + guild_id_path + "/settings", j.dump(), [this, callback](const http::response_type &response) {
|
||||
if (CheckCode(response))
|
||||
callback(DiscordError::NONE);
|
||||
else
|
||||
callback(GetCodeFromResponse(response));
|
||||
});
|
||||
}
|
||||
|
||||
void DiscordClient::MarkAllAsRead(sigc::slot<void(DiscordError code)> callback) {
|
||||
AckBulkData data;
|
||||
for (const auto &[unread, mention_count] : m_unread) {
|
||||
const auto iter = m_last_message_id.find(unread);
|
||||
if (iter == m_last_message_id.end()) continue;
|
||||
auto &e = data.ReadStates.emplace_back();
|
||||
e.ID = unread;
|
||||
e.LastMessageID = iter->second;
|
||||
}
|
||||
|
||||
if (data.ReadStates.empty()) return;
|
||||
|
||||
m_http.MakePOST("/read-states/ack-bulk", nlohmann::json(data).dump(), [this, callback](const http::response_type &response) {
|
||||
if (CheckCode(response))
|
||||
callback(DiscordError::NONE);
|
||||
else
|
||||
callback(GetCodeFromResponse(response));
|
||||
});
|
||||
}
|
||||
|
||||
void DiscordClient::MuteGuild(Snowflake id, sigc::slot<void(DiscordError code)> callback) {
|
||||
m_http.MakePATCH("/users/@me/guilds/" + std::to_string(id) + "/settings", R"({"muted":true})", [this, callback](const http::response_type &response) {
|
||||
if (CheckCode(response))
|
||||
callback(DiscordError::NONE);
|
||||
else
|
||||
callback(GetCodeFromResponse(response));
|
||||
});
|
||||
}
|
||||
|
||||
void DiscordClient::UnmuteGuild(Snowflake id, sigc::slot<void(DiscordError code)> callback) {
|
||||
m_http.MakePATCH("/users/@me/guilds/" + std::to_string(id) + "/settings", R"({"muted":false})", [this, callback](const http::response_type &response) {
|
||||
if (CheckCode(response))
|
||||
callback(DiscordError::NONE);
|
||||
else
|
||||
callback(GetCodeFromResponse(response));
|
||||
});
|
||||
}
|
||||
|
||||
void DiscordClient::MuteThread(Snowflake id, sigc::slot<void(DiscordError code)> callback) {
|
||||
m_http.MakePATCH("/channels/" + std::to_string(id) + "/thread-members/@me/settings", R"({"muted":true})", [this, callback](const http::response_type &response) {
|
||||
if (CheckCode(response))
|
||||
callback(DiscordError::NONE);
|
||||
else
|
||||
callback(GetCodeFromResponse(response));
|
||||
});
|
||||
}
|
||||
|
||||
void DiscordClient::UnmuteThread(Snowflake id, sigc::slot<void(DiscordError code)> callback) {
|
||||
m_http.MakePATCH("/channels/" + std::to_string(id) + "/thread-members/@me/settings", R"({"muted":false})", [this, callback](const http::response_type &response) {
|
||||
if (CheckCode(response))
|
||||
callback(DiscordError::NONE);
|
||||
else
|
||||
callback(GetCodeFromResponse(response));
|
||||
});
|
||||
}
|
||||
|
||||
void DiscordClient::FetchPinned(Snowflake id, sigc::slot<void(std::vector<Message>, DiscordError code)> callback) {
|
||||
// return from db if we know the pins have already been requested
|
||||
if (m_channels_pinned_requested.find(id) != m_channels_pinned_requested.end()) {
|
||||
@@ -1058,6 +1187,47 @@ void DiscordClient::SetUserAgent(std::string agent) {
|
||||
m_websocket.SetUserAgent(agent);
|
||||
}
|
||||
|
||||
bool DiscordClient::IsChannelMuted(Snowflake id) const noexcept {
|
||||
return m_muted_channels.find(id) != m_muted_channels.end();
|
||||
}
|
||||
|
||||
bool DiscordClient::IsGuildMuted(Snowflake id) const noexcept {
|
||||
return m_muted_guilds.find(id) != m_muted_guilds.end();
|
||||
}
|
||||
|
||||
int DiscordClient::GetUnreadStateForChannel(Snowflake id) const noexcept {
|
||||
const auto iter = m_unread.find(id);
|
||||
if (iter == m_unread.end()) return -1; // todo: no magic number (who am i kidding ill never change this)
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
bool DiscordClient::GetUnreadStateForGuild(Snowflake id, int &total_mentions) const noexcept {
|
||||
total_mentions = 0;
|
||||
bool has_any_unread = false;
|
||||
const auto channels = GetChannelsInGuild(id);
|
||||
for (const auto channel_id : channels) {
|
||||
const auto channel_unread = GetUnreadStateForChannel(channel_id);
|
||||
if (channel_unread > -1)
|
||||
total_mentions += channel_unread;
|
||||
|
||||
// channels under muted categories wont contribute to unread state
|
||||
if (const auto iter = m_channel_muted_parent.find(channel_id); iter != m_channel_muted_parent.end())
|
||||
continue;
|
||||
|
||||
if (!has_any_unread && channel_unread > -1 && !IsChannelMuted(channel_id))
|
||||
has_any_unread = true;
|
||||
}
|
||||
return has_any_unread;
|
||||
}
|
||||
|
||||
int DiscordClient::GetUnreadDMsCount() const {
|
||||
const auto channels = GetPrivateChannels();
|
||||
int count = 0;
|
||||
for (const auto channel_id : channels)
|
||||
if (!IsChannelMuted(channel_id) && GetUnreadStateForChannel(channel_id) > -1) count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
PresenceStatus DiscordClient::GetUserStatus(Snowflake id) const {
|
||||
auto it = m_user_to_status.find(id);
|
||||
if (it != m_user_to_status.end())
|
||||
@@ -1288,6 +1458,12 @@ void DiscordClient::HandleGatewayMessage(std::string str) {
|
||||
case GatewayEvent::THREAD_MEMBER_LIST_UPDATE: {
|
||||
HandleGatewayThreadMemberListUpdate(m);
|
||||
} break;
|
||||
case GatewayEvent::MESSAGE_ACK: {
|
||||
HandleGatewayMessageAck(m);
|
||||
} break;
|
||||
case GatewayEvent::USER_GUILD_SETTINGS_UPDATE: {
|
||||
HandleGatewayUserGuildSettingsUpdate(m);
|
||||
} break;
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
@@ -1377,6 +1553,7 @@ void DiscordClient::HandleGatewayReady(const GatewayMessage &msg) {
|
||||
m_store.BeginTransaction();
|
||||
|
||||
for (const auto &dm : data.PrivateChannels) {
|
||||
m_guild_to_channels[Snowflake::Invalid].insert(dm.ID);
|
||||
m_store.SetChannel(dm.ID, dm);
|
||||
if (dm.Recipients.has_value())
|
||||
for (const auto &recipient : *dm.Recipients)
|
||||
@@ -1409,6 +1586,10 @@ void DiscordClient::HandleGatewayReady(const GatewayMessage &msg) {
|
||||
m_session_id = data.SessionID;
|
||||
m_user_data = data.SelfUser;
|
||||
m_user_settings = data.Settings;
|
||||
|
||||
HandleReadyReadState(data);
|
||||
HandleReadyGuildSettings(data);
|
||||
|
||||
m_signal_gateway_ready.emit();
|
||||
}
|
||||
|
||||
@@ -1417,6 +1598,12 @@ void DiscordClient::HandleGatewayMessageCreate(const GatewayMessage &msg) {
|
||||
StoreMessageData(data);
|
||||
if (data.GuildID.has_value())
|
||||
AddUserToGuild(data.Author.ID, *data.GuildID);
|
||||
m_last_message_id[data.ChannelID] = data.ID;
|
||||
if (data.Author.ID != GetUserData().ID)
|
||||
m_unread[data.ChannelID];
|
||||
if (data.DoesMention(GetUserData().ID)) {
|
||||
m_unread[data.ChannelID]++;
|
||||
}
|
||||
m_signal_message_create.emit(data);
|
||||
}
|
||||
|
||||
@@ -1752,9 +1939,24 @@ void DiscordClient::HandleGatewayThreadMembersUpdate(const GatewayMessage &msg)
|
||||
|
||||
void DiscordClient::HandleGatewayThreadMemberUpdate(const GatewayMessage &msg) {
|
||||
ThreadMemberUpdateData data = msg.Data;
|
||||
if (!data.Member.ThreadID.has_value()) return;
|
||||
|
||||
m_joined_threads.insert(*data.Member.ThreadID);
|
||||
if (*data.Member.UserID == GetUserData().ID)
|
||||
m_signal_added_to_thread.emit(*data.Member.ThreadID);
|
||||
|
||||
if (data.Member.IsMuted.has_value()) {
|
||||
const bool was_muted = IsChannelMuted(*data.Member.ThreadID);
|
||||
const bool now_muted = *data.Member.IsMuted;
|
||||
|
||||
if (was_muted && !now_muted) {
|
||||
m_muted_channels.erase(*data.Member.ThreadID);
|
||||
m_signal_channel_unmuted.emit(*data.Member.ThreadID);
|
||||
} else if (!was_muted && now_muted) {
|
||||
m_muted_channels.insert(*data.Member.ThreadID);
|
||||
m_signal_channel_muted.emit(*data.Member.ThreadID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DiscordClient::HandleGatewayThreadUpdate(const GatewayMessage &msg) {
|
||||
@@ -1776,6 +1978,77 @@ void DiscordClient::HandleGatewayThreadMemberListUpdate(const GatewayMessage &ms
|
||||
m_signal_thread_member_list_update.emit(data);
|
||||
}
|
||||
|
||||
void DiscordClient::HandleGatewayMessageAck(const GatewayMessage &msg) {
|
||||
MessageAckData data = msg.Data;
|
||||
m_unread.erase(data.ChannelID);
|
||||
m_signal_message_ack.emit(data);
|
||||
}
|
||||
|
||||
void DiscordClient::HandleGatewayUserGuildSettingsUpdate(const GatewayMessage &msg) {
|
||||
UserGuildSettingsUpdateData data = msg.Data;
|
||||
const bool for_dms = !data.Settings.GuildID.IsValid();
|
||||
|
||||
const auto channels = for_dms ? GetPrivateChannels() : GetChannelsInGuild(data.Settings.GuildID);
|
||||
std::set<Snowflake> now_muted_channels;
|
||||
const auto now = Snowflake::FromNow();
|
||||
|
||||
if (!for_dms) {
|
||||
const bool was_muted = IsGuildMuted(data.Settings.GuildID);
|
||||
bool now_muted = false;
|
||||
if (data.Settings.Muted) {
|
||||
if (data.Settings.MuteConfig.EndTime.has_value()) {
|
||||
const auto end = Snowflake::FromISO8601(*data.Settings.MuteConfig.EndTime);
|
||||
if (end.IsValid() && end > now)
|
||||
now_muted = true;
|
||||
} else {
|
||||
now_muted = true;
|
||||
}
|
||||
}
|
||||
if (was_muted && !now_muted) {
|
||||
m_muted_guilds.erase(data.Settings.GuildID);
|
||||
m_signal_guild_unmuted.emit(data.Settings.GuildID);
|
||||
} else if (!was_muted && now_muted) {
|
||||
m_muted_guilds.insert(data.Settings.GuildID);
|
||||
m_signal_guild_muted.emit(data.Settings.GuildID);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &override : data.Settings.ChannelOverrides) {
|
||||
if (override.Muted) {
|
||||
if (override.MuteConfig.EndTime.has_value()) {
|
||||
const auto end = Snowflake::FromISO8601(*override.MuteConfig.EndTime);
|
||||
if (end.IsValid() && end > now)
|
||||
now_muted_channels.insert(override.ChannelID);
|
||||
} else {
|
||||
now_muted_channels.insert(override.ChannelID);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto &channel_id : channels) {
|
||||
const bool was_muted = IsChannelMuted(channel_id);
|
||||
const bool now_muted = now_muted_channels.find(channel_id) != now_muted_channels.end();
|
||||
if (now_muted) {
|
||||
m_muted_channels.insert(channel_id);
|
||||
if (!was_muted) {
|
||||
if (const auto chan = GetChannel(channel_id); chan.has_value() && chan->IsCategory())
|
||||
for (const auto child_id : chan->GetChildIDs())
|
||||
m_channel_muted_parent.insert(child_id);
|
||||
|
||||
m_signal_channel_muted.emit(channel_id);
|
||||
}
|
||||
} else {
|
||||
m_muted_channels.erase(channel_id);
|
||||
if (was_muted) {
|
||||
if (const auto chan = GetChannel(channel_id); chan.has_value() && chan->IsCategory())
|
||||
for (const auto child_id : chan->GetChildIDs())
|
||||
m_channel_muted_parent.erase(child_id);
|
||||
|
||||
m_signal_channel_unmuted.emit(channel_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DiscordClient::HandleGatewayReadySupplemental(const GatewayMessage &msg) {
|
||||
ReadySupplementalData data = msg.Data;
|
||||
for (const auto &p : data.MergedPresences.Friends) {
|
||||
@@ -1951,15 +2224,9 @@ void DiscordClient::AddUserToGuild(Snowflake user_id, Snowflake guild_id) {
|
||||
}
|
||||
|
||||
std::set<Snowflake> DiscordClient::GetPrivateChannels() const {
|
||||
auto ret = std::set<Snowflake>();
|
||||
|
||||
for (const auto &id : m_store.GetChannels()) {
|
||||
const auto chan = m_store.GetChannel(id);
|
||||
if (chan->Type == ChannelType::DM || chan->Type == ChannelType::GROUP_DM)
|
||||
ret.insert(id);
|
||||
}
|
||||
|
||||
return ret;
|
||||
if (const auto iter = m_guild_to_channels.find(Snowflake::Invalid); iter != m_guild_to_channels.end())
|
||||
return iter->second;
|
||||
return {};
|
||||
}
|
||||
|
||||
EPremiumType DiscordClient::GetSelfPremiumType() const {
|
||||
@@ -2103,6 +2370,103 @@ void DiscordClient::StoreMessageData(Message &msg) {
|
||||
StoreMessageData(**msg.ReferencedMessage);
|
||||
}
|
||||
|
||||
// some notes for myself
|
||||
// a read channel is determined by checking if the channel object's last message id is equal to the read state's last message id
|
||||
// channels without entries are also unread
|
||||
// here the absence of an entry in m_unread indicates a read channel and the value is only the mention count since the message doesnt matter
|
||||
// no entry.id cannot be a guild even though sometimes it looks like it
|
||||
void DiscordClient::HandleReadyReadState(const ReadyEventData &data) {
|
||||
for (const auto &guild : data.Guilds) {
|
||||
for (const auto &channel : *guild.Channels)
|
||||
if (channel.LastMessageID.has_value())
|
||||
m_last_message_id[channel.ID] = *channel.LastMessageID;
|
||||
for (const auto &thread : *guild.Threads)
|
||||
if (thread.LastMessageID.has_value())
|
||||
m_last_message_id[thread.ID] = *thread.LastMessageID;
|
||||
}
|
||||
for (const auto &channel : data.PrivateChannels)
|
||||
if (channel.LastMessageID.has_value())
|
||||
m_last_message_id[channel.ID] = *channel.LastMessageID;
|
||||
|
||||
for (const auto &entry : data.ReadState.Entries) {
|
||||
const auto it = m_last_message_id.find(entry.ID);
|
||||
if (it == m_last_message_id.end()) continue;
|
||||
if (it->second > entry.LastMessageID) {
|
||||
if (HasChannelPermission(GetUserData().ID, entry.ID, Permission::VIEW_CHANNEL))
|
||||
m_unread[entry.ID] = entry.MentionCount;
|
||||
}
|
||||
}
|
||||
|
||||
// channels that arent in the read state are considered unread
|
||||
for (const auto &guild : data.Guilds) {
|
||||
if (!guild.JoinedAt.has_value()) continue; // doubt this can happen but whatever
|
||||
const auto joined_at = Snowflake::FromISO8601(*guild.JoinedAt);
|
||||
for (const auto &channel : *guild.Channels) {
|
||||
if (channel.LastMessageID.has_value()) {
|
||||
// unread messages from before you joined dont count as unread
|
||||
if (*channel.LastMessageID < joined_at) continue;
|
||||
if (std::find_if(data.ReadState.Entries.begin(), data.ReadState.Entries.end(), [id = channel.ID](const ReadStateEntry &e) {
|
||||
return e.ID == id;
|
||||
}) == data.ReadState.Entries.end()) {
|
||||
// cant be unread if u cant even see the channel
|
||||
// better to check here since HasChannelPermission hits the store
|
||||
if (HasChannelPermission(GetUserData().ID, channel.ID, Permission::VIEW_CHANNEL))
|
||||
m_unread[channel.ID] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DiscordClient::HandleReadyGuildSettings(const ReadyEventData &data) {
|
||||
// i dont like this implementation for muted categories but its rather simple and doesnt use a horriiible amount of ram
|
||||
|
||||
std::unordered_map<Snowflake, std::vector<Snowflake>> category_children;
|
||||
for (const auto &guild : data.Guilds) {
|
||||
for (const auto &channel : *guild.Channels)
|
||||
if (channel.ParentID.has_value() && !channel.IsThread())
|
||||
category_children[*channel.ParentID].push_back(channel.ID);
|
||||
for (const auto &thread : *guild.Threads)
|
||||
if (thread.ThreadMember.has_value() && thread.ThreadMember->IsMuted.has_value() && *thread.ThreadMember->IsMuted)
|
||||
m_muted_channels.insert(thread.ID);
|
||||
}
|
||||
|
||||
const auto now = Snowflake::FromNow();
|
||||
for (const auto &entry : data.GuildSettings.Entries) {
|
||||
// even if muted is true a guild/channel can be unmuted if the current time passes mute_config.end_time
|
||||
if (entry.Muted) {
|
||||
if (entry.MuteConfig.EndTime.has_value()) {
|
||||
const auto end = Snowflake::FromISO8601(*entry.MuteConfig.EndTime);
|
||||
if (end.IsValid() && end > now)
|
||||
m_muted_guilds.insert(entry.GuildID);
|
||||
} else {
|
||||
m_muted_guilds.insert(entry.GuildID);
|
||||
}
|
||||
}
|
||||
for (const auto &override : entry.ChannelOverrides) {
|
||||
if (override.Muted) {
|
||||
if (const auto iter = category_children.find(override.ChannelID); iter != category_children.end())
|
||||
for (const auto child : iter->second)
|
||||
m_channel_muted_parent.insert(child);
|
||||
|
||||
if (override.MuteConfig.EndTime.has_value()) {
|
||||
const auto end = Snowflake::FromISO8601(*override.MuteConfig.EndTime);
|
||||
if (end.IsValid() && end > now)
|
||||
m_muted_channels.insert(override.ChannelID);
|
||||
} else {
|
||||
m_muted_channels.insert(override.ChannelID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DiscordClient::HandleUserGuildSettingsUpdateForDMs(const UserGuildSettingsUpdateData &data) {
|
||||
const auto channels = GetPrivateChannels();
|
||||
std::set<Snowflake> now_muted_channels;
|
||||
const auto now = Snowflake::FromNow();
|
||||
}
|
||||
|
||||
void DiscordClient::LoadEventMap() {
|
||||
m_event_map["READY"] = GatewayEvent::READY;
|
||||
m_event_map["MESSAGE_CREATE"] = GatewayEvent::MESSAGE_CREATE;
|
||||
@@ -2145,6 +2509,8 @@ void DiscordClient::LoadEventMap() {
|
||||
m_event_map["THREAD_MEMBER_UPDATE"] = GatewayEvent::THREAD_MEMBER_UPDATE;
|
||||
m_event_map["THREAD_UPDATE"] = GatewayEvent::THREAD_UPDATE;
|
||||
m_event_map["THREAD_MEMBER_LIST_UPDATE"] = GatewayEvent::THREAD_MEMBER_LIST_UPDATE;
|
||||
m_event_map["MESSAGE_ACK"] = GatewayEvent::MESSAGE_ACK;
|
||||
m_event_map["USER_GUILD_SETTINGS_UPDATE"] = GatewayEvent::USER_GUILD_SETTINGS_UPDATE;
|
||||
}
|
||||
|
||||
DiscordClient::type_signal_gateway_ready DiscordClient::signal_gateway_ready() {
|
||||
@@ -2307,6 +2673,10 @@ DiscordClient::type_signal_thread_member_list_update DiscordClient::signal_threa
|
||||
return m_signal_thread_member_list_update;
|
||||
}
|
||||
|
||||
DiscordClient::type_signal_message_ack DiscordClient::signal_message_ack() {
|
||||
return m_signal_message_ack;
|
||||
}
|
||||
|
||||
DiscordClient::type_signal_added_to_thread DiscordClient::signal_added_to_thread() {
|
||||
return m_signal_added_to_thread;
|
||||
}
|
||||
@@ -2319,6 +2689,22 @@ DiscordClient::type_signal_message_sent DiscordClient::signal_message_sent() {
|
||||
return m_signal_message_sent;
|
||||
}
|
||||
|
||||
DiscordClient::type_signal_channel_muted DiscordClient::signal_channel_muted() {
|
||||
return m_signal_channel_muted;
|
||||
}
|
||||
|
||||
DiscordClient::type_signal_channel_unmuted DiscordClient::signal_channel_unmuted() {
|
||||
return m_signal_channel_unmuted;
|
||||
}
|
||||
|
||||
DiscordClient::type_signal_guild_muted DiscordClient::signal_guild_muted() {
|
||||
return m_signal_guild_muted;
|
||||
}
|
||||
|
||||
DiscordClient::type_signal_guild_unmuted DiscordClient::signal_guild_unmuted() {
|
||||
return m_signal_guild_unmuted;
|
||||
}
|
||||
|
||||
DiscordClient::type_signal_message_send_fail DiscordClient::signal_message_send_fail() {
|
||||
return m_signal_message_send_fail;
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ public:
|
||||
std::vector<ChannelData> GetActiveThreads(Snowflake channel_id) const;
|
||||
void GetArchivedPublicThreads(Snowflake channel_id, sigc::slot<void(DiscordError, const ArchivedThreadsResponseData &)> callback);
|
||||
void GetArchivedPrivateThreads(Snowflake channel_id, sigc::slot<void(DiscordError, const ArchivedThreadsResponseData &)> callback);
|
||||
std::vector<Snowflake> GetChildChannelIDs(Snowflake parent_id) const;
|
||||
|
||||
bool IsThreadJoined(Snowflake thread_id) const;
|
||||
bool HasGuildPermission(Snowflake user_id, Snowflake guild_id, Permission perm) const;
|
||||
@@ -138,6 +139,15 @@ public:
|
||||
void LeaveThread(Snowflake channel_id, const std::string &location, sigc::slot<void(DiscordError code)> callback);
|
||||
void ArchiveThread(Snowflake channel_id, sigc::slot<void(DiscordError code)> callback);
|
||||
void UnArchiveThread(Snowflake channel_id, sigc::slot<void(DiscordError code)> callback);
|
||||
void MarkChannelAsRead(Snowflake channel_id, sigc::slot<void(DiscordError code)> callback);
|
||||
void MarkGuildAsRead(Snowflake guild_id, sigc::slot<void(DiscordError code)> callback);
|
||||
void MuteChannel(Snowflake channel_id, sigc::slot<void(DiscordError code)> callback);
|
||||
void UnmuteChannel(Snowflake channel_id, sigc::slot<void(DiscordError code)> callback);
|
||||
void MarkAllAsRead(sigc::slot<void(DiscordError code)> callback);
|
||||
void MuteGuild(Snowflake id, sigc::slot<void(DiscordError code)> callback);
|
||||
void UnmuteGuild(Snowflake id, sigc::slot<void(DiscordError code)> callback);
|
||||
void MuteThread(Snowflake id, sigc::slot<void(DiscordError code)> callback);
|
||||
void UnmuteThread(Snowflake id, sigc::slot<void(DiscordError code)> callback);
|
||||
|
||||
bool CanModifyRole(Snowflake guild_id, Snowflake role_id) const;
|
||||
bool CanModifyRole(Snowflake guild_id, Snowflake role_id, Snowflake user_id) const;
|
||||
@@ -182,6 +192,12 @@ public:
|
||||
void UpdateToken(std::string token);
|
||||
void SetUserAgent(std::string agent);
|
||||
|
||||
bool IsChannelMuted(Snowflake id) const noexcept;
|
||||
bool IsGuildMuted(Snowflake id) const noexcept;
|
||||
int GetUnreadStateForChannel(Snowflake id) const noexcept;
|
||||
bool GetUnreadStateForGuild(Snowflake id, int &total_mentions) const noexcept;
|
||||
int GetUnreadDMsCount() const;
|
||||
|
||||
PresenceStatus GetUserStatus(Snowflake id) const;
|
||||
|
||||
std::map<Snowflake, RelationshipType> GetRelationships() const;
|
||||
@@ -244,6 +260,8 @@ private:
|
||||
void HandleGatewayThreadMemberUpdate(const GatewayMessage &msg);
|
||||
void HandleGatewayThreadUpdate(const GatewayMessage &msg);
|
||||
void HandleGatewayThreadMemberListUpdate(const GatewayMessage &msg);
|
||||
void HandleGatewayMessageAck(const GatewayMessage &msg);
|
||||
void HandleGatewayUserGuildSettingsUpdate(const GatewayMessage &msg);
|
||||
void HandleGatewayReadySupplemental(const GatewayMessage &msg);
|
||||
void HandleGatewayReconnect(const GatewayMessage &msg);
|
||||
void HandleGatewayInvalidSession(const GatewayMessage &msg);
|
||||
@@ -259,6 +277,11 @@ private:
|
||||
|
||||
void StoreMessageData(Message &msg);
|
||||
|
||||
void HandleReadyReadState(const ReadyEventData &data);
|
||||
void HandleReadyGuildSettings(const ReadyEventData &data);
|
||||
|
||||
void HandleUserGuildSettingsUpdateForDMs(const UserGuildSettingsUpdateData &data);
|
||||
|
||||
std::string m_token;
|
||||
|
||||
void AddUserToGuild(Snowflake user_id, Snowflake guild_id);
|
||||
@@ -269,6 +292,11 @@ private:
|
||||
std::map<Snowflake, RelationshipType> m_user_relationships;
|
||||
std::set<Snowflake> m_joined_threads;
|
||||
std::map<Snowflake, std::vector<Snowflake>> m_thread_members;
|
||||
std::map<Snowflake, Snowflake> m_last_message_id;
|
||||
std::unordered_set<Snowflake> m_muted_guilds;
|
||||
std::unordered_set<Snowflake> m_muted_channels;
|
||||
std::unordered_map<Snowflake, int> m_unread;
|
||||
std::unordered_set<Snowflake> m_channel_muted_parent;
|
||||
|
||||
UserData m_user_data;
|
||||
UserSettings m_user_settings;
|
||||
@@ -343,6 +371,7 @@ public:
|
||||
typedef sigc::signal<void, ThreadMembersUpdateData> type_signal_thread_members_update;
|
||||
typedef sigc::signal<void, ThreadUpdateData> type_signal_thread_update;
|
||||
typedef sigc::signal<void, ThreadMemberListUpdateData> type_signal_thread_member_list_update;
|
||||
typedef sigc::signal<void, MessageAckData> type_signal_message_ack;
|
||||
|
||||
// not discord dispatch events
|
||||
typedef sigc::signal<void, Snowflake> type_signal_added_to_thread;
|
||||
@@ -350,6 +379,10 @@ public:
|
||||
typedef sigc::signal<void, Message> type_signal_message_unpinned;
|
||||
typedef sigc::signal<void, Message> type_signal_message_pinned;
|
||||
typedef sigc::signal<void, Message> type_signal_message_sent;
|
||||
typedef sigc::signal<void, Snowflake> type_signal_channel_muted;
|
||||
typedef sigc::signal<void, Snowflake> type_signal_channel_unmuted;
|
||||
typedef sigc::signal<void, Snowflake> type_signal_guild_muted;
|
||||
typedef sigc::signal<void, Snowflake> type_signal_guild_unmuted;
|
||||
|
||||
typedef sigc::signal<void, std::string /* nonce */, float /* retry_after */> type_signal_message_send_fail; // retry after param will be 0 if it failed for a reason that isnt slowmode
|
||||
typedef sigc::signal<void, bool, GatewayCloseCode> type_signal_disconnected; // bool true if reconnecting
|
||||
@@ -393,10 +426,15 @@ public:
|
||||
type_signal_thread_members_update signal_thread_members_update();
|
||||
type_signal_thread_update signal_thread_update();
|
||||
type_signal_thread_member_list_update signal_thread_member_list_update();
|
||||
type_signal_message_ack signal_message_ack();
|
||||
|
||||
type_signal_added_to_thread signal_added_to_thread();
|
||||
type_signal_removed_from_thread signal_removed_from_thread();
|
||||
type_signal_message_sent signal_message_sent();
|
||||
type_signal_channel_muted signal_channel_muted();
|
||||
type_signal_channel_unmuted signal_channel_unmuted();
|
||||
type_signal_guild_muted signal_guild_muted();
|
||||
type_signal_guild_unmuted signal_guild_unmuted();
|
||||
type_signal_message_send_fail signal_message_send_fail();
|
||||
type_signal_disconnected signal_disconnected();
|
||||
type_signal_connected signal_connected();
|
||||
@@ -440,10 +478,15 @@ protected:
|
||||
type_signal_thread_members_update m_signal_thread_members_update;
|
||||
type_signal_thread_update m_signal_thread_update;
|
||||
type_signal_thread_member_list_update m_signal_thread_member_list_update;
|
||||
type_signal_message_ack m_signal_message_ack;
|
||||
|
||||
type_signal_removed_from_thread m_signal_removed_from_thread;
|
||||
type_signal_added_to_thread m_signal_added_to_thread;
|
||||
type_signal_message_sent m_signal_message_sent;
|
||||
type_signal_channel_muted m_signal_channel_muted;
|
||||
type_signal_channel_unmuted m_signal_channel_unmuted;
|
||||
type_signal_guild_muted m_signal_guild_muted;
|
||||
type_signal_guild_unmuted m_signal_guild_unmuted;
|
||||
type_signal_message_send_fail m_signal_message_send_fail;
|
||||
type_signal_disconnected m_signal_disconnected;
|
||||
type_signal_connected m_signal_connected;
|
||||
|
||||
@@ -188,21 +188,6 @@ std::vector<Snowflake> GuildData::GetSortedChannels(Snowflake ignore) const {
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<RoleData> GuildData::FetchRoles() const {
|
||||
if (!Roles.has_value()) return {};
|
||||
std::vector<RoleData> 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);
|
||||
|
||||
@@ -50,7 +50,7 @@ struct GuildData {
|
||||
std::optional<int> VerificationLevel;
|
||||
std::optional<int> DefaultMessageNotifications;
|
||||
std::optional<int> ExplicitContentFilter;
|
||||
std::optional<std::vector<RoleData>> Roles; // only access id
|
||||
std::optional<std::vector<RoleData>> Roles;
|
||||
std::optional<std::vector<EmojiData>> Emojis; // only access id
|
||||
std::optional<std::unordered_set<std::string>> Features;
|
||||
std::optional<int> MFALevel;
|
||||
@@ -96,5 +96,4 @@ struct GuildData {
|
||||
bool HasAnimatedIcon() const;
|
||||
std::string GetIconURL(std::string ext = "png", std::string size = "32") const;
|
||||
std::vector<Snowflake> GetSortedChannels(Snowflake ignore = Snowflake::Invalid) const;
|
||||
std::vector<RoleData> FetchRoles() const; // sorted
|
||||
};
|
||||
|
||||
@@ -263,3 +263,9 @@ bool Message::IsDeleted() const {
|
||||
bool Message::IsEdited() const {
|
||||
return m_edited;
|
||||
}
|
||||
|
||||
bool Message::DoesMention(Snowflake id) const noexcept {
|
||||
return std::any_of(Mentions.begin(), Mentions.end(), [id](const UserData &user) {
|
||||
return user.ID == id;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -212,6 +212,8 @@ struct Message {
|
||||
bool IsDeleted() const;
|
||||
bool IsEdited() const;
|
||||
|
||||
bool DoesMention(Snowflake id) const noexcept;
|
||||
|
||||
private:
|
||||
bool m_deleted = false;
|
||||
bool m_edited = false;
|
||||
|
||||
@@ -119,6 +119,84 @@ void to_json(nlohmann::json &j, const UpdateStatusMessage &m) {
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, ReadStateEntry &m) {
|
||||
JS_ON("mention_count", m.MentionCount);
|
||||
JS_ON("last_message_id", m.LastMessageID);
|
||||
JS_D("id", m.ID);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const ReadStateEntry &m) {
|
||||
j["channel_id"] = m.ID;
|
||||
j["message_id"] = m.LastMessageID;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, ReadStateData &m) {
|
||||
JS_ON("version", m.Version);
|
||||
JS_ON("partial", m.IsPartial);
|
||||
JS_ON("entries", m.Entries);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, UserGuildSettingsChannelOverride &m) {
|
||||
JS_D("muted", m.Muted);
|
||||
JS_D("message_notifications", m.MessageNotifications);
|
||||
JS_D("collapsed", m.Collapsed);
|
||||
JS_D("channel_id", m.ChannelID);
|
||||
JS_N("mute_config", m.MuteConfig);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const UserGuildSettingsChannelOverride &m) {
|
||||
j["channel_id"] = m.ChannelID;
|
||||
j["collapsed"] = m.Collapsed;
|
||||
j["message_notifications"] = m.MessageNotifications;
|
||||
j["mute_config"] = m.MuteConfig;
|
||||
j["muted"] = m.Muted;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, MuteConfigData &m) {
|
||||
JS_ON("end_time", m.EndTime);
|
||||
JS_D("selected_time_window", m.SelectedTimeWindow);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const MuteConfigData &m) {
|
||||
if (m.EndTime.has_value())
|
||||
j["end_time"] = *m.EndTime;
|
||||
else
|
||||
j["end_time"] = nullptr;
|
||||
j["selected_time_window"] = m.SelectedTimeWindow;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, UserGuildSettingsEntry &m) {
|
||||
JS_D("version", m.Version);
|
||||
JS_D("suppress_roles", m.SuppressRoles);
|
||||
JS_D("suppress_everyone", m.SuppressEveryone);
|
||||
JS_D("muted", m.Muted);
|
||||
JS_D("mobile_push", m.MobilePush);
|
||||
JS_D("message_notifications", m.MessageNotifications);
|
||||
JS_D("hide_muted_channels", m.HideMutedChannels);
|
||||
JS_N("guild_id", m.GuildID);
|
||||
JS_D("channel_overrides", m.ChannelOverrides);
|
||||
JS_N("mute_config", m.MuteConfig);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const UserGuildSettingsEntry &m) {
|
||||
j["channel_overrides"] = m.ChannelOverrides;
|
||||
j["guild_id"] = m.GuildID;
|
||||
j["hide_muted_channels"] = m.HideMutedChannels;
|
||||
j["message_notifications"] = m.MessageNotifications;
|
||||
j["mobile_push"] = m.MobilePush;
|
||||
j["mute_config"] = m.MuteConfig;
|
||||
j["muted"] = m.Muted;
|
||||
j["suppress_everyone"] = m.SuppressEveryone;
|
||||
j["suppress_roles"] = m.SuppressRoles;
|
||||
j["version"] = m.Version;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, UserGuildSettingsData &m) {
|
||||
JS_D("version", m.Version);
|
||||
JS_D("partial", m.IsPartial);
|
||||
JS_D("entries", m.Entries);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, ReadyEventData &m) {
|
||||
JS_D("v", m.GatewayVersion);
|
||||
JS_D("user", m.SelfUser);
|
||||
@@ -132,6 +210,8 @@ void from_json(const nlohmann::json &j, ReadyEventData &m) {
|
||||
JS_ON("merged_members", m.MergedMembers);
|
||||
JS_O("relationships", m.Relationships);
|
||||
JS_O("guild_join_requests", m.GuildJoinRequests);
|
||||
JS_O("read_state", m.ReadState);
|
||||
JS_D("user_guild_settings", m.GuildSettings);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, MergedPresence &m) {
|
||||
@@ -532,3 +612,17 @@ void to_json(nlohmann::json &j, const ModifyChannelObject &m) {
|
||||
JS_IF("archived", m.Archived);
|
||||
JS_IF("locked", m.Locked);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, MessageAckData &m) {
|
||||
// JS_D("version", m.Version);
|
||||
JS_D("message_id", m.MessageID);
|
||||
JS_D("channel_id", m.ChannelID);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const AckBulkData &m) {
|
||||
j["read_states"] = m.ReadStates;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, UserGuildSettingsUpdateData &m) {
|
||||
m.Settings = j;
|
||||
}
|
||||
|
||||
@@ -78,6 +78,8 @@ enum class GatewayEvent : int {
|
||||
THREAD_MEMBER_UPDATE,
|
||||
THREAD_MEMBERS_UPDATE,
|
||||
THREAD_MEMBER_LIST_UPDATE,
|
||||
MESSAGE_ACK,
|
||||
USER_GUILD_SETTINGS_UPDATE,
|
||||
};
|
||||
|
||||
enum class GatewayCloseCode : uint16_t {
|
||||
@@ -224,6 +226,59 @@ struct UpdateStatusMessage {
|
||||
friend void to_json(nlohmann::json &j, const UpdateStatusMessage &m);
|
||||
};
|
||||
|
||||
struct ReadStateEntry {
|
||||
int MentionCount;
|
||||
Snowflake LastMessageID;
|
||||
Snowflake ID;
|
||||
// std::string LastPinTimestamp; iso
|
||||
|
||||
friend void from_json(const nlohmann::json &j, ReadStateEntry &m);
|
||||
friend void to_json(nlohmann::json &j, const ReadStateEntry &m);
|
||||
};
|
||||
|
||||
struct ReadStateData {
|
||||
int Version;
|
||||
bool IsPartial;
|
||||
std::vector<ReadStateEntry> Entries;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, ReadStateData &m);
|
||||
};
|
||||
|
||||
struct UserGuildSettingsChannelOverride {
|
||||
bool Muted;
|
||||
MuteConfigData MuteConfig;
|
||||
int MessageNotifications;
|
||||
bool Collapsed;
|
||||
Snowflake ChannelID;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, UserGuildSettingsChannelOverride &m);
|
||||
friend void to_json(nlohmann::json &j, const UserGuildSettingsChannelOverride &m);
|
||||
};
|
||||
|
||||
struct UserGuildSettingsEntry {
|
||||
int Version;
|
||||
bool SuppressRoles;
|
||||
bool SuppressEveryone;
|
||||
bool Muted;
|
||||
MuteConfigData MuteConfig;
|
||||
bool MobilePush;
|
||||
int MessageNotifications;
|
||||
bool HideMutedChannels;
|
||||
Snowflake GuildID;
|
||||
std::vector<UserGuildSettingsChannelOverride> ChannelOverrides;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, UserGuildSettingsEntry &m);
|
||||
friend void to_json(nlohmann::json &j, const UserGuildSettingsEntry &m);
|
||||
};
|
||||
|
||||
struct UserGuildSettingsData {
|
||||
int Version;
|
||||
bool IsPartial;
|
||||
std::vector<UserGuildSettingsEntry> Entries;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, UserGuildSettingsData &m);
|
||||
};
|
||||
|
||||
struct ReadyEventData {
|
||||
int GatewayVersion;
|
||||
UserData SelfUser;
|
||||
@@ -239,6 +294,8 @@ struct ReadyEventData {
|
||||
std::optional<std::vector<std::vector<GuildMember>>> MergedMembers;
|
||||
std::optional<std::vector<RelationshipData>> Relationships;
|
||||
std::optional<std::vector<GuildApplicationData>> GuildJoinRequests;
|
||||
ReadStateData ReadState;
|
||||
UserGuildSettingsData GuildSettings;
|
||||
// std::vector<Unknown> ConnectedAccounts; // opt
|
||||
// std::map<std::string, Unknown> Consents; // opt
|
||||
// std::vector<Unknown> Experiments; // opt
|
||||
@@ -745,3 +802,23 @@ struct ModifyChannelObject {
|
||||
|
||||
friend void to_json(nlohmann::json &j, const ModifyChannelObject &m);
|
||||
};
|
||||
|
||||
struct MessageAckData {
|
||||
// int Version; // what is this ?!?!?!!?
|
||||
Snowflake MessageID;
|
||||
Snowflake ChannelID;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, MessageAckData &m);
|
||||
};
|
||||
|
||||
struct AckBulkData {
|
||||
std::vector<ReadStateEntry> ReadStates;
|
||||
|
||||
friend void to_json(nlohmann::json &j, const AckBulkData &m);
|
||||
};
|
||||
|
||||
struct UserGuildSettingsUpdateData {
|
||||
UserGuildSettingsEntry Settings;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, UserGuildSettingsUpdateData &m);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#include "snowflake.hpp"
|
||||
#include "util.hpp"
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
#include <chrono>
|
||||
|
||||
constexpr static uint64_t DiscordEpochSeconds = 1420070400;
|
||||
|
||||
@@ -14,13 +15,13 @@ Snowflake::Snowflake(uint64_t n)
|
||||
: m_num(n) {}
|
||||
|
||||
Snowflake::Snowflake(const std::string &str) {
|
||||
if (str.size())
|
||||
if (!str.empty())
|
||||
m_num = std::stoull(str);
|
||||
else
|
||||
m_num = Invalid;
|
||||
}
|
||||
Snowflake::Snowflake(const Glib::ustring &str) {
|
||||
if (str.size())
|
||||
if (!str.empty())
|
||||
m_num = std::strtoull(str.c_str(), nullptr, 10);
|
||||
else
|
||||
m_num = Invalid;
|
||||
@@ -38,6 +39,16 @@ Snowflake Snowflake::FromNow() {
|
||||
return snowflake;
|
||||
}
|
||||
|
||||
Snowflake Snowflake::FromISO8601(std::string_view ts) {
|
||||
int yr, mon, day, hr, min, sec, tzhr, tzmin;
|
||||
float milli;
|
||||
if (std::sscanf(ts.data(), "%d-%d-%dT%d:%d:%d%f+%d:%d",
|
||||
&yr, &mon, &day, &hr, &min, &sec, &milli, &tzhr, &tzmin) != 9) return Snowflake::Invalid;
|
||||
const auto epoch = util::TimeToEpoch(yr, mon, day, hr, min, sec);
|
||||
if (epoch < DiscordEpochSeconds) return Snowflake::Invalid;
|
||||
return SecondsInterval * (epoch - DiscordEpochSeconds) + static_cast<uint64_t>(milli * static_cast<float>(SecondsInterval));
|
||||
}
|
||||
|
||||
bool Snowflake::IsValid() const {
|
||||
return m_num != Invalid;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ struct Snowflake {
|
||||
Snowflake(const Glib::ustring &str);
|
||||
|
||||
static Snowflake FromNow(); // not thread safe
|
||||
static Snowflake FromISO8601(std::string_view ts);
|
||||
|
||||
bool IsValid() const;
|
||||
std::string GetLocalTimestamp() const;
|
||||
@@ -26,7 +27,7 @@ struct Snowflake {
|
||||
return m_num;
|
||||
}
|
||||
|
||||
const static Snowflake Invalid; // makes sense to me
|
||||
const static Snowflake Invalid; // makes sense to me
|
||||
const static uint64_t SecondsInterval = 4194304000ULL; // the "difference" between two snowflakes one second apart
|
||||
|
||||
friend void from_json(const nlohmann::json &j, Snowflake &s);
|
||||
|
||||
@@ -15,7 +15,7 @@ Store::Store(bool mem_store)
|
||||
|
||||
m_db.Execute(R"(
|
||||
PRAGMA writable_schema = 1;
|
||||
DELETE FROM sqlite_master;
|
||||
DELETE FROM sqlite_master WHERE TYPE IN ("view", "table", "index", "trigger");
|
||||
PRAGMA writable_schema = 0;
|
||||
VACUUM;
|
||||
PRAGMA integrity_check;
|
||||
@@ -571,6 +571,23 @@ std::vector<ChannelData> Store::GetActiveThreads(Snowflake channel_id) const {
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<Snowflake> Store::GetChannelIDsWithParentID(Snowflake channel_id) const {
|
||||
auto &s = m_stmt_get_chan_ids_parent;
|
||||
|
||||
s->Bind(1, channel_id);
|
||||
|
||||
std::vector<Snowflake> ret;
|
||||
while (s->FetchOne()) {
|
||||
Snowflake x;
|
||||
s->Get(0, x);
|
||||
ret.push_back(x);
|
||||
}
|
||||
|
||||
s->Reset();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Store::AddReaction(const MessageReactionAddObject &data, bool byself) {
|
||||
auto &s = m_stmt_add_reaction;
|
||||
|
||||
@@ -765,6 +782,16 @@ std::optional<GuildData> 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 +988,17 @@ std::optional<RoleData> Store::GetRole(Snowflake id) const {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto role = GetRoleBound(s);
|
||||
|
||||
s->Reset();
|
||||
|
||||
return role;
|
||||
}
|
||||
|
||||
RoleData Store::GetRoleBound(std::unique_ptr<Statement> &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 +1008,6 @@ std::optional<RoleData> Store::GetRole(Snowflake id) const {
|
||||
s->Get(7, r.IsManaged);
|
||||
s->Get(8, r.IsMentionable);
|
||||
|
||||
s->Reset();
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -1726,6 +1759,14 @@ bool Store::CreateStatements() {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_stmt_get_guild_roles = std::make_unique<Statement>(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<Statement>(m_db, R"(
|
||||
REPLACE INTO emojis VALUES (
|
||||
?, ?, ?, ?, ?, ?, ?
|
||||
@@ -2096,6 +2137,14 @@ bool Store::CreateStatements() {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_stmt_get_chan_ids_parent = std::make_unique<Statement>(m_db, R"(
|
||||
SELECT id FROM channels WHERE parent_id = ?
|
||||
)");
|
||||
if (!m_stmt_get_chan_ids_parent->OK()) {
|
||||
fprintf(stderr, "failed to prepare get channel ids for parent statement: %s\n", m_db.ErrStr());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ public:
|
||||
std::vector<Message> GetMessagesBefore(Snowflake channel_id, Snowflake message_id, size_t limit) const;
|
||||
std::vector<Message> GetPinnedMessages(Snowflake channel_id) const;
|
||||
std::vector<ChannelData> GetActiveThreads(Snowflake channel_id) const; // public
|
||||
std::vector<Snowflake> GetChannelIDsWithParentID(Snowflake channel_id) const;
|
||||
|
||||
void AddReaction(const MessageReactionAddObject &data, bool byself);
|
||||
void RemoveReaction(const MessageReactionRemoveObject &data, bool byself);
|
||||
@@ -235,6 +236,7 @@ private:
|
||||
};
|
||||
|
||||
Message GetMessageBound(std::unique_ptr<Statement> &stmt) const;
|
||||
RoleData GetRoleBound(std::unique_ptr<Statement> &stmt) const;
|
||||
|
||||
void SetMessageInteractionPair(Snowflake message_id, const MessageInteractionData &interaction);
|
||||
|
||||
@@ -264,6 +266,7 @@ private:
|
||||
STMT(get_member);
|
||||
STMT(set_role);
|
||||
STMT(get_role);
|
||||
STMT(get_guild_roles);
|
||||
STMT(set_emoji);
|
||||
STMT(get_emoji);
|
||||
STMT(set_perm);
|
||||
@@ -298,5 +301,6 @@ private:
|
||||
STMT(add_reaction);
|
||||
STMT(sub_reaction);
|
||||
STMT(get_reactions);
|
||||
STMT(get_chan_ids_parent);
|
||||
#undef STMT
|
||||
};
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
#include "platform.hpp"
|
||||
#include "util.hpp"
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#include <config.h>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
using namespace std::literals::string_literals;
|
||||
|
||||
#if defined(_WIN32) && defined(_MSC_VER)
|
||||
#include <Windows.h>
|
||||
#include <Shlwapi.h>
|
||||
#include <ShlObj_core.h>
|
||||
#include <pango/pangocairo.h>
|
||||
#include <pango/pangofc-fontmap.h>
|
||||
#include <ShlObj_core.h>
|
||||
#include <Shlwapi.h>
|
||||
#include <Windows.h>
|
||||
#pragma comment(lib, "Shlwapi.lib")
|
||||
bool Platform::SetupFonts() {
|
||||
using namespace std::string_literals;
|
||||
@@ -107,17 +107,29 @@ std::string Platform::FindResourceFolder() {
|
||||
}
|
||||
|
||||
std::string Platform::FindConfigFile() {
|
||||
const auto x = std::getenv("ABADDON_CONFIG");
|
||||
if (x != nullptr)
|
||||
return x;
|
||||
const auto cfg = std::getenv("ABADDON_CONFIG");
|
||||
if (cfg != nullptr) return cfg;
|
||||
|
||||
const auto home_env = std::getenv("HOME");
|
||||
if (home_env != nullptr) {
|
||||
const auto home_path = home_env + "/.config/abaddon/abaddon.ini"s;
|
||||
for (auto path : { "./abaddon.ini"s, home_path }) {
|
||||
if (util::IsFile(path)) return path;
|
||||
// use config present in cwd first
|
||||
if (util::IsFile("./abaddon.ini"))
|
||||
return "./abaddon.ini";
|
||||
|
||||
if (const auto home_env = std::getenv("HOME")) {
|
||||
// use ~/.config if present
|
||||
if (auto home_path = home_env + "/.config/abaddon/abaddon.ini"s; util::IsFile(home_path)) {
|
||||
return home_path;
|
||||
}
|
||||
|
||||
// fallback to ~/.config if the directory exists/can be created
|
||||
std::error_code ec;
|
||||
const auto home_path = home_env + "/.config/abaddon"s;
|
||||
if (!util::IsFolder(home_path))
|
||||
std::filesystem::create_directories(home_path, ec);
|
||||
if (util::IsFolder(home_path))
|
||||
return home_path + "/abaddon.ini";
|
||||
}
|
||||
|
||||
// fallback to cwd if cant find + cant make in ~/.config
|
||||
puts("can't find configuration file!");
|
||||
return "./abaddon.ini";
|
||||
}
|
||||
|
||||
@@ -47,11 +47,15 @@ void SettingsManager::ReadSettings() {
|
||||
SMBOOL("gui", "owner_crown", ShowOwnerCrown);
|
||||
SMBOOL("gui", "save_state", SaveState);
|
||||
SMBOOL("gui", "stock_emojis", ShowStockEmojis);
|
||||
SMBOOL("gui", "unreads", Unreads);
|
||||
SMINT("http", "concurrent", CacheHTTPConcurrency);
|
||||
SMSTR("http", "user_agent", UserAgent);
|
||||
SMSTR("style", "expandercolor", ChannelsExpanderColor);
|
||||
SMSTR("style", "linkcolor", LinkColor);
|
||||
SMSTR("style", "nsfwchannelcolor", NSFWChannelColor);
|
||||
SMSTR("style", "channelcolor", ChannelColor);
|
||||
SMSTR("style", "mentionbadgecolor", MentionBadgeColor);
|
||||
SMSTR("style", "mentionbadgetextcolor", MentionBadgeTextColor);
|
||||
|
||||
#undef SMBOOL
|
||||
#undef SMSTR
|
||||
@@ -95,11 +99,15 @@ void SettingsManager::Close() {
|
||||
SMBOOL("gui", "owner_crown", ShowOwnerCrown);
|
||||
SMBOOL("gui", "save_state", SaveState);
|
||||
SMBOOL("gui", "stock_emojis", ShowStockEmojis);
|
||||
SMBOOL("gui", "unreads", Unreads);
|
||||
SMINT("http", "concurrent", CacheHTTPConcurrency);
|
||||
SMSTR("http", "user_agent", UserAgent);
|
||||
SMSTR("style", "expandercolor", ChannelsExpanderColor);
|
||||
SMSTR("style", "linkcolor", LinkColor);
|
||||
SMSTR("style", "nsfwchannelcolor", NSFWChannelColor);
|
||||
SMSTR("style", "channelcolor", ChannelColor);
|
||||
SMSTR("style", "mentionbadgecolor", MentionBadgeColor);
|
||||
SMSTR("style", "mentionbadgetextcolor", MentionBadgeTextColor);
|
||||
|
||||
#undef SMSTR
|
||||
#undef SMBOOL
|
||||
|
||||
@@ -26,16 +26,20 @@ public:
|
||||
#else
|
||||
bool ShowStockEmojis { true };
|
||||
#endif
|
||||
bool Unreads { true };
|
||||
|
||||
// [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
|
||||
// TODO: convert to StyleProperty... or maybe not? i still cant figure out what the "correct" method is for this
|
||||
std::string LinkColor { "rgba(40, 200, 180, 255)" };
|
||||
std::string ChannelsExpanderColor { "rgba(255, 83, 112, 255)" };
|
||||
std::string NSFWChannelColor { "#ed6666" };
|
||||
std::string ChannelColor { "#fbfbfb" };
|
||||
std::string MentionBadgeColor { "#b82525" };
|
||||
std::string MentionBadgeTextColor { "#fbfbfb" };
|
||||
};
|
||||
|
||||
SettingsManager(const std::string &filename);
|
||||
|
||||
29
src/util.cpp
29
src/util.cpp
@@ -1,4 +1,5 @@
|
||||
#include "util.hpp"
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
|
||||
Semaphore::Semaphore(int count)
|
||||
@@ -215,3 +216,31 @@ bool util::IsFile(std::string_view path) {
|
||||
if (ec) return false;
|
||||
return status.type() == std::filesystem::file_type::regular;
|
||||
}
|
||||
|
||||
constexpr bool IsLeapYear(int year) {
|
||||
if (year % 4 != 0) return false;
|
||||
if (year % 100 != 0) return true;
|
||||
return (year % 400) == 0;
|
||||
}
|
||||
|
||||
constexpr static int SecsPerMinute = 60;
|
||||
constexpr static int SecsPerHour = 3600;
|
||||
constexpr static int SecsPerDay = 86400;
|
||||
constexpr static std::array<int, 12> DaysOfMonth = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
||||
|
||||
// may god smite whoever is responsible for the absolutely abominable api that is C time functions
|
||||
// i shouldnt have to write this. mktime ALMOST works but it adds the current timezone offset. WHY???
|
||||
uint64_t util::TimeToEpoch(int year, int month, int day, int hour, int minute, int seconds) {
|
||||
uint64_t secs = 0;
|
||||
for (int y = 1970; y < year; ++y)
|
||||
secs += (IsLeapYear(y) ? 366 : 365) * SecsPerDay;
|
||||
for (int m = 1; m < month; ++m) {
|
||||
secs += DaysOfMonth[m - 1] * SecsPerDay;
|
||||
if (m == 2 && IsLeapYear(year)) secs += SecsPerDay;
|
||||
}
|
||||
secs += (day - 1) * SecsPerDay;
|
||||
secs += hour * SecsPerHour;
|
||||
secs += minute * SecsPerMinute;
|
||||
secs += seconds;
|
||||
return secs;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#include <type_traits>
|
||||
#include <gtkmm.h>
|
||||
|
||||
#define NOOP_CALLBACK [](...) {}
|
||||
|
||||
namespace util {
|
||||
template<typename T>
|
||||
struct is_optional : ::std::false_type {};
|
||||
@@ -25,6 +27,8 @@ struct is_optional<::std::optional<T>> : ::std::true_type {};
|
||||
bool IsFolder(std::string_view path);
|
||||
|
||||
bool IsFile(std::string_view path);
|
||||
|
||||
uint64_t TimeToEpoch(int year, int month, int day, int hour, int minute, int seconds);
|
||||
} // namespace util
|
||||
|
||||
class Semaphore {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -5,10 +5,13 @@ MainWindow::MainWindow()
|
||||
: m_main_box(Gtk::ORIENTATION_VERTICAL)
|
||||
, m_content_box(Gtk::ORIENTATION_HORIZONTAL)
|
||||
, m_chan_content_paned(Gtk::ORIENTATION_HORIZONTAL)
|
||||
, m_content_members_paned(Gtk::ORIENTATION_HORIZONTAL) {
|
||||
, m_content_members_paned(Gtk::ORIENTATION_HORIZONTAL)
|
||||
, m_accels(Gtk::AccelGroup::create()) {
|
||||
set_default_size(1200, 800);
|
||||
get_style_context()->add_class("app-window");
|
||||
|
||||
add_accel_group(m_accels);
|
||||
|
||||
m_menu_discord.set_label("Discord");
|
||||
m_menu_discord.set_submenu(m_menu_discord_sub);
|
||||
m_menu_discord_connect.set_label("Connect");
|
||||
@@ -42,9 +45,14 @@ MainWindow::MainWindow()
|
||||
m_menu_view_friends.set_label("Friends");
|
||||
m_menu_view_pins.set_label("Pins");
|
||||
m_menu_view_threads.set_label("Threads");
|
||||
m_menu_view_mark_guild_as_read.set_label("Mark Server as Read");
|
||||
m_menu_view_mark_guild_as_read.add_accelerator("activate", m_accels, GDK_KEY_Escape, Gdk::SHIFT_MASK, Gtk::ACCEL_VISIBLE);
|
||||
m_menu_view_mark_all_as_read.set_label("Mark All as Read");
|
||||
m_menu_view_sub.append(m_menu_view_friends);
|
||||
m_menu_view_sub.append(m_menu_view_pins);
|
||||
m_menu_view_sub.append(m_menu_view_threads);
|
||||
m_menu_view_sub.append(m_menu_view_mark_guild_as_read);
|
||||
m_menu_view_sub.append(m_menu_view_mark_all_as_read);
|
||||
m_menu_view_sub.signal_popped_up().connect(sigc::mem_fun(*this, &MainWindow::OnViewSubmenuPopup));
|
||||
|
||||
m_menu_bar.append(m_menu_file);
|
||||
@@ -98,6 +106,19 @@ MainWindow::MainWindow()
|
||||
m_signal_action_view_threads.emit(GetChatActiveChannel());
|
||||
});
|
||||
|
||||
m_menu_view_mark_guild_as_read.signal_activate().connect([this] {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto channel_id = GetChatActiveChannel();
|
||||
const auto channel = discord.GetChannel(channel_id);
|
||||
if (channel.has_value() && channel->GuildID.has_value()) {
|
||||
discord.MarkGuildAsRead(*channel->GuildID, NOOP_CALLBACK);
|
||||
}
|
||||
});
|
||||
|
||||
m_menu_view_mark_all_as_read.signal_activate().connect([this] {
|
||||
Abaddon::Get().GetDiscordClient().MarkAllAsRead(NOOP_CALLBACK);
|
||||
});
|
||||
|
||||
m_content_box.set_hexpand(true);
|
||||
m_content_box.set_vexpand(true);
|
||||
m_content_box.show();
|
||||
@@ -138,6 +159,7 @@ MainWindow::MainWindow()
|
||||
m_chan_content_paned.set_position(200);
|
||||
m_chan_content_paned.show();
|
||||
m_content_box.add(m_chan_content_paned);
|
||||
m_channel_list.UsePanedHack(m_chan_content_paned);
|
||||
|
||||
m_content_members_paned.pack1(m_content_stack);
|
||||
m_content_members_paned.pack2(*member_list);
|
||||
@@ -246,13 +268,18 @@ void MainWindow::OnDiscordSubmenuPopup(const Gdk::Rectangle *flipped_rect, const
|
||||
}
|
||||
|
||||
void MainWindow::OnViewSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y) {
|
||||
m_menu_view_friends.set_sensitive(Abaddon::Get().GetDiscordClient().IsStarted());
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const bool discord_active = discord.IsStarted();
|
||||
|
||||
m_menu_view_friends.set_sensitive(discord_active);
|
||||
m_menu_view_mark_guild_as_read.set_sensitive(discord_active);
|
||||
m_menu_view_mark_all_as_read.set_sensitive(discord_active);
|
||||
|
||||
auto channel_id = GetChatActiveChannel();
|
||||
m_menu_view_pins.set_sensitive(false);
|
||||
m_menu_view_threads.set_sensitive(false);
|
||||
if (channel_id.IsValid()) {
|
||||
auto channel = Abaddon::Get().GetDiscordClient().GetChannel(channel_id);
|
||||
if (channel.has_value()) {
|
||||
if (auto channel = discord.GetChannel(channel_id); channel.has_value()) {
|
||||
m_menu_view_threads.set_sensitive(channel->Type == ChannelType::GUILD_TEXT || channel->IsThread());
|
||||
m_menu_view_pins.set_sensitive(channel->Type == ChannelType::GUILD_TEXT || channel->Type == ChannelType::DM || channel->Type == ChannelType::GROUP_DM || channel->IsThread());
|
||||
}
|
||||
|
||||
@@ -74,6 +74,8 @@ protected:
|
||||
|
||||
Gtk::Stack m_content_stack;
|
||||
|
||||
Glib::RefPtr<Gtk::AccelGroup> m_accels;
|
||||
|
||||
Gtk::MenuBar m_menu_bar;
|
||||
Gtk::MenuItem m_menu_discord;
|
||||
Gtk::Menu m_menu_discord_sub;
|
||||
@@ -95,5 +97,7 @@ protected:
|
||||
Gtk::MenuItem m_menu_view_friends;
|
||||
Gtk::MenuItem m_menu_view_pins;
|
||||
Gtk::MenuItem m_menu_view_threads;
|
||||
Gtk::MenuItem m_menu_view_mark_guild_as_read;
|
||||
Gtk::MenuItem m_menu_view_mark_all_as_read;
|
||||
void OnViewSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user