mirror of
https://github.com/uowuo/abaddon.git
synced 2025-03-04 03:03:16 -05:00
Merge pull request #242 from uowuo/classic-channels
Discord-style channel/server listing
This commit is contained in:
commit
43f87ae381
36
README.md
36
README.md
@ -291,23 +291,25 @@ For example, memory_db would be set by adding `memory_db = true` under the line
|
||||
|
||||
#### gui
|
||||
|
||||
| Setting | Type | Default | Description |
|
||||
|-----------------------------|---------|---------|----------------------------------------------------------------------------------------------------------------------------|
|
||||
| `member_list_discriminator` | boolean | true | show user discriminators in the member list |
|
||||
| `stock_emojis` | boolean | true | allow abaddon to substitute unicode emojis with images from emojis.bin, must be false to allow GTK to render emojis itself |
|
||||
| `custom_emojis` | boolean | true | download and use custom Discord emojis |
|
||||
| `css` | string | | path to the main CSS file |
|
||||
| `animations` | boolean | true | use animated images where available (e.g. server icons, emojis, avatars). false means static images will be used |
|
||||
| `animated_guild_hover_only` | boolean | true | only animate guild icons when the guild is being hovered over |
|
||||
| `owner_crown` | boolean | true | show a crown next to the owner |
|
||||
| `unreads` | boolean | true | show unread indicators and mention badges |
|
||||
| `save_state` | boolean | true | save the state of the gui (active channels, tabs, expanded channels) |
|
||||
| `alt_menu` | boolean | false | keep the menu hidden unless revealed with alt key |
|
||||
| `hide_to_tray` | boolean | false | hide abaddon to the system tray on window close |
|
||||
| `show_deleted_indicator` | boolean | true | show \[deleted\] indicator next to deleted messages instead of actually deleting the message |
|
||||
| `font_scale` | double | | scale font rendering. 1 is unchanged |
|
||||
| `image_embed_clamp_width` | int | 400 | maximum width of image embeds |
|
||||
| `image_embed_clamp_height` | int | 300 | maximum height of image embeds |
|
||||
| Setting | Type | Default | Description |
|
||||
|--------------------------------|---------|---------|----------------------------------------------------------------------------------------------------------------------------|
|
||||
| `member_list_discriminator` | boolean | true | show user discriminators in the member list |
|
||||
| `stock_emojis` | boolean | true | allow abaddon to substitute unicode emojis with images from emojis.bin, must be false to allow GTK to render emojis itself |
|
||||
| `custom_emojis` | boolean | true | download and use custom Discord emojis |
|
||||
| `css` | string | | path to the main CSS file |
|
||||
| `animations` | boolean | true | use animated images where available (e.g. server icons, emojis, avatars). false means static images will be used |
|
||||
| `animated_guild_hover_only` | boolean | true | only animate guild icons when the guild is being hovered over |
|
||||
| `owner_crown` | boolean | true | show a crown next to the owner |
|
||||
| `unreads` | boolean | true | show unread indicators and mention badges |
|
||||
| `save_state` | boolean | true | save the state of the gui (active channels, tabs, expanded channels) |
|
||||
| `alt_menu` | boolean | false | keep the menu hidden unless revealed with alt key |
|
||||
| `hide_to_tray` | boolean | false | hide abaddon to the system tray on window close |
|
||||
| `show_deleted_indicator` | boolean | true | show \[deleted\] indicator next to deleted messages instead of actually deleting the message |
|
||||
| `font_scale` | double | | scale font rendering. 1 is unchanged |
|
||||
| `image_embed_clamp_width` | int | 400 | maximum width of image embeds |
|
||||
| `image_embed_clamp_height` | int | 300 | maximum height of image embeds |
|
||||
| `classic_channels` | boolean | false | use classic Discord-style interface for server/channel listing |
|
||||
| `classic_change_guild_on_open` | boolean | true | change displayed guild when selecting a channel (classic channel list) |
|
||||
|
||||
#### style
|
||||
|
||||
|
@ -3,3 +3,5 @@ actions/call-stop-symbolic
|
||||
status/microphone-disabled-symbolic
|
||||
status/audio-volume-muted-symbolic
|
||||
devices/camera-web-symbolic
|
||||
status/user-available-symbolic
|
||||
places/folder-symbolic
|
||||
|
@ -145,3 +145,37 @@
|
||||
.message-text.failed {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.guild-list-scroll > scrollbar {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.guild-list-scroll > scrollbar slider {
|
||||
border: none;
|
||||
margin: 0px;
|
||||
min-width: 0px;
|
||||
min-height: 0px;
|
||||
}
|
||||
|
||||
.channel-list .view:selected {
|
||||
background-color: @theme_selected_bg_color;
|
||||
}
|
||||
|
||||
.classic-guild-list > row {
|
||||
padding-left: 0px;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.classic-guild-list > row:hover {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.classic-guild-list-guild.has-unread {
|
||||
background: radial-gradient(7px circle at left, @theme_selected_bg_color 50%, transparent 20%);
|
||||
}
|
||||
|
||||
.classic-guild-list-guild box, .classic-guild-list-folder stack {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
@ -54,7 +54,10 @@ Abaddon::Abaddon()
|
||||
: m_settings(Platform::FindConfigFile())
|
||||
, m_discord(GetSettings().UseMemoryDB) // stupid but easy
|
||||
, m_emojis(GetResPath("/emojis.bin"))
|
||||
, m_audio(GetSettings().Backends) {
|
||||
#ifdef WITH_VOICE
|
||||
, m_audio(GetSettings().Backends)
|
||||
#endif
|
||||
{
|
||||
LoadFromSettings();
|
||||
|
||||
// todo: set user agent for non-client(?)
|
||||
|
@ -1,4 +1,4 @@
|
||||
#include "channelscellrenderer.hpp"
|
||||
#include "cellrendererchannels.hpp"
|
||||
#include <gdkmm/general.h>
|
||||
#include "abaddon.hpp"
|
||||
|
131
src/components/channellist/channellist.cpp
Normal file
131
src/components/channellist/channellist.cpp
Normal file
@ -0,0 +1,131 @@
|
||||
#include "channellist.hpp"
|
||||
|
||||
#include "abaddon.hpp"
|
||||
|
||||
ChannelList::ChannelList() {
|
||||
get_style_context()->add_class("channel-browser-pane");
|
||||
|
||||
ConnectSignals();
|
||||
|
||||
m_guilds.set_halign(Gtk::ALIGN_START);
|
||||
|
||||
m_guilds_scroll.get_style_context()->add_class("guild-list-scroll");
|
||||
m_guilds_scroll.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
|
||||
|
||||
m_guilds.signal_guild_selected().connect([this](Snowflake guild_id) {
|
||||
m_tree.SetSelectedGuild(guild_id);
|
||||
});
|
||||
|
||||
m_guilds.signal_dms_selected().connect([this]() {
|
||||
m_tree.SetSelectedDMs();
|
||||
});
|
||||
|
||||
m_guilds.show();
|
||||
m_tree.show();
|
||||
m_guilds_scroll.add(m_guilds);
|
||||
pack_start(m_guilds_scroll, false, false); // only take the space it needs
|
||||
pack_start(m_tree, true, true); // use all the remaining space
|
||||
}
|
||||
|
||||
void ChannelList::UpdateListing() {
|
||||
m_tree.UpdateListing();
|
||||
if (m_is_classic) m_guilds.UpdateListing();
|
||||
}
|
||||
|
||||
void ChannelList::SetActiveChannel(Snowflake id, bool expand_to) {
|
||||
if (Abaddon::Get().GetSettings().ClassicChangeGuildOnOpen) {
|
||||
if (const auto channel = Abaddon::Get().GetDiscordClient().GetChannel(id); channel.has_value() && channel->GuildID.has_value()) {
|
||||
m_tree.SetSelectedGuild(*channel->GuildID);
|
||||
} else {
|
||||
m_tree.SetSelectedDMs();
|
||||
}
|
||||
}
|
||||
|
||||
m_tree.SetActiveChannel(id, expand_to);
|
||||
}
|
||||
|
||||
void ChannelList::UseExpansionState(const ExpansionStateRoot &state) {
|
||||
m_tree.UseExpansionState(state);
|
||||
}
|
||||
|
||||
ExpansionStateRoot ChannelList::GetExpansionState() const {
|
||||
return m_tree.GetExpansionState();
|
||||
}
|
||||
|
||||
void ChannelList::UsePanedHack(Gtk::Paned &paned) {
|
||||
m_tree.UsePanedHack(paned);
|
||||
}
|
||||
|
||||
void ChannelList::SetClassic(bool value) {
|
||||
m_is_classic = value;
|
||||
m_tree.SetClassic(value);
|
||||
m_guilds_scroll.set_visible(value);
|
||||
}
|
||||
|
||||
void ChannelList::ConnectSignals() {
|
||||
// TODO: if these all just travel upwards to the singleton then get rid of them but mayeb they dont
|
||||
|
||||
#ifdef WITH_LIBHANDY
|
||||
m_tree.signal_action_open_new_tab().connect([this](Snowflake id) {
|
||||
m_signal_action_open_new_tab.emit(id);
|
||||
});
|
||||
#endif
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
m_tree.signal_action_join_voice_channel().connect([this](Snowflake id) {
|
||||
m_signal_action_join_voice_channel.emit(id);
|
||||
});
|
||||
|
||||
m_tree.signal_action_disconnect_voice().connect([this]() {
|
||||
m_signal_action_disconnect_voice.emit();
|
||||
});
|
||||
#endif
|
||||
|
||||
m_tree.signal_action_channel_item_select().connect([this](Snowflake id) {
|
||||
m_signal_action_channel_item_select.emit(id);
|
||||
});
|
||||
|
||||
m_tree.signal_action_guild_leave().connect([this](Snowflake id) {
|
||||
m_signal_action_guild_leave.emit(id);
|
||||
});
|
||||
|
||||
m_tree.signal_action_guild_settings().connect([this](Snowflake id) {
|
||||
m_signal_action_guild_settings.emit(id);
|
||||
});
|
||||
|
||||
m_guilds.signal_action_guild_leave().connect([this](Snowflake id) {
|
||||
m_signal_action_guild_leave.emit(id);
|
||||
});
|
||||
|
||||
m_guilds.signal_action_guild_settings().connect([this](Snowflake id) {
|
||||
m_signal_action_guild_settings.emit(id);
|
||||
});
|
||||
}
|
||||
|
||||
#ifdef WITH_LIBHANDY
|
||||
ChannelList::type_signal_action_open_new_tab ChannelList::signal_action_open_new_tab() {
|
||||
return m_signal_action_open_new_tab;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
ChannelList::type_signal_action_join_voice_channel ChannelList::signal_action_join_voice_channel() {
|
||||
return m_signal_action_join_voice_channel;
|
||||
}
|
||||
|
||||
ChannelList::type_signal_action_disconnect_voice ChannelList::signal_action_disconnect_voice() {
|
||||
return m_signal_action_disconnect_voice;
|
||||
}
|
||||
#endif
|
||||
|
||||
ChannelList::type_signal_action_channel_item_select ChannelList::signal_action_channel_item_select() {
|
||||
return m_signal_action_channel_item_select;
|
||||
}
|
||||
|
||||
ChannelList::type_signal_action_guild_leave ChannelList::signal_action_guild_leave() {
|
||||
return m_signal_action_guild_leave;
|
||||
}
|
||||
|
||||
ChannelList::type_signal_action_guild_settings ChannelList::signal_action_guild_settings() {
|
||||
return m_signal_action_guild_settings;
|
||||
}
|
71
src/components/channellist/channellist.hpp
Normal file
71
src/components/channellist/channellist.hpp
Normal file
@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
#include <gtkmm/box.h>
|
||||
#include <gtkmm/paned.h>
|
||||
#include "channellisttree.hpp"
|
||||
#include "classic/guildlist.hpp"
|
||||
#include "discord/snowflake.hpp"
|
||||
#include "state.hpp"
|
||||
|
||||
// Contains the actual ChannelListTree and the classic listing if enabled
|
||||
class ChannelList : public Gtk::HBox {
|
||||
// have to proxy public and signals to underlying tree... ew!!!
|
||||
public:
|
||||
ChannelList();
|
||||
|
||||
void UpdateListing();
|
||||
void SetActiveChannel(Snowflake id, bool expand_to);
|
||||
|
||||
// channel list should be populated when this is called
|
||||
void UseExpansionState(const ExpansionStateRoot &state);
|
||||
ExpansionStateRoot GetExpansionState() const;
|
||||
|
||||
void UsePanedHack(Gtk::Paned &paned);
|
||||
|
||||
void SetClassic(bool value);
|
||||
|
||||
private:
|
||||
void ConnectSignals();
|
||||
|
||||
ChannelListTree m_tree;
|
||||
|
||||
Gtk::ScrolledWindow m_guilds_scroll;
|
||||
GuildList m_guilds;
|
||||
|
||||
bool m_is_classic = false;
|
||||
|
||||
public:
|
||||
using type_signal_action_channel_item_select = sigc::signal<void, Snowflake>;
|
||||
using type_signal_action_guild_leave = sigc::signal<void, Snowflake>;
|
||||
using type_signal_action_guild_settings = sigc::signal<void, Snowflake>;
|
||||
|
||||
#ifdef WITH_LIBHANDY
|
||||
using type_signal_action_open_new_tab = sigc::signal<void, Snowflake>;
|
||||
type_signal_action_open_new_tab signal_action_open_new_tab();
|
||||
#endif
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
using type_signal_action_join_voice_channel = sigc::signal<void, Snowflake>;
|
||||
using type_signal_action_disconnect_voice = sigc::signal<void>;
|
||||
|
||||
type_signal_action_join_voice_channel signal_action_join_voice_channel();
|
||||
type_signal_action_disconnect_voice signal_action_disconnect_voice();
|
||||
#endif
|
||||
|
||||
type_signal_action_channel_item_select signal_action_channel_item_select();
|
||||
type_signal_action_guild_leave signal_action_guild_leave();
|
||||
type_signal_action_guild_settings signal_action_guild_settings();
|
||||
|
||||
private:
|
||||
type_signal_action_channel_item_select m_signal_action_channel_item_select;
|
||||
type_signal_action_guild_leave m_signal_action_guild_leave;
|
||||
type_signal_action_guild_settings m_signal_action_guild_settings;
|
||||
|
||||
#ifdef WITH_LIBHANDY
|
||||
type_signal_action_open_new_tab m_signal_action_open_new_tab;
|
||||
#endif
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
type_signal_action_join_voice_channel m_signal_action_join_voice_channel;
|
||||
type_signal_action_disconnect_voice m_signal_action_disconnect_voice;
|
||||
#endif
|
||||
};
|
@ -1,14 +1,20 @@
|
||||
#include "channels.hpp"
|
||||
#include "imgmanager.hpp"
|
||||
#include "channellisttree.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <gtkmm/main.h>
|
||||
|
||||
#include "abaddon.hpp"
|
||||
#include "imgmanager.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
ChannelList::ChannelList()
|
||||
: Glib::ObjectBase(typeid(ChannelList))
|
||||
ChannelListTree::ChannelListTree()
|
||||
: Glib::ObjectBase(typeid(ChannelListTree))
|
||||
, m_model(Gtk::TreeStore::create(m_columns))
|
||||
, m_filter_model(Gtk::TreeModelFilter::create(m_model))
|
||||
, m_sort_model(Gtk::TreeModelSort::create(m_filter_model))
|
||||
, m_menu_guild_copy_id("_Copy ID", true)
|
||||
, m_menu_guild_settings("View _Settings", true)
|
||||
, m_menu_guild_leave("_Leave", true)
|
||||
@ -37,9 +43,12 @@ ChannelList::ChannelList()
|
||||
, m_menu_thread_mark_as_read("Mark as _Read", true) {
|
||||
get_style_context()->add_class("channel-list");
|
||||
|
||||
// todo: move to method
|
||||
// Filter iters
|
||||
const auto cb = [this](const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *column) {
|
||||
auto row = *m_model->get_iter(path);
|
||||
auto view_path = ConvertViewPathToModel(path);
|
||||
if (!view_path) return;
|
||||
auto row = *m_model->get_iter(view_path);
|
||||
if (!row) return;
|
||||
const auto type = row[m_columns.m_type];
|
||||
// text channels should not be allowed to be collapsed
|
||||
// maybe they should be but it seems a little difficult to handle expansion to permit this
|
||||
@ -60,12 +69,12 @@ ChannelList::ChannelList()
|
||||
}
|
||||
};
|
||||
m_view.signal_row_activated().connect(cb, false);
|
||||
m_view.signal_row_collapsed().connect(sigc::mem_fun(*this, &ChannelList::OnRowCollapsed), false);
|
||||
m_view.signal_row_expanded().connect(sigc::mem_fun(*this, &ChannelList::OnRowExpanded), false);
|
||||
m_view.signal_row_collapsed().connect(sigc::mem_fun(*this, &ChannelListTree::OnRowCollapsed), false);
|
||||
m_view.signal_row_expanded().connect(sigc::mem_fun(*this, &ChannelListTree::OnRowExpanded), false);
|
||||
m_view.set_activate_on_single_click(true);
|
||||
m_view.get_selection()->set_mode(Gtk::SELECTION_SINGLE);
|
||||
m_view.get_selection()->set_select_function(sigc::mem_fun(*this, &ChannelList::SelectionFunc));
|
||||
m_view.signal_button_press_event().connect(sigc::mem_fun(*this, &ChannelList::OnButtonPressEvent), false);
|
||||
m_view.get_selection()->set_select_function(sigc::mem_fun(*this, &ChannelListTree::SelectionFunc));
|
||||
m_view.signal_button_press_event().connect(sigc::mem_fun(*this, &ChannelListTree::OnButtonPressEvent), false);
|
||||
|
||||
m_view.set_hexpand(true);
|
||||
m_view.set_vexpand(true);
|
||||
@ -73,13 +82,33 @@ ChannelList::ChannelList()
|
||||
m_view.set_show_expanders(false);
|
||||
m_view.set_enable_search(false);
|
||||
m_view.set_headers_visible(false);
|
||||
m_view.set_model(m_model);
|
||||
m_model->set_sort_column(m_columns.m_sort, Gtk::SORT_ASCENDING);
|
||||
m_view.set_model(m_sort_model);
|
||||
m_sort_model->set_sort_column(m_columns.m_sort, Gtk::SORT_ASCENDING);
|
||||
m_sort_model->set_sort_func(m_columns.m_sort, sigc::mem_fun(*this, &ChannelListTree::SortFunc));
|
||||
|
||||
m_model->signal_row_inserted().connect([this](const Gtk::TreeModel::Path &path, const Gtk::TreeModel::iterator &iter) {
|
||||
if (m_updating_listing) return;
|
||||
if (auto parent = iter->parent(); parent && (*parent)[m_columns.m_expanded])
|
||||
m_view.expand_row(m_model->get_path(parent), false);
|
||||
if (auto parent = iter->parent(); parent && (*parent)[m_columns.m_expanded]) {
|
||||
if (const auto view_path = ConvertModelPathToView(m_model->get_path(parent))) {
|
||||
m_view.expand_row(view_path, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
m_filter_model->set_visible_func([this](const Gtk::TreeModel::const_iterator &iter) -> bool {
|
||||
if (!m_classic || m_updating_listing) return true;
|
||||
|
||||
const RenderType type = (*iter)[m_columns.m_type];
|
||||
|
||||
if (m_classic_selected_dms) {
|
||||
if (iter->parent()) return true;
|
||||
return type == RenderType::DMHeader;
|
||||
}
|
||||
|
||||
if (type == RenderType::Guild) {
|
||||
return (*iter)[m_columns.m_id] == m_classic_selected_guild;
|
||||
}
|
||||
return type != RenderType::DMHeader;
|
||||
});
|
||||
|
||||
m_view.show();
|
||||
@ -263,40 +292,107 @@ ChannelList::ChannelList()
|
||||
m_menu_thread.show_all();
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
discord.signal_message_create().connect(sigc::mem_fun(*this, &ChannelList::OnMessageCreate));
|
||||
discord.signal_guild_create().connect(sigc::mem_fun(*this, &ChannelList::UpdateNewGuild));
|
||||
discord.signal_guild_delete().connect(sigc::mem_fun(*this, &ChannelList::UpdateRemoveGuild));
|
||||
discord.signal_channel_delete().connect(sigc::mem_fun(*this, &ChannelList::UpdateRemoveChannel));
|
||||
discord.signal_channel_update().connect(sigc::mem_fun(*this, &ChannelList::UpdateChannel));
|
||||
discord.signal_channel_create().connect(sigc::mem_fun(*this, &ChannelList::UpdateCreateChannel));
|
||||
discord.signal_thread_delete().connect(sigc::mem_fun(*this, &ChannelList::OnThreadDelete));
|
||||
discord.signal_thread_update().connect(sigc::mem_fun(*this, &ChannelList::OnThreadUpdate));
|
||||
discord.signal_thread_list_sync().connect(sigc::mem_fun(*this, &ChannelList::OnThreadListSync));
|
||||
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));
|
||||
discord.signal_message_create().connect(sigc::mem_fun(*this, &ChannelListTree::OnMessageCreate));
|
||||
discord.signal_guild_create().connect(sigc::mem_fun(*this, &ChannelListTree::UpdateNewGuild));
|
||||
discord.signal_guild_delete().connect(sigc::mem_fun(*this, &ChannelListTree::UpdateRemoveGuild));
|
||||
discord.signal_channel_delete().connect(sigc::mem_fun(*this, &ChannelListTree::UpdateRemoveChannel));
|
||||
discord.signal_channel_update().connect(sigc::mem_fun(*this, &ChannelListTree::UpdateChannel));
|
||||
discord.signal_channel_create().connect(sigc::mem_fun(*this, &ChannelListTree::UpdateCreateChannel));
|
||||
discord.signal_thread_delete().connect(sigc::mem_fun(*this, &ChannelListTree::OnThreadDelete));
|
||||
discord.signal_thread_update().connect(sigc::mem_fun(*this, &ChannelListTree::OnThreadUpdate));
|
||||
discord.signal_thread_list_sync().connect(sigc::mem_fun(*this, &ChannelListTree::OnThreadListSync));
|
||||
discord.signal_added_to_thread().connect(sigc::mem_fun(*this, &ChannelListTree::OnThreadJoined));
|
||||
discord.signal_removed_from_thread().connect(sigc::mem_fun(*this, &ChannelListTree::OnThreadRemoved));
|
||||
discord.signal_guild_update().connect(sigc::mem_fun(*this, &ChannelListTree::UpdateGuild));
|
||||
discord.signal_message_ack().connect(sigc::mem_fun(*this, &ChannelListTree::OnMessageAck));
|
||||
discord.signal_channel_muted().connect(sigc::mem_fun(*this, &ChannelListTree::OnChannelMute));
|
||||
discord.signal_channel_unmuted().connect(sigc::mem_fun(*this, &ChannelListTree::OnChannelUnmute));
|
||||
discord.signal_guild_muted().connect(sigc::mem_fun(*this, &ChannelListTree::OnGuildMute));
|
||||
discord.signal_guild_unmuted().connect(sigc::mem_fun(*this, &ChannelListTree::OnGuildUnmute));
|
||||
|
||||
#if WITH_VOICE
|
||||
discord.signal_voice_user_connect().connect(sigc::mem_fun(*this, &ChannelList::OnVoiceUserConnect));
|
||||
discord.signal_voice_user_disconnect().connect(sigc::mem_fun(*this, &ChannelList::OnVoiceUserDisconnect));
|
||||
discord.signal_voice_state_set().connect(sigc::mem_fun(*this, &ChannelList::OnVoiceStateSet));
|
||||
discord.signal_voice_user_connect().connect(sigc::mem_fun(*this, &ChannelListTree::OnVoiceUserConnect));
|
||||
discord.signal_voice_user_disconnect().connect(sigc::mem_fun(*this, &ChannelListTree::OnVoiceUserDisconnect));
|
||||
discord.signal_voice_state_set().connect(sigc::mem_fun(*this, &ChannelListTree::OnVoiceStateSet));
|
||||
#endif
|
||||
}
|
||||
|
||||
void ChannelList::UsePanedHack(Gtk::Paned &paned) {
|
||||
paned.property_position().signal_changed().connect(sigc::mem_fun(*this, &ChannelList::OnPanedPositionChanged));
|
||||
void ChannelListTree::UsePanedHack(Gtk::Paned &paned) {
|
||||
paned.property_position().signal_changed().connect(sigc::mem_fun(*this, &ChannelListTree::OnPanedPositionChanged));
|
||||
}
|
||||
|
||||
void ChannelList::OnPanedPositionChanged() {
|
||||
void ChannelListTree::SetClassic(bool value) {
|
||||
m_classic = value;
|
||||
m_filter_model->refilter();
|
||||
}
|
||||
|
||||
void ChannelListTree::SetSelectedGuild(Snowflake guild_id) {
|
||||
m_classic_selected_guild = guild_id;
|
||||
m_classic_selected_dms = false;
|
||||
m_filter_model->refilter();
|
||||
auto guild_iter = GetIteratorForGuildFromID(guild_id);
|
||||
if (guild_iter) {
|
||||
if (auto view_iter = ConvertModelIterToView(guild_iter)) {
|
||||
m_view.expand_row(GetViewPathFromViewIter(view_iter), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelListTree::SetSelectedDMs() {
|
||||
m_classic_selected_dms = true;
|
||||
m_filter_model->refilter();
|
||||
if (m_dm_header) {
|
||||
if (auto view_path = ConvertModelPathToView(m_dm_header)) {
|
||||
m_view.expand_row(view_path, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ChannelListTree::SortFunc(const Gtk::TreeModel::iterator &a, const Gtk::TreeModel::iterator &b) {
|
||||
const RenderType a_type = (*a)[m_columns.m_type];
|
||||
const RenderType b_type = (*b)[m_columns.m_type];
|
||||
const int64_t a_sort = (*a)[m_columns.m_sort];
|
||||
const int64_t b_sort = (*b)[m_columns.m_sort];
|
||||
if (a_type == RenderType::DMHeader) return -1;
|
||||
if (b_type == RenderType::DMHeader) return 1;
|
||||
#ifdef WITH_VOICE
|
||||
if (a_type == RenderType::TextChannel && b_type == RenderType::VoiceChannel) return -1;
|
||||
if (b_type == RenderType::TextChannel && a_type == RenderType::VoiceChannel) return 1;
|
||||
#endif
|
||||
return static_cast<int>(std::clamp(a_sort - b_sort, int64_t(-1), int64_t(1)));
|
||||
}
|
||||
|
||||
void ChannelListTree::OnPanedPositionChanged() {
|
||||
m_view.queue_draw();
|
||||
}
|
||||
|
||||
void ChannelList::UpdateListing() {
|
||||
void ChannelListTree::UpdateListingClassic() {
|
||||
m_updating_listing = true;
|
||||
|
||||
// refilter so every row is visible
|
||||
// otherwise clear() causes a CRITICAL assert in a slot for the filter model
|
||||
m_filter_model->refilter();
|
||||
m_model->clear();
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto guild_ids = discord.GetUserSortedGuilds();
|
||||
for (const auto guild_id : guild_ids) {
|
||||
if (const auto guild = discord.GetGuild(guild_id); guild.has_value()) {
|
||||
AddGuild(*guild, m_model->children());
|
||||
}
|
||||
}
|
||||
|
||||
m_updating_listing = false;
|
||||
|
||||
AddPrivateChannels();
|
||||
}
|
||||
|
||||
void ChannelListTree::UpdateListing() {
|
||||
if (m_classic) {
|
||||
UpdateListingClassic();
|
||||
return;
|
||||
}
|
||||
|
||||
m_updating_listing = true;
|
||||
|
||||
m_model->clear();
|
||||
@ -366,7 +462,7 @@ void ChannelList::UpdateListing() {
|
||||
}
|
||||
|
||||
// TODO update for folders
|
||||
void ChannelList::UpdateNewGuild(const GuildData &guild) {
|
||||
void ChannelListTree::UpdateNewGuild(const GuildData &guild) {
|
||||
AddGuild(guild, m_model->children());
|
||||
// update sort order
|
||||
int sortnum = 0;
|
||||
@ -377,19 +473,19 @@ void ChannelList::UpdateNewGuild(const GuildData &guild) {
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelList::UpdateRemoveGuild(Snowflake id) {
|
||||
void ChannelListTree::UpdateRemoveGuild(Snowflake id) {
|
||||
auto iter = GetIteratorForGuildFromID(id);
|
||||
if (!iter) return;
|
||||
m_model->erase(iter);
|
||||
}
|
||||
|
||||
void ChannelList::UpdateRemoveChannel(Snowflake id) {
|
||||
void ChannelListTree::UpdateRemoveChannel(Snowflake id) {
|
||||
auto iter = GetIteratorForRowFromID(id);
|
||||
if (!iter) return;
|
||||
m_model->erase(iter);
|
||||
}
|
||||
|
||||
void ChannelList::UpdateChannel(Snowflake id) {
|
||||
void ChannelListTree::UpdateChannel(Snowflake id) {
|
||||
auto iter = GetIteratorForRowFromID(id);
|
||||
auto channel = Abaddon::Get().GetDiscordClient().GetChannel(id);
|
||||
if (!iter || !channel.has_value()) return;
|
||||
@ -413,7 +509,7 @@ void ChannelList::UpdateChannel(Snowflake id) {
|
||||
MoveRow(iter, new_parent);
|
||||
}
|
||||
|
||||
void ChannelList::UpdateCreateChannel(const ChannelData &channel) {
|
||||
void ChannelListTree::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;
|
||||
@ -439,7 +535,7 @@ void ChannelList::UpdateCreateChannel(const ChannelData &channel) {
|
||||
channel_row[m_columns.m_sort] = *channel.Position;
|
||||
}
|
||||
|
||||
void ChannelList::UpdateGuild(Snowflake id) {
|
||||
void ChannelListTree::UpdateGuild(Snowflake id) {
|
||||
auto iter = GetIteratorForGuildFromID(id);
|
||||
auto &img = Abaddon::Get().GetImageManager();
|
||||
const auto guild = Abaddon::Get().GetDiscordClient().GetGuild(id);
|
||||
@ -463,7 +559,7 @@ void ChannelList::UpdateGuild(Snowflake id) {
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelList::OnThreadJoined(Snowflake id) {
|
||||
void ChannelListTree::OnThreadJoined(Snowflake id) {
|
||||
if (GetIteratorForRowFromID(id)) return;
|
||||
const auto channel = Abaddon::Get().GetDiscordClient().GetChannel(id);
|
||||
if (!channel.has_value()) return;
|
||||
@ -472,16 +568,16 @@ void ChannelList::OnThreadJoined(Snowflake id) {
|
||||
CreateThreadRow(parent->children(), *channel);
|
||||
}
|
||||
|
||||
void ChannelList::OnThreadRemoved(Snowflake id) {
|
||||
void ChannelListTree::OnThreadRemoved(Snowflake id) {
|
||||
DeleteThreadRow(id);
|
||||
}
|
||||
|
||||
void ChannelList::OnThreadDelete(const ThreadDeleteData &data) {
|
||||
void ChannelListTree::OnThreadDelete(const ThreadDeleteData &data) {
|
||||
DeleteThreadRow(data.ID);
|
||||
}
|
||||
|
||||
// todo probably make the row stick around if its selected until the selection changes
|
||||
void ChannelList::OnThreadUpdate(const ThreadUpdateData &data) {
|
||||
void ChannelListTree::OnThreadUpdate(const ThreadUpdateData &data) {
|
||||
auto iter = GetIteratorForRowFromID(data.Thread.ID);
|
||||
if (iter)
|
||||
(*iter)[m_columns.m_name] = "- " + Glib::Markup::escape_text(*data.Thread.Name);
|
||||
@ -490,7 +586,7 @@ void ChannelList::OnThreadUpdate(const ThreadUpdateData &data) {
|
||||
DeleteThreadRow(data.Thread.ID);
|
||||
}
|
||||
|
||||
void ChannelList::OnThreadListSync(const ThreadListSyncData &data) {
|
||||
void ChannelListTree::OnThreadListSync(const ThreadListSyncData &data) {
|
||||
// get the threads in the guild
|
||||
std::vector<Snowflake> threads;
|
||||
auto guild_iter = GetIteratorForGuildFromID(data.GuildID);
|
||||
@ -526,7 +622,7 @@ void ChannelList::OnThreadListSync(const ThreadListSyncData &data) {
|
||||
}
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
void ChannelList::OnVoiceUserConnect(Snowflake user_id, Snowflake channel_id) {
|
||||
void ChannelListTree::OnVoiceUserConnect(Snowflake user_id, Snowflake channel_id) {
|
||||
auto parent_iter = GetIteratorForRowFromIDOfType(channel_id, RenderType::VoiceChannel);
|
||||
if (!parent_iter) parent_iter = GetIteratorForRowFromIDOfType(channel_id, RenderType::DM);
|
||||
if (!parent_iter) return;
|
||||
@ -536,48 +632,50 @@ void ChannelList::OnVoiceUserConnect(Snowflake user_id, Snowflake channel_id) {
|
||||
CreateVoiceParticipantRow(*user, parent_iter->children());
|
||||
}
|
||||
|
||||
void ChannelList::OnVoiceUserDisconnect(Snowflake user_id, Snowflake channel_id) {
|
||||
void ChannelListTree::OnVoiceUserDisconnect(Snowflake user_id, Snowflake channel_id) {
|
||||
if (auto iter = GetIteratorForRowFromIDOfType(user_id, RenderType::VoiceParticipant)) {
|
||||
m_model->erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelList::OnVoiceStateSet(Snowflake user_id, Snowflake channel_id, VoiceStateFlags flags) {
|
||||
void ChannelListTree::OnVoiceStateSet(Snowflake user_id, Snowflake channel_id, VoiceStateFlags flags) {
|
||||
if (auto iter = GetIteratorForRowFromIDOfType(user_id, RenderType::VoiceParticipant)) {
|
||||
(*iter)[m_columns.m_voice_flags] = flags;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void ChannelList::DeleteThreadRow(Snowflake id) {
|
||||
void ChannelListTree::DeleteThreadRow(Snowflake id) {
|
||||
auto iter = GetIteratorForRowFromID(id);
|
||||
if (iter)
|
||||
m_model->erase(iter);
|
||||
}
|
||||
|
||||
void ChannelList::OnChannelMute(Snowflake id) {
|
||||
void ChannelListTree::OnChannelMute(Snowflake id) {
|
||||
if (auto iter = GetIteratorForRowFromID(id))
|
||||
m_model->row_changed(m_model->get_path(iter), iter);
|
||||
}
|
||||
|
||||
void ChannelList::OnChannelUnmute(Snowflake id) {
|
||||
void ChannelListTree::OnChannelUnmute(Snowflake id) {
|
||||
if (auto iter = GetIteratorForRowFromID(id))
|
||||
m_model->row_changed(m_model->get_path(iter), iter);
|
||||
}
|
||||
|
||||
void ChannelList::OnGuildMute(Snowflake id) {
|
||||
void ChannelListTree::OnGuildMute(Snowflake id) {
|
||||
if (auto iter = GetIteratorForGuildFromID(id))
|
||||
m_model->row_changed(m_model->get_path(iter), iter);
|
||||
}
|
||||
|
||||
void ChannelList::OnGuildUnmute(Snowflake id) {
|
||||
void ChannelListTree::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, bool expand_to) {
|
||||
void ChannelListTree::SetActiveChannel(Snowflake id, bool expand_to) {
|
||||
while (Gtk::Main::events_pending()) Gtk::Main::iteration();
|
||||
|
||||
// mark channel as read when switching off
|
||||
if (m_active_channel.IsValid())
|
||||
Abaddon::Get().GetDiscordClient().MarkChannelAsRead(m_active_channel, [](...) {});
|
||||
@ -594,10 +692,14 @@ void ChannelList::SetActiveChannel(Snowflake id, bool expand_to) {
|
||||
|
||||
const auto channel_iter = GetIteratorForRowFromID(id);
|
||||
if (channel_iter) {
|
||||
if (expand_to) {
|
||||
m_view.expand_to_path(m_model->get_path(channel_iter));
|
||||
m_view.get_selection()->unselect_all();
|
||||
const auto view_iter = ConvertModelIterToView(channel_iter);
|
||||
if (view_iter) {
|
||||
if (expand_to) {
|
||||
m_view.expand_to_path(GetViewPathFromViewIter(view_iter));
|
||||
}
|
||||
m_view.get_selection()->select(view_iter);
|
||||
}
|
||||
m_view.get_selection()->select(channel_iter);
|
||||
} else {
|
||||
m_view.get_selection()->unselect_all();
|
||||
const auto channel = Abaddon::Get().GetDiscordClient().GetChannel(id);
|
||||
@ -605,11 +707,17 @@ void ChannelList::SetActiveChannel(Snowflake id, bool expand_to) {
|
||||
auto parent_iter = GetIteratorForRowFromID(*channel->ParentID);
|
||||
if (!parent_iter) return;
|
||||
m_temporary_thread_row = CreateThreadRow(parent_iter->children(), *channel);
|
||||
m_view.get_selection()->select(m_temporary_thread_row);
|
||||
const auto view_iter = ConvertModelIterToView(m_temporary_thread_row);
|
||||
if (view_iter) {
|
||||
m_view.get_selection()->select(view_iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelList::UseExpansionState(const ExpansionStateRoot &root) {
|
||||
void ChannelListTree::UseExpansionState(const ExpansionStateRoot &root) {
|
||||
m_updating_listing = true;
|
||||
m_filter_model->refilter();
|
||||
|
||||
auto recurse = [this](auto &self, const ExpansionStateRoot &root) -> void {
|
||||
for (const auto &[id, state] : root.Children) {
|
||||
Gtk::TreeModel::iterator row_iter;
|
||||
@ -620,10 +728,15 @@ void ChannelList::UseExpansionState(const ExpansionStateRoot &root) {
|
||||
}
|
||||
|
||||
if (row_iter) {
|
||||
if (state.IsExpanded)
|
||||
m_view.expand_row(m_model->get_path(row_iter), false);
|
||||
else
|
||||
m_view.collapse_row(m_model->get_path(row_iter));
|
||||
(*row_iter)[m_columns.m_expanded] = state.IsExpanded;
|
||||
auto view_iter = ConvertModelIterToView(row_iter);
|
||||
if (view_iter) {
|
||||
if (state.IsExpanded) {
|
||||
m_view.expand_row(GetViewPathFromViewIter(view_iter), false);
|
||||
} else {
|
||||
m_view.collapse_row(GetViewPathFromViewIter(view_iter));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self(self, state.Children);
|
||||
@ -632,32 +745,42 @@ void ChannelList::UseExpansionState(const ExpansionStateRoot &root) {
|
||||
|
||||
for (const auto &[id, state] : root.Children) {
|
||||
if (const auto iter = GetIteratorForTopLevelFromID(id)) {
|
||||
if (state.IsExpanded)
|
||||
m_view.expand_row(m_model->get_path(iter), false);
|
||||
else
|
||||
m_view.collapse_row(m_model->get_path(iter));
|
||||
(*iter)[m_columns.m_expanded] = state.IsExpanded;
|
||||
auto view_iter = ConvertModelIterToView(iter);
|
||||
if (view_iter) {
|
||||
if (state.IsExpanded) {
|
||||
m_view.expand_row(GetViewPathFromViewIter(view_iter), false);
|
||||
} else {
|
||||
m_view.collapse_row(GetViewPathFromViewIter(view_iter));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
recurse(recurse, state.Children);
|
||||
}
|
||||
|
||||
m_updating_listing = false;
|
||||
m_filter_model->refilter();
|
||||
|
||||
m_tmp_row_map.clear();
|
||||
m_tmp_guild_row_map.clear();
|
||||
}
|
||||
|
||||
ExpansionStateRoot ChannelList::GetExpansionState() const {
|
||||
ExpansionStateRoot ChannelListTree::GetExpansionState() const {
|
||||
ExpansionStateRoot r;
|
||||
|
||||
auto recurse = [this](auto &self, const Gtk::TreeRow &row) -> ExpansionState {
|
||||
ExpansionState r;
|
||||
|
||||
r.IsExpanded = row[m_columns.m_expanded];
|
||||
for (const auto &child : row.children())
|
||||
for (auto child : row.children()) {
|
||||
r.Children.Children[static_cast<Snowflake>(child[m_columns.m_id])] = self(self, child);
|
||||
}
|
||||
|
||||
return r;
|
||||
};
|
||||
|
||||
for (const auto &child : m_model->children()) {
|
||||
for (auto child : m_model->children()) {
|
||||
const auto id = static_cast<Snowflake>(child[m_columns.m_id]);
|
||||
if (static_cast<uint64_t>(id) == 0ULL) continue; // dont save DM header
|
||||
r.Children[id] = recurse(recurse, child);
|
||||
@ -666,7 +789,51 @@ ExpansionStateRoot ChannelList::GetExpansionState() const {
|
||||
return r;
|
||||
}
|
||||
|
||||
Gtk::TreeModel::iterator ChannelList::AddFolder(const UserSettingsGuildFoldersEntry &folder) {
|
||||
Gtk::TreePath ChannelListTree::ConvertModelPathToView(const Gtk::TreePath &path) {
|
||||
if (const auto filter_path = m_filter_model->convert_child_path_to_path(path)) {
|
||||
if (const auto sort_path = m_sort_model->convert_child_path_to_path(filter_path)) {
|
||||
return sort_path;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Gtk::TreeIter ChannelListTree::ConvertModelIterToView(const Gtk::TreeIter &iter) {
|
||||
if (const auto filter_iter = m_filter_model->convert_child_iter_to_iter(iter)) {
|
||||
if (const auto sort_iter = m_sort_model->convert_child_iter_to_iter(filter_iter)) {
|
||||
return sort_iter;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Gtk::TreePath ChannelListTree::ConvertViewPathToModel(const Gtk::TreePath &path) {
|
||||
if (const auto filter_path = m_sort_model->convert_path_to_child_path(path)) {
|
||||
if (const auto model_path = m_filter_model->convert_path_to_child_path(filter_path)) {
|
||||
return model_path;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Gtk::TreeIter ChannelListTree::ConvertViewIterToModel(const Gtk::TreeIter &iter) {
|
||||
if (const auto filter_iter = m_sort_model->convert_iter_to_child_iter(iter)) {
|
||||
if (const auto model_iter = m_filter_model->convert_iter_to_child_iter(filter_iter)) {
|
||||
return model_iter;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Gtk::TreePath ChannelListTree::GetViewPathFromViewIter(const Gtk::TreeIter &iter) {
|
||||
return m_sort_model->get_path(iter);
|
||||
}
|
||||
|
||||
Gtk::TreeModel::iterator ChannelListTree::AddFolder(const UserSettingsGuildFoldersEntry &folder) {
|
||||
if (!folder.ID.has_value()) {
|
||||
// just a guild
|
||||
if (!folder.GuildIDs.empty()) {
|
||||
@ -704,7 +871,7 @@ Gtk::TreeModel::iterator ChannelList::AddFolder(const UserSettingsGuildFoldersEn
|
||||
return {};
|
||||
}
|
||||
|
||||
Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild, const Gtk::TreeNodeChildren &root) {
|
||||
Gtk::TreeModel::iterator ChannelListTree::AddGuild(const GuildData &guild, const Gtk::TreeNodeChildren &root) {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
auto &img = Abaddon::Get().GetImageManager();
|
||||
|
||||
@ -764,8 +931,9 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild, const Gtk
|
||||
const auto it = threads.find(channel.ID);
|
||||
if (it == threads.end()) return;
|
||||
|
||||
for (const auto &thread : it->second)
|
||||
m_tmp_row_map[thread.ID] = CreateThreadRow(row.children(), thread);
|
||||
for (const auto &thread : it->second) {
|
||||
CreateThreadRow(row.children(), thread);
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
@ -834,7 +1002,7 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild, const Gtk
|
||||
return guild_row;
|
||||
}
|
||||
|
||||
Gtk::TreeModel::iterator ChannelList::UpdateCreateChannelCategory(const ChannelData &channel) {
|
||||
Gtk::TreeModel::iterator ChannelListTree::UpdateCreateChannelCategory(const ChannelData &channel) {
|
||||
const auto iter = GetIteratorForGuildFromID(*channel.GuildID);
|
||||
if (!iter) return {};
|
||||
|
||||
@ -848,7 +1016,7 @@ Gtk::TreeModel::iterator ChannelList::UpdateCreateChannelCategory(const ChannelD
|
||||
return cat_row;
|
||||
}
|
||||
|
||||
Gtk::TreeModel::iterator ChannelList::CreateThreadRow(const Gtk::TreeNodeChildren &children, const ChannelData &channel) {
|
||||
Gtk::TreeModel::iterator ChannelListTree::CreateThreadRow(const Gtk::TreeNodeChildren &children, const ChannelData &channel) {
|
||||
auto thread_iter = m_model->append(children);
|
||||
auto thread_row = *thread_iter;
|
||||
thread_row[m_columns.m_type] = RenderType::Thread;
|
||||
@ -861,7 +1029,7 @@ Gtk::TreeModel::iterator ChannelList::CreateThreadRow(const Gtk::TreeNodeChildre
|
||||
}
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
Gtk::TreeModel::iterator ChannelList::CreateVoiceParticipantRow(const UserData &user, const Gtk::TreeNodeChildren &parent) {
|
||||
Gtk::TreeModel::iterator ChannelListTree::CreateVoiceParticipantRow(const UserData &user, const Gtk::TreeNodeChildren &parent) {
|
||||
auto row = *m_model->append(parent);
|
||||
row[m_columns.m_type] = RenderType::VoiceParticipant;
|
||||
row[m_columns.m_id] = user.ID;
|
||||
@ -884,7 +1052,7 @@ Gtk::TreeModel::iterator ChannelList::CreateVoiceParticipantRow(const UserData &
|
||||
}
|
||||
#endif
|
||||
|
||||
void ChannelList::UpdateChannelCategory(const ChannelData &channel) {
|
||||
void ChannelListTree::UpdateChannelCategory(const ChannelData &channel) {
|
||||
auto iter = GetIteratorForRowFromID(channel.ID);
|
||||
if (!iter) return;
|
||||
|
||||
@ -893,7 +1061,7 @@ void ChannelList::UpdateChannelCategory(const ChannelData &channel) {
|
||||
}
|
||||
|
||||
// todo this all needs refactoring for shooore
|
||||
Gtk::TreeModel::iterator ChannelList::GetIteratorForTopLevelFromID(Snowflake id) {
|
||||
Gtk::TreeModel::iterator ChannelListTree::GetIteratorForTopLevelFromID(Snowflake id) {
|
||||
for (const auto &child : m_model->children()) {
|
||||
if ((child[m_columns.m_type] == RenderType::Guild || child[m_columns.m_type] == RenderType::Folder) && child[m_columns.m_id] == id) {
|
||||
return child;
|
||||
@ -908,7 +1076,7 @@ Gtk::TreeModel::iterator ChannelList::GetIteratorForTopLevelFromID(Snowflake id)
|
||||
return {};
|
||||
}
|
||||
|
||||
Gtk::TreeModel::iterator ChannelList::GetIteratorForGuildFromID(Snowflake id) {
|
||||
Gtk::TreeModel::iterator ChannelListTree::GetIteratorForGuildFromID(Snowflake id) {
|
||||
for (const auto &child : m_model->children()) {
|
||||
if (child[m_columns.m_type] == RenderType::Guild && child[m_columns.m_id] == id) {
|
||||
return child;
|
||||
@ -923,7 +1091,7 @@ Gtk::TreeModel::iterator ChannelList::GetIteratorForGuildFromID(Snowflake id) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Gtk::TreeModel::iterator ChannelList::GetIteratorForRowFromID(Snowflake id) {
|
||||
Gtk::TreeModel::iterator ChannelListTree::GetIteratorForRowFromID(Snowflake id) {
|
||||
std::queue<Gtk::TreeModel::iterator> queue;
|
||||
for (const auto &child : m_model->children())
|
||||
for (const auto &child2 : child.children())
|
||||
@ -940,7 +1108,7 @@ Gtk::TreeModel::iterator ChannelList::GetIteratorForRowFromID(Snowflake id) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Gtk::TreeModel::iterator ChannelList::GetIteratorForRowFromIDOfType(Snowflake id, RenderType type) {
|
||||
Gtk::TreeModel::iterator ChannelListTree::GetIteratorForRowFromIDOfType(Snowflake id, RenderType type) {
|
||||
std::queue<Gtk::TreeModel::iterator> queue;
|
||||
for (const auto &child : m_model->children())
|
||||
for (const auto &child2 : child.children())
|
||||
@ -957,20 +1125,22 @@ Gtk::TreeModel::iterator ChannelList::GetIteratorForRowFromIDOfType(Snowflake id
|
||||
return {};
|
||||
}
|
||||
|
||||
bool ChannelList::IsTextChannel(ChannelType type) {
|
||||
bool ChannelListTree::IsTextChannel(ChannelType type) {
|
||||
return type == ChannelType::GUILD_TEXT || type == ChannelType::GUILD_NEWS;
|
||||
}
|
||||
|
||||
// this should be unncessary but something is behaving strange so its just in case
|
||||
void ChannelList::OnRowCollapsed(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path) const {
|
||||
void ChannelListTree::OnRowCollapsed(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path) const {
|
||||
(*iter)[m_columns.m_expanded] = false;
|
||||
}
|
||||
|
||||
void ChannelList::OnRowExpanded(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path) {
|
||||
void ChannelListTree::OnRowExpanded(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path) {
|
||||
// restore previous expansion
|
||||
for (auto it = iter->children().begin(); it != iter->children().end(); it++) {
|
||||
if ((*it)[m_columns.m_expanded])
|
||||
m_view.expand_row(m_model->get_path(it), false);
|
||||
auto model_iter = ConvertViewIterToModel(iter);
|
||||
for (auto it = model_iter->children().begin(); it != model_iter->children().end(); it++) {
|
||||
if ((*it)[m_columns.m_expanded]) {
|
||||
m_view.expand_row(GetViewPathFromViewIter(ConvertModelIterToView(it)), false);
|
||||
}
|
||||
}
|
||||
|
||||
// try and restore selection if previous collapsed
|
||||
@ -978,19 +1148,21 @@ void ChannelList::OnRowExpanded(const Gtk::TreeModel::iterator &iter, const Gtk:
|
||||
selection->select(m_last_selected);
|
||||
}
|
||||
|
||||
(*iter)[m_columns.m_expanded] = true;
|
||||
(*model_iter)[m_columns.m_expanded] = true;
|
||||
}
|
||||
|
||||
bool ChannelList::SelectionFunc(const Glib::RefPtr<Gtk::TreeModel> &model, const Gtk::TreeModel::Path &path, bool is_currently_selected) {
|
||||
if (auto selection = m_view.get_selection())
|
||||
if (auto row = selection->get_selected())
|
||||
m_last_selected = m_model->get_path(row);
|
||||
bool ChannelListTree::SelectionFunc(const Glib::RefPtr<Gtk::TreeModel> &model, const Gtk::TreeModel::Path &path, bool is_currently_selected) {
|
||||
if (auto selection = m_view.get_selection()) {
|
||||
if (auto row = selection->get_selected()) {
|
||||
m_last_selected = GetViewPathFromViewIter(row);
|
||||
}
|
||||
}
|
||||
|
||||
auto type = (*m_model->get_iter(path))[m_columns.m_type];
|
||||
auto type = (*model->get_iter(path))[m_columns.m_type];
|
||||
return type == RenderType::TextChannel || type == RenderType::DM || type == RenderType::Thread;
|
||||
}
|
||||
|
||||
void ChannelList::AddPrivateChannels() {
|
||||
void ChannelListTree::AddPrivateChannels() {
|
||||
auto header_row = *m_model->append();
|
||||
header_row[m_columns.m_type] = RenderType::DMHeader;
|
||||
header_row[m_columns.m_sort] = -1;
|
||||
@ -1031,7 +1203,7 @@ void ChannelList::AddPrivateChannels() {
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelList::UpdateCreateDMChannel(const ChannelData &dm) {
|
||||
void ChannelListTree::UpdateCreateDMChannel(const ChannelData &dm) {
|
||||
auto header_row = m_model->get_iter(m_dm_header);
|
||||
auto &img = Abaddon::Get().GetImageManager();
|
||||
|
||||
@ -1046,7 +1218,7 @@ void ChannelList::UpdateCreateDMChannel(const ChannelData &dm) {
|
||||
SetDMChannelIcon(iter, dm);
|
||||
}
|
||||
|
||||
void ChannelList::SetDMChannelIcon(Gtk::TreeIter iter, const ChannelData &dm) {
|
||||
void ChannelListTree::SetDMChannelIcon(Gtk::TreeIter iter, const ChannelData &dm) {
|
||||
auto &img = Abaddon::Get().GetImageManager();
|
||||
|
||||
std::optional<UserData> top_recipient;
|
||||
@ -1103,7 +1275,7 @@ void ChannelList::SetDMChannelIcon(Gtk::TreeIter iter, const ChannelData &dm) {
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelList::RedrawUnreadIndicatorsForChannel(const ChannelData &channel) {
|
||||
void ChannelListTree::RedrawUnreadIndicatorsForChannel(const ChannelData &channel) {
|
||||
if (channel.GuildID.has_value()) {
|
||||
auto iter = GetIteratorForGuildFromID(*channel.GuildID);
|
||||
if (iter) m_model->row_changed(m_model->get_path(iter), iter);
|
||||
@ -1114,7 +1286,7 @@ void ChannelList::RedrawUnreadIndicatorsForChannel(const ChannelData &channel) {
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelList::OnMessageAck(const MessageAckData &data) {
|
||||
void ChannelListTree::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 = GetIteratorForRowFromID(data.ChannelID);
|
||||
@ -1125,7 +1297,7 @@ void ChannelList::OnMessageAck(const MessageAckData &data) {
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelList::OnMessageCreate(const Message &msg) {
|
||||
void ChannelListTree::OnMessageCreate(const Message &msg) {
|
||||
auto iter = GetIteratorForRowFromID(msg.ChannelID);
|
||||
if (iter) m_model->row_changed(m_model->get_path(iter), iter); // redraw
|
||||
const auto channel = Abaddon::Get().GetDiscordClient().GetChannel(msg.ChannelID);
|
||||
@ -1137,9 +1309,11 @@ void ChannelList::OnMessageCreate(const Message &msg) {
|
||||
RedrawUnreadIndicatorsForChannel(*channel);
|
||||
}
|
||||
|
||||
bool ChannelList::OnButtonPressEvent(GdkEventButton *ev) {
|
||||
bool ChannelListTree::OnButtonPressEvent(GdkEventButton *ev) {
|
||||
if (ev->button == GDK_BUTTON_SECONDARY && ev->type == GDK_BUTTON_PRESS) {
|
||||
if (m_view.get_path_at_pos(static_cast<int>(ev->x), static_cast<int>(ev->y), m_path_for_menu)) {
|
||||
m_path_for_menu = m_filter_model->convert_path_to_child_path(m_sort_model->convert_path_to_child_path(m_path_for_menu));
|
||||
if (!m_path_for_menu) return true;
|
||||
auto row = (*m_model->get_iter(m_path_for_menu));
|
||||
switch (static_cast<RenderType>(row[m_columns.m_type])) {
|
||||
case RenderType::Guild:
|
||||
@ -1184,7 +1358,7 @@ bool ChannelList::OnButtonPressEvent(GdkEventButton *ev) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void ChannelList::MoveRow(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::iterator &new_parent) {
|
||||
void ChannelListTree::MoveRow(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::iterator &new_parent) {
|
||||
// duplicate the row data under the new parent and then delete the old row
|
||||
auto row = *m_model->append(new_parent->children());
|
||||
// would be nice to be able to get all columns out at runtime so i dont need this
|
||||
@ -1212,7 +1386,7 @@ void ChannelList::MoveRow(const Gtk::TreeModel::iterator &iter, const Gtk::TreeM
|
||||
m_model->erase(iter);
|
||||
}
|
||||
|
||||
void ChannelList::OnGuildSubmenuPopup() {
|
||||
void ChannelListTree::OnGuildSubmenuPopup() {
|
||||
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]);
|
||||
@ -1227,7 +1401,7 @@ void ChannelList::OnGuildSubmenuPopup() {
|
||||
m_menu_guild_leave.set_sensitive(!(guild.has_value() && guild->OwnerID == self_id));
|
||||
}
|
||||
|
||||
void ChannelList::OnCategorySubmenuPopup() {
|
||||
void ChannelListTree::OnCategorySubmenuPopup() {
|
||||
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]);
|
||||
@ -1237,7 +1411,7 @@ void ChannelList::OnCategorySubmenuPopup() {
|
||||
m_menu_category_toggle_mute.set_label("Mute");
|
||||
}
|
||||
|
||||
void ChannelList::OnChannelSubmenuPopup() {
|
||||
void ChannelListTree::OnChannelSubmenuPopup() {
|
||||
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]);
|
||||
@ -1253,7 +1427,7 @@ void ChannelList::OnChannelSubmenuPopup() {
|
||||
}
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
void ChannelList::OnVoiceChannelSubmenuPopup() {
|
||||
void ChannelListTree::OnVoiceChannelSubmenuPopup() {
|
||||
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]);
|
||||
@ -1268,7 +1442,7 @@ void ChannelList::OnVoiceChannelSubmenuPopup() {
|
||||
}
|
||||
#endif
|
||||
|
||||
void ChannelList::OnDMSubmenuPopup() {
|
||||
void ChannelListTree::OnDMSubmenuPopup() {
|
||||
auto iter = m_model->get_iter(m_path_for_menu);
|
||||
if (!iter) return;
|
||||
const auto id = static_cast<Snowflake>((*iter)[m_columns.m_id]);
|
||||
@ -1289,7 +1463,7 @@ void ChannelList::OnDMSubmenuPopup() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void ChannelList::OnThreadSubmenuPopup() {
|
||||
void ChannelListTree::OnThreadSubmenuPopup() {
|
||||
m_menu_thread_archive.set_visible(false);
|
||||
m_menu_thread_unarchive.set_visible(false);
|
||||
|
||||
@ -1311,35 +1485,35 @@ void ChannelList::OnThreadSubmenuPopup() {
|
||||
m_menu_thread_unarchive.set_visible(channel->ThreadMetadata->IsArchived);
|
||||
}
|
||||
|
||||
ChannelList::type_signal_action_channel_item_select ChannelList::signal_action_channel_item_select() {
|
||||
ChannelListTree::type_signal_action_channel_item_select ChannelListTree::signal_action_channel_item_select() {
|
||||
return m_signal_action_channel_item_select;
|
||||
}
|
||||
|
||||
ChannelList::type_signal_action_guild_leave ChannelList::signal_action_guild_leave() {
|
||||
ChannelListTree::type_signal_action_guild_leave ChannelListTree::signal_action_guild_leave() {
|
||||
return m_signal_action_guild_leave;
|
||||
}
|
||||
|
||||
ChannelList::type_signal_action_guild_settings ChannelList::signal_action_guild_settings() {
|
||||
ChannelListTree::type_signal_action_guild_settings ChannelListTree::signal_action_guild_settings() {
|
||||
return m_signal_action_guild_settings;
|
||||
}
|
||||
|
||||
#ifdef WITH_LIBHANDY
|
||||
ChannelList::type_signal_action_open_new_tab ChannelList::signal_action_open_new_tab() {
|
||||
ChannelListTree::type_signal_action_open_new_tab ChannelListTree::signal_action_open_new_tab() {
|
||||
return m_signal_action_open_new_tab;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
ChannelList::type_signal_action_join_voice_channel ChannelList::signal_action_join_voice_channel() {
|
||||
ChannelListTree::type_signal_action_join_voice_channel ChannelListTree::signal_action_join_voice_channel() {
|
||||
return m_signal_action_join_voice_channel;
|
||||
}
|
||||
|
||||
ChannelList::type_signal_action_disconnect_voice ChannelList::signal_action_disconnect_voice() {
|
||||
ChannelListTree::type_signal_action_disconnect_voice ChannelListTree::signal_action_disconnect_voice() {
|
||||
return m_signal_action_disconnect_voice;
|
||||
}
|
||||
#endif
|
||||
|
||||
ChannelList::ModelColumns::ModelColumns() {
|
||||
ChannelListTree::ModelColumns::ModelColumns() {
|
||||
add(m_type);
|
||||
add(m_id);
|
||||
add(m_name);
|
@ -8,20 +8,21 @@
|
||||
#include <gtkmm/scrolledwindow.h>
|
||||
#include <gtkmm/treemodel.h>
|
||||
#include <gtkmm/treestore.h>
|
||||
#include <gtkmm/treemodelfilter.h>
|
||||
#include <gtkmm/treeview.h>
|
||||
#include <sigc++/sigc++.h>
|
||||
#include "discord/discord.hpp"
|
||||
#include "state.hpp"
|
||||
#include "channelscellrenderer.hpp"
|
||||
#include "cellrendererchannels.hpp"
|
||||
|
||||
constexpr static int GuildIconSize = 24;
|
||||
constexpr static int DMIconSize = 20;
|
||||
constexpr static int VoiceParticipantIconSize = 18;
|
||||
constexpr static int OrphanChannelSortOffset = -100; // forces orphan channels to the top of the list
|
||||
|
||||
class ChannelList : public Gtk::ScrolledWindow {
|
||||
class ChannelListTree : public Gtk::ScrolledWindow {
|
||||
public:
|
||||
ChannelList();
|
||||
ChannelListTree();
|
||||
|
||||
void UpdateListing();
|
||||
void SetActiveChannel(Snowflake id, bool expand_to);
|
||||
@ -32,9 +33,17 @@ public:
|
||||
|
||||
void UsePanedHack(Gtk::Paned &paned);
|
||||
|
||||
void SetClassic(bool value);
|
||||
void SetSelectedGuild(Snowflake guild_id);
|
||||
void SetSelectedDMs();
|
||||
|
||||
protected:
|
||||
int SortFunc(const Gtk::TreeModel::iterator &a, const Gtk::TreeModel::iterator &b);
|
||||
|
||||
void OnPanedPositionChanged();
|
||||
|
||||
void UpdateListingClassic();
|
||||
|
||||
void UpdateNewGuild(const GuildData &guild);
|
||||
void UpdateRemoveGuild(Snowflake id);
|
||||
void UpdateRemoveChannel(Snowflake id);
|
||||
@ -82,7 +91,14 @@ protected:
|
||||
|
||||
ModelColumns m_columns;
|
||||
Glib::RefPtr<Gtk::TreeStore> m_model;
|
||||
Glib::RefPtr<Gtk::TreeModelFilter> m_filter_model;
|
||||
Glib::RefPtr<Gtk::TreeModelSort> m_sort_model;
|
||||
|
||||
Gtk::TreePath ConvertModelPathToView(const Gtk::TreePath &path);
|
||||
Gtk::TreeIter ConvertModelIterToView(const Gtk::TreeIter &iter);
|
||||
Gtk::TreePath ConvertViewPathToModel(const Gtk::TreePath &path);
|
||||
Gtk::TreeIter ConvertViewIterToModel(const Gtk::TreeIter &iter);
|
||||
Gtk::TreePath GetViewPathFromViewIter(const Gtk::TreeIter &iter);
|
||||
Gtk::TreeModel::iterator AddFolder(const UserSettingsGuildFoldersEntry &folder);
|
||||
Gtk::TreeModel::iterator AddGuild(const GuildData &guild, const Gtk::TreeNodeChildren &root);
|
||||
Gtk::TreeModel::iterator UpdateCreateChannelCategory(const ChannelData &channel);
|
||||
@ -116,7 +132,7 @@ protected:
|
||||
void UpdateCreateDMChannel(const ChannelData &channel);
|
||||
void SetDMChannelIcon(Gtk::TreeIter iter, const ChannelData &dm);
|
||||
|
||||
void RedrawUnreadIndicatorsForChannel(const ChannelData& channel);
|
||||
void RedrawUnreadIndicatorsForChannel(const ChannelData &channel);
|
||||
void OnMessageAck(const MessageAckData &data);
|
||||
void OnMessageCreate(const Message &msg);
|
||||
|
||||
@ -184,10 +200,13 @@ protected:
|
||||
|
||||
bool m_updating_listing = false;
|
||||
|
||||
bool m_classic = false;
|
||||
Snowflake m_classic_selected_guild;
|
||||
bool m_classic_selected_dms = false;
|
||||
|
||||
Snowflake m_active_channel;
|
||||
|
||||
// (GetIteratorForChannelFromID is rather slow)
|
||||
// only temporary since i dont want to worry about maintaining this map
|
||||
// hashtable for the billion lookups done in UseExpansionState
|
||||
std::unordered_map<Snowflake, Gtk::TreeModel::iterator> m_tmp_row_map;
|
||||
std::unordered_map<Snowflake, Gtk::TreeModel::iterator> m_tmp_guild_row_map;
|
||||
|
177
src/components/channellist/classic/guildlist.cpp
Normal file
177
src/components/channellist/classic/guildlist.cpp
Normal file
@ -0,0 +1,177 @@
|
||||
#include "guildlist.hpp"
|
||||
|
||||
#include "abaddon.hpp"
|
||||
#include "guildlistfolderitem.hpp"
|
||||
|
||||
class GuildListDMsButton : public Gtk::EventBox {
|
||||
public:
|
||||
GuildListDMsButton() {
|
||||
set_size_request(48, 48);
|
||||
|
||||
m_img.property_icon_name() = "user-available-symbolic"; // meh
|
||||
m_img.property_icon_size() = Gtk::ICON_SIZE_DND;
|
||||
add(m_img);
|
||||
show_all_children();
|
||||
}
|
||||
|
||||
private:
|
||||
Gtk::Image m_img;
|
||||
};
|
||||
|
||||
GuildList::GuildList()
|
||||
: 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) {
|
||||
get_style_context()->add_class("classic-guild-list");
|
||||
set_selection_mode(Gtk::SELECTION_NONE);
|
||||
show_all_children();
|
||||
|
||||
m_menu_guild_copy_id.signal_activate().connect([this] {
|
||||
Gtk::Clipboard::get()->set_text(std::to_string(m_menu_guild_target));
|
||||
});
|
||||
m_menu_guild_settings.signal_activate().connect([this] {
|
||||
m_signal_action_guild_settings.emit(m_menu_guild_target);
|
||||
});
|
||||
m_menu_guild_leave.signal_activate().connect([this] {
|
||||
m_signal_action_guild_leave.emit(m_menu_guild_target);
|
||||
});
|
||||
m_menu_guild_mark_as_read.signal_activate().connect([this] {
|
||||
Abaddon::Get().GetDiscordClient().MarkGuildAsRead(m_menu_guild_target, [](...) {});
|
||||
});
|
||||
m_menu_guild_toggle_mute.signal_activate().connect([this] {
|
||||
const auto id = m_menu_guild_target;
|
||||
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();
|
||||
}
|
||||
|
||||
void GuildList::UpdateListing() {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
|
||||
Clear();
|
||||
|
||||
auto *dms = Gtk::make_managed<GuildListDMsButton>();
|
||||
dms->show();
|
||||
dms->signal_button_press_event().connect([this](GdkEventButton *ev) -> bool {
|
||||
if (ev->type == GDK_BUTTON_PRESS && ev->button == GDK_BUTTON_PRIMARY) {
|
||||
m_signal_dms_selected.emit();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
add(*dms);
|
||||
|
||||
// does this function still even work ??lol
|
||||
const auto folders = discord.GetUserSettings().GuildFolders;
|
||||
const auto guild_ids = discord.GetUserSortedGuilds();
|
||||
|
||||
// same logic from ChannelListTree
|
||||
|
||||
std::set<Snowflake> foldered_guilds;
|
||||
for (const auto &group : folders) {
|
||||
foldered_guilds.insert(group.GuildIDs.begin(), group.GuildIDs.end());
|
||||
}
|
||||
|
||||
for (auto iter = guild_ids.crbegin(); iter != guild_ids.crend(); iter++) {
|
||||
if (foldered_guilds.find(*iter) == foldered_guilds.end()) {
|
||||
AddGuild(*iter);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &group : folders) {
|
||||
AddFolder(group);
|
||||
}
|
||||
}
|
||||
|
||||
void GuildList::AddGuild(Snowflake id) {
|
||||
if (auto item = CreateGuildWidget(id)) {
|
||||
item->show();
|
||||
add(*item);
|
||||
}
|
||||
}
|
||||
|
||||
GuildListGuildItem *GuildList::CreateGuildWidget(Snowflake id) {
|
||||
const auto guild = Abaddon::Get().GetDiscordClient().GetGuild(id);
|
||||
if (!guild.has_value()) return nullptr;
|
||||
|
||||
auto *item = Gtk::make_managed<GuildListGuildItem>(*guild);
|
||||
item->signal_button_press_event().connect([this, id](GdkEventButton *event) -> bool {
|
||||
if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_PRIMARY) {
|
||||
m_signal_guild_selected.emit(id);
|
||||
} else if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_SECONDARY) {
|
||||
m_menu_guild_target = id;
|
||||
OnGuildSubmenuPopup();
|
||||
m_menu_guild.popup_at_pointer(reinterpret_cast<GdkEvent *>(event));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
void GuildList::AddFolder(const UserSettingsGuildFoldersEntry &folder) {
|
||||
// groups with no ID arent actually folders
|
||||
if (!folder.ID.has_value()) {
|
||||
if (!folder.GuildIDs.empty()) {
|
||||
AddGuild(folder.GuildIDs[0]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto *folder_widget = Gtk::make_managed<GuildListFolderItem>(folder);
|
||||
for (const auto guild_id : folder.GuildIDs) {
|
||||
if (auto *guild_widget = CreateGuildWidget(guild_id)) {
|
||||
guild_widget->show();
|
||||
folder_widget->AddGuildWidget(guild_widget);
|
||||
}
|
||||
}
|
||||
|
||||
folder_widget->show();
|
||||
add(*folder_widget);
|
||||
}
|
||||
|
||||
void GuildList::Clear() {
|
||||
const auto children = get_children();
|
||||
for (auto *child : children) {
|
||||
delete child;
|
||||
}
|
||||
}
|
||||
|
||||
void GuildList::OnGuildSubmenuPopup() {
|
||||
const auto id = m_menu_guild_target;
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
if (discord.IsGuildMuted(id)) {
|
||||
m_menu_guild_toggle_mute.set_label("Unmute");
|
||||
} else {
|
||||
m_menu_guild_toggle_mute.set_label("Mute");
|
||||
}
|
||||
|
||||
const auto guild = discord.GetGuild(id);
|
||||
const auto self_id = discord.GetUserData().ID;
|
||||
m_menu_guild_leave.set_sensitive(!(guild.has_value() && guild->OwnerID == self_id));
|
||||
}
|
||||
|
||||
GuildList::type_signal_guild_selected GuildList::signal_guild_selected() {
|
||||
return m_signal_guild_selected;
|
||||
}
|
||||
|
||||
GuildList::type_signal_dms_selected GuildList::signal_dms_selected() {
|
||||
return m_signal_dms_selected;
|
||||
}
|
||||
|
||||
GuildList::type_signal_action_guild_leave GuildList::signal_action_guild_leave() {
|
||||
return m_signal_action_guild_leave;
|
||||
}
|
||||
|
||||
GuildList::type_signal_action_guild_settings GuildList::signal_action_guild_settings() {
|
||||
return m_signal_action_guild_settings;
|
||||
}
|
48
src/components/channellist/classic/guildlist.hpp
Normal file
48
src/components/channellist/classic/guildlist.hpp
Normal file
@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
#include <gtkmm/listbox.h>
|
||||
#include "discord/snowflake.hpp"
|
||||
#include "discord/usersettings.hpp"
|
||||
|
||||
class GuildListGuildItem;
|
||||
|
||||
class GuildList : public Gtk::ListBox {
|
||||
public:
|
||||
GuildList();
|
||||
|
||||
void UpdateListing();
|
||||
|
||||
private:
|
||||
void AddGuild(Snowflake id);
|
||||
void AddFolder(const UserSettingsGuildFoldersEntry &folder);
|
||||
void Clear();
|
||||
|
||||
GuildListGuildItem *CreateGuildWidget(Snowflake id);
|
||||
|
||||
// todo code duplication not good no sir
|
||||
Gtk::Menu m_menu_guild;
|
||||
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;
|
||||
Snowflake m_menu_guild_target;
|
||||
|
||||
void OnGuildSubmenuPopup();
|
||||
|
||||
public:
|
||||
using type_signal_guild_selected = sigc::signal<void, Snowflake>;
|
||||
using type_signal_dms_selected = sigc::signal<void>;
|
||||
using type_signal_action_guild_leave = sigc::signal<void, Snowflake>;
|
||||
using type_signal_action_guild_settings = sigc::signal<void, Snowflake>;
|
||||
|
||||
type_signal_guild_selected signal_guild_selected();
|
||||
type_signal_dms_selected signal_dms_selected();
|
||||
type_signal_action_guild_leave signal_action_guild_leave();
|
||||
type_signal_action_guild_settings signal_action_guild_settings();
|
||||
|
||||
private:
|
||||
type_signal_guild_selected m_signal_guild_selected;
|
||||
type_signal_dms_selected m_signal_dms_selected;
|
||||
type_signal_action_guild_leave m_signal_action_guild_leave;
|
||||
type_signal_action_guild_settings m_signal_action_guild_settings;
|
||||
};
|
129
src/components/channellist/classic/guildlistfolderitem.cpp
Normal file
129
src/components/channellist/classic/guildlistfolderitem.cpp
Normal file
@ -0,0 +1,129 @@
|
||||
#include "guildlistfolderitem.hpp"
|
||||
|
||||
#include "abaddon.hpp"
|
||||
#include "guildlistguilditem.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
// doing my best to copy discord here
|
||||
|
||||
const int FolderGridButtonSize = 48;
|
||||
const int FolderGridImageSize = 24;
|
||||
|
||||
GuildListFolderButton::GuildListFolderButton() {
|
||||
set_size_request(FolderGridButtonSize, FolderGridButtonSize);
|
||||
}
|
||||
|
||||
void GuildListFolderButton::SetGuilds(const std::vector<Snowflake> &guild_ids) {
|
||||
for (int y = 0; y < 2; y++) {
|
||||
for (int x = 0; x < 2; x++) {
|
||||
const size_t i = y * 2 + x;
|
||||
auto &widget = m_images[x][y];
|
||||
widget.property_pixbuf() = Abaddon::Get().GetImageManager().GetPlaceholder(FolderGridImageSize);
|
||||
attach(widget, x, y, 1, 1);
|
||||
|
||||
if (i < guild_ids.size()) {
|
||||
widget.show();
|
||||
|
||||
if (const auto guild = Abaddon::Get().GetDiscordClient().GetGuild(guild_ids[i]); guild.has_value() && guild->HasIcon()) {
|
||||
const auto cb = [&widget](const Glib::RefPtr<Gdk::Pixbuf> &pb) {
|
||||
widget.property_pixbuf() = pb->scale_simple(FolderGridImageSize, FolderGridImageSize, Gdk::INTERP_BILINEAR);
|
||||
};
|
||||
Abaddon::Get().GetImageManager().LoadFromURL(guild->GetIconURL("png", "32"), sigc::track_obj(cb, *this));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GuildListFolderItem::GuildListFolderItem(const UserSettingsGuildFoldersEntry &folder) {
|
||||
m_guild_ids = folder.GuildIDs;
|
||||
|
||||
get_style_context()->add_class("classic-guild-list-folder");
|
||||
|
||||
if (folder.Name.has_value()) {
|
||||
set_tooltip_text(*folder.Name);
|
||||
}
|
||||
|
||||
m_revealer.add(m_box);
|
||||
m_revealer.set_reveal_child(false);
|
||||
|
||||
m_image.property_pixbuf() = Abaddon::Get().GetImageManager().GetPlaceholder(48);
|
||||
|
||||
m_ev.signal_button_press_event().connect([this](GdkEventButton *event) -> bool {
|
||||
if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_PRIMARY) {
|
||||
m_revealer.set_reveal_child(!m_revealer.get_reveal_child());
|
||||
if (!Abaddon::Get().GetSettings().FolderIconOnly) {
|
||||
if (m_revealer.get_reveal_child()) {
|
||||
m_stack.set_visible_child("icon", Gtk::STACK_TRANSITION_TYPE_SLIDE_DOWN);
|
||||
} else {
|
||||
m_stack.set_visible_child("grid", Gtk::STACK_TRANSITION_TYPE_SLIDE_UP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
m_grid.SetGuilds(folder.GuildIDs);
|
||||
m_grid.show();
|
||||
|
||||
m_icon.property_icon_name() = "folder-symbolic";
|
||||
m_icon.property_icon_size() = Gtk::ICON_SIZE_DND;
|
||||
if (folder.Color.has_value()) {
|
||||
m_icon.override_color(IntToRGBA(*folder.Color));
|
||||
}
|
||||
m_icon.show();
|
||||
|
||||
m_stack.add(m_grid, "grid");
|
||||
m_stack.add(m_icon, "icon");
|
||||
m_stack.set_visible_child(Abaddon::Get().GetSettings().FolderIconOnly ? "icon" : "grid");
|
||||
m_stack.show();
|
||||
|
||||
m_ev.add(m_stack);
|
||||
add(m_ev);
|
||||
add(m_revealer);
|
||||
|
||||
m_ev.show();
|
||||
m_revealer.show();
|
||||
m_box.show();
|
||||
m_image.show();
|
||||
show();
|
||||
|
||||
Abaddon::Get().GetDiscordClient().signal_message_create().connect(sigc::mem_fun(*this, &GuildListFolderItem::OnMessageCreate));
|
||||
Abaddon::Get().GetDiscordClient().signal_message_ack().connect(sigc::mem_fun(*this, &GuildListFolderItem::OnMessageAck));
|
||||
|
||||
CheckUnreadStatus();
|
||||
}
|
||||
|
||||
void GuildListFolderItem::AddGuildWidget(GuildListGuildItem *widget) {
|
||||
m_box.add(*widget);
|
||||
}
|
||||
|
||||
void GuildListFolderItem::OnMessageCreate(const Message &msg) {
|
||||
if (msg.GuildID.has_value() && std::find(m_guild_ids.begin(), m_guild_ids.end(), *msg.GuildID) != m_guild_ids.end()) CheckUnreadStatus();
|
||||
}
|
||||
|
||||
void GuildListFolderItem::OnMessageAck(const MessageAckData &data) {
|
||||
CheckUnreadStatus();
|
||||
}
|
||||
|
||||
void GuildListFolderItem::CheckUnreadStatus() {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
if (!Abaddon::Get().GetSettings().Unreads) return;
|
||||
|
||||
bool has_any_unreads = false;
|
||||
|
||||
for (auto guild_id : m_guild_ids) {
|
||||
int mentions;
|
||||
if (!discord.IsGuildMuted(guild_id) && discord.GetUnreadStateForGuild(guild_id, mentions)) {
|
||||
has_any_unreads = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_any_unreads) {
|
||||
get_style_context()->add_class("has-unread");
|
||||
} else {
|
||||
get_style_context()->remove_class("has-unread");
|
||||
}
|
||||
}
|
44
src/components/channellist/classic/guildlistfolderitem.hpp
Normal file
44
src/components/channellist/classic/guildlistfolderitem.hpp
Normal file
@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
#include <gtkmm/box.h>
|
||||
#include <gtkmm/eventbox.h>
|
||||
#include <gtkmm/grid.h>
|
||||
#include <gtkmm/image.h>
|
||||
#include <gtkmm/revealer.h>
|
||||
#include <gtkmm/stack.h>
|
||||
|
||||
#include "guildlistguilditem.hpp"
|
||||
#include "discord/usersettings.hpp"
|
||||
|
||||
class GuildListGuildItem;
|
||||
|
||||
class GuildListFolderButton : public Gtk::Grid {
|
||||
public:
|
||||
GuildListFolderButton();
|
||||
void SetGuilds(const std::vector<Snowflake> &guild_ids);
|
||||
|
||||
private:
|
||||
Gtk::Image m_images[2][2];
|
||||
};
|
||||
|
||||
class GuildListFolderItem : public Gtk::VBox {
|
||||
public:
|
||||
GuildListFolderItem(const UserSettingsGuildFoldersEntry &folder);
|
||||
|
||||
void AddGuildWidget(GuildListGuildItem *widget);
|
||||
|
||||
private:
|
||||
void OnMessageCreate(const Message &msg);
|
||||
void OnMessageAck(const MessageAckData &data);
|
||||
void CheckUnreadStatus();
|
||||
|
||||
std::vector<Snowflake> m_guild_ids;
|
||||
|
||||
Gtk::Stack m_stack;
|
||||
GuildListFolderButton m_grid;
|
||||
Gtk::Image m_icon;
|
||||
|
||||
Gtk::EventBox m_ev;
|
||||
Gtk::Image m_image;
|
||||
Gtk::Revealer m_revealer;
|
||||
Gtk::VBox m_box;
|
||||
};
|
52
src/components/channellist/classic/guildlistguilditem.cpp
Normal file
52
src/components/channellist/classic/guildlistguilditem.cpp
Normal file
@ -0,0 +1,52 @@
|
||||
#include "guildlistguilditem.hpp"
|
||||
|
||||
#include "abaddon.hpp"
|
||||
|
||||
GuildListGuildItem::GuildListGuildItem(const GuildData &guild)
|
||||
: ID(guild.ID) {
|
||||
get_style_context()->add_class("classic-guild-list-guild");
|
||||
|
||||
m_image.property_pixbuf() = Abaddon::Get().GetImageManager().GetPlaceholder(48);
|
||||
|
||||
add(m_box);
|
||||
m_box.pack_start(m_image);
|
||||
show_all_children();
|
||||
|
||||
set_tooltip_text(guild.Name);
|
||||
|
||||
UpdateIcon();
|
||||
|
||||
Abaddon::Get().GetDiscordClient().signal_message_create().connect(sigc::mem_fun(*this, &GuildListGuildItem::OnMessageCreate));
|
||||
Abaddon::Get().GetDiscordClient().signal_message_ack().connect(sigc::mem_fun(*this, &GuildListGuildItem::OnMessageAck));
|
||||
|
||||
CheckUnreadStatus();
|
||||
}
|
||||
|
||||
void GuildListGuildItem::UpdateIcon() {
|
||||
const auto guild = Abaddon::Get().GetDiscordClient().GetGuild(ID);
|
||||
if (!guild.has_value() || !guild->HasIcon()) return;
|
||||
Abaddon::Get().GetImageManager().LoadFromURL(guild->GetIconURL("png", "64"), sigc::mem_fun(*this, &GuildListGuildItem::OnIconFetched));
|
||||
}
|
||||
|
||||
void GuildListGuildItem::OnIconFetched(const Glib::RefPtr<Gdk::Pixbuf> &pb) {
|
||||
m_image.property_pixbuf() = pb->scale_simple(48, 48, Gdk::INTERP_BILINEAR);
|
||||
}
|
||||
|
||||
void GuildListGuildItem::OnMessageCreate(const Message &msg) {
|
||||
if (msg.GuildID.has_value() && *msg.GuildID == ID) CheckUnreadStatus();
|
||||
}
|
||||
|
||||
void GuildListGuildItem::OnMessageAck(const MessageAckData &data) {
|
||||
CheckUnreadStatus();
|
||||
}
|
||||
|
||||
void GuildListGuildItem::CheckUnreadStatus() {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
if (!Abaddon::Get().GetSettings().Unreads) return;
|
||||
int mentions;
|
||||
if (!discord.IsGuildMuted(ID) && discord.GetUnreadStateForGuild(ID, mentions)) {
|
||||
get_style_context()->add_class("has-unread");
|
||||
} else {
|
||||
get_style_context()->remove_class("has-unread");
|
||||
}
|
||||
}
|
22
src/components/channellist/classic/guildlistguilditem.hpp
Normal file
22
src/components/channellist/classic/guildlistguilditem.hpp
Normal file
@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include <gtkmm/box.h>
|
||||
#include <gtkmm/eventbox.h>
|
||||
#include <gtkmm/image.h>
|
||||
#include "discord/guild.hpp"
|
||||
|
||||
class GuildListGuildItem : public Gtk::EventBox {
|
||||
public:
|
||||
GuildListGuildItem(const GuildData &guild);
|
||||
|
||||
Snowflake ID;
|
||||
|
||||
private:
|
||||
void UpdateIcon();
|
||||
void OnIconFetched(const Glib::RefPtr<Gdk::Pixbuf> &pb);
|
||||
void OnMessageCreate(const Message &msg);
|
||||
void OnMessageAck(const MessageAckData &data);
|
||||
void CheckUnreadStatus();
|
||||
|
||||
Gtk::Box m_box;
|
||||
Gtk::Image m_image;
|
||||
};
|
@ -99,8 +99,11 @@ void SettingsManager::DefineSettings() {
|
||||
AddSetting("gui", "hide_to_try", false, &Settings::HideToTray);
|
||||
AddSetting("gui", "show_deleted_indicator", true, &Settings::ShowDeletedIndicator);
|
||||
AddSetting("gui", "font_scale", -1.0, &Settings::FontScale);
|
||||
AddSetting("gui", "folder_icon_only", false, &Settings::FolderIconOnly);
|
||||
AddSetting("gui", "classic_change_guild_on_open", true, &Settings::ClassicChangeGuildOnOpen);
|
||||
AddSetting("gui", "image_embed_clamp_width", 400, &Settings::ImageEmbedClampWidth);
|
||||
AddSetting("gui", "image_embed_clamp_height", 300, &Settings::ImageEmbedClampHeight);
|
||||
AddSetting("gui", "classic_channels", false, &Settings::ClassicChannels);
|
||||
|
||||
AddSetting("http", "concurrent", 20, &Settings::CacheHTTPConcurrency);
|
||||
AddSetting("http", "user_agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36"s, &Settings::UserAgent);
|
||||
|
@ -28,8 +28,11 @@ public:
|
||||
bool HideToTray;
|
||||
bool ShowDeletedIndicator;
|
||||
double FontScale;
|
||||
bool FolderIconOnly;
|
||||
bool ClassicChangeGuildOnOpen;
|
||||
int ImageEmbedClampWidth;
|
||||
int ImageEmbedClampHeight;
|
||||
bool ClassicChannels;
|
||||
|
||||
// [http]
|
||||
int CacheHTTPConcurrency;
|
||||
|
@ -36,6 +36,7 @@ MainWindow::MainWindow()
|
||||
});
|
||||
#endif
|
||||
|
||||
m_channel_list.SetClassic(Abaddon::Get().GetSettings().ClassicChannels);
|
||||
m_channel_list.set_vexpand(true);
|
||||
m_channel_list.set_size_request(-1, -1);
|
||||
m_channel_list.show();
|
||||
@ -395,7 +396,7 @@ void MainWindow::SetupMenu() {
|
||||
});
|
||||
|
||||
m_menu_view_channels.signal_activate().connect([this]() {
|
||||
m_channel_list.set_visible(m_menu_view_channels.get_active());
|
||||
m_left_pane.set_visible(m_menu_view_channels.get_active());
|
||||
});
|
||||
|
||||
m_menu_view_members.signal_activate().connect([this]() {
|
||||
|
@ -1,5 +1,5 @@
|
||||
#pragma once
|
||||
#include "components/channels.hpp"
|
||||
#include "components/channellist/channellist.hpp"
|
||||
#include "components/chatwindow.hpp"
|
||||
#include "components/memberlist.hpp"
|
||||
#include "components/friendslist.hpp"
|
||||
|
Loading…
Reference in New Issue
Block a user