summaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/channels.cpp33
-rw-r--r--src/components/channels.hpp27
-rw-r--r--src/components/channeltabswitcherhandy.cpp202
-rw-r--r--src/components/channeltabswitcherhandy.hpp66
-rw-r--r--src/components/chatwindow.cpp46
-rw-r--r--src/components/chatwindow.hpp33
6 files changed, 390 insertions, 17 deletions
diff --git a/src/components/channels.cpp b/src/components/channels.cpp
index 4a6b1bc..2b83eb0 100644
--- a/src/components/channels.cpp
+++ b/src/components/channels.cpp
@@ -17,6 +17,10 @@ ChannelList::ChannelList()
, 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)
+#ifdef WITH_LIBHANDY
+ , m_menu_channel_open_tab("Open in New _Tab", true)
+ , m_menu_dm_open_tab("Open in New _Tab", true)
+#endif
, 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)
@@ -143,6 +147,15 @@ ChannelList::ChannelList()
else
discord.MuteChannel(id, NOOP_CALLBACK);
});
+
+#ifdef WITH_LIBHANDY
+ m_menu_channel_open_tab.signal_activate().connect([this] {
+ const auto id = static_cast<Snowflake>((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]);
+ m_signal_action_open_new_tab.emit(id);
+ });
+ m_menu_channel.append(m_menu_channel_open_tab);
+#endif
+
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);
@@ -170,6 +183,13 @@ ChannelList::ChannelList()
else
discord.MuteChannel(id, NOOP_CALLBACK);
});
+#ifdef WITH_LIBHANDY
+ m_menu_dm_open_tab.signal_activate().connect([this] {
+ const auto id = static_cast<Snowflake>((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]);
+ m_signal_action_open_new_tab.emit(id);
+ });
+ m_menu_dm.append(m_menu_dm_open_tab);
+#endif
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);
@@ -442,7 +462,7 @@ void ChannelList::OnGuildUnmute(Snowflake id) {
// 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) {
+void ChannelList::SetActiveChannel(Snowflake id, bool expand_to) {
// mark channel as read when switching off
if (m_active_channel.IsValid())
Abaddon::Get().GetDiscordClient().MarkChannelAsRead(m_active_channel, [](...) {});
@@ -459,11 +479,12 @@ void ChannelList::SetActiveChannel(Snowflake id) {
const auto channel_iter = GetIteratorForChannelFromID(id);
if (channel_iter) {
- m_view.expand_to_path(m_model->get_path(channel_iter));
+ if (expand_to) {
+ m_view.expand_to_path(m_model->get_path(channel_iter));
+ }
m_view.get_selection()->select(channel_iter);
} else {
m_view.get_selection()->unselect_all();
- // SetActiveChannel should probably just take the channel object
const auto channel = Abaddon::Get().GetDiscordClient().GetChannel(id);
if (!channel.has_value() || !channel->IsThread()) return;
auto parent_iter = GetIteratorForChannelFromID(*channel->ParentID);
@@ -960,6 +981,12 @@ ChannelList::type_signal_action_guild_settings ChannelList::signal_action_guild_
return m_signal_action_guild_settings;
}
+#ifdef WITH_LIBHANDY
+ChannelList::type_signal_action_open_new_tab ChannelList::signal_action_open_new_tab() {
+ return m_signal_action_open_new_tab;
+}
+#endif
+
ChannelList::ModelColumns::ModelColumns() {
add(m_type);
add(m_id);
diff --git a/src/components/channels.hpp b/src/components/channels.hpp
index 044d0b5..53a68c9 100644
--- a/src/components/channels.hpp
+++ b/src/components/channels.hpp
@@ -19,7 +19,7 @@ public:
ChannelList();
void UpdateListing();
- void SetActiveChannel(Snowflake id);
+ void SetActiveChannel(Snowflake id, bool expand_to);
// channel list should be populated when this is called
void UseExpansionState(const ExpansionStateRoot &state);
@@ -121,11 +121,19 @@ protected:
Gtk::MenuItem m_menu_channel_mark_as_read;
Gtk::MenuItem m_menu_channel_toggle_mute;
+#ifdef WITH_LIBHANDY
+ Gtk::MenuItem m_menu_channel_open_tab;
+#endif
+
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;
+#ifdef WITH_LIBHANDY
+ Gtk::MenuItem m_menu_dm_open_tab;
+#endif
+
Gtk::Menu m_menu_thread;
Gtk::MenuItem m_menu_thread_copy_id;
Gtk::MenuItem m_menu_thread_leave;
@@ -149,16 +157,25 @@ protected:
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;
- typedef sigc::signal<void, Snowflake> type_signal_action_guild_settings;
+ 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
type_signal_action_channel_item_select signal_action_channel_item_select();
type_signal_action_guild_leave signal_action_guild_leave();
type_signal_action_guild_settings signal_action_guild_settings();
-protected:
+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
};
diff --git a/src/components/channeltabswitcherhandy.cpp b/src/components/channeltabswitcherhandy.cpp
new file mode 100644
index 0000000..f7b0226
--- /dev/null
+++ b/src/components/channeltabswitcherhandy.cpp
@@ -0,0 +1,202 @@
+#ifdef WITH_LIBHANDY
+
+ #include "channeltabswitcherhandy.hpp"
+ #include "abaddon.hpp"
+
+void selected_page_notify_cb(HdyTabView *view, GParamSpec *pspec, ChannelTabSwitcherHandy *switcher) {
+ auto *page = hdy_tab_view_get_selected_page(view);
+ if (auto it = switcher->m_pages_rev.find(page); it != switcher->m_pages_rev.end()) {
+ switcher->m_signal_channel_switched_to.emit(it->second);
+ }
+}
+
+gboolean close_page_cb(HdyTabView *view, HdyTabPage *page, ChannelTabSwitcherHandy *switcher) {
+ switcher->ClearPage(page);
+ hdy_tab_view_close_page_finish(view, page, true);
+ return GDK_EVENT_STOP;
+}
+
+ChannelTabSwitcherHandy::ChannelTabSwitcherHandy() {
+ m_tab_bar = hdy_tab_bar_new();
+ m_tab_bar_wrapped = Glib::wrap(GTK_WIDGET(m_tab_bar));
+ m_tab_view = hdy_tab_view_new();
+ m_tab_view_wrapped = Glib::wrap(GTK_WIDGET(m_tab_view));
+
+ g_signal_connect(m_tab_view, "notify::selected-page", G_CALLBACK(selected_page_notify_cb), this);
+ g_signal_connect(m_tab_view, "close-page", G_CALLBACK(close_page_cb), this);
+
+ hdy_tab_bar_set_view(m_tab_bar, m_tab_view);
+ add(*m_tab_bar_wrapped);
+ m_tab_bar_wrapped->show();
+
+ auto &discord = Abaddon::Get().GetDiscordClient();
+ discord.signal_message_create().connect([this](const Message &data) {
+ CheckUnread(data.ChannelID);
+ });
+
+ discord.signal_message_ack().connect([this](const MessageAckData &data) {
+ CheckUnread(data.ChannelID);
+ });
+}
+
+void ChannelTabSwitcherHandy::AddChannelTab(Snowflake id) {
+ if (m_pages.find(id) != m_pages.end()) return;
+
+ auto &discord = Abaddon::Get().GetDiscordClient();
+ const auto channel = discord.GetChannel(id);
+ if (!channel.has_value()) return;
+
+ auto *dummy = Gtk::make_managed<Gtk::Box>(); // minimal
+ auto *page = hdy_tab_view_append(m_tab_view, GTK_WIDGET(dummy->gobj()));
+
+ hdy_tab_page_set_title(page, channel->GetDisplayName().c_str());
+ hdy_tab_page_set_tooltip(page, nullptr);
+
+ m_pages[id] = page;
+ m_pages_rev[page] = id;
+
+ CheckUnread(id);
+ CheckPageIcon(page, *channel);
+ AppendPageHistory(page, id);
+}
+
+void ChannelTabSwitcherHandy::ReplaceActiveTab(Snowflake id) {
+ auto *page = hdy_tab_view_get_selected_page(m_tab_view);
+ if (page == nullptr) {
+ AddChannelTab(id);
+ } else if (auto it = m_pages.find(id); it != m_pages.end()) {
+ hdy_tab_view_set_selected_page(m_tab_view, it->second);
+ } else {
+ auto &discord = Abaddon::Get().GetDiscordClient();
+ const auto channel = discord.GetChannel(id);
+ if (!channel.has_value()) return;
+
+ hdy_tab_page_set_title(page, channel->GetDisplayName().c_str());
+
+ ClearPage(page);
+ m_pages[id] = page;
+ m_pages_rev[page] = id;
+
+ CheckUnread(id);
+ CheckPageIcon(page, *channel);
+ AppendPageHistory(page, id);
+ }
+}
+
+TabsState ChannelTabSwitcherHandy::GetTabsState() {
+ TabsState state;
+
+ const gint num_pages = hdy_tab_view_get_n_pages(m_tab_view);
+ for (gint i = 0; i < num_pages; i++) {
+ auto *page = hdy_tab_view_get_nth_page(m_tab_view, i);
+ if (page != nullptr) {
+ if (const auto it = m_pages_rev.find(page); it != m_pages_rev.end()) {
+ state.Channels.push_back(it->second);
+ }
+ }
+ }
+
+ return state;
+}
+
+void ChannelTabSwitcherHandy::UseTabsState(const TabsState &state) {
+ for (auto id : state.Channels) {
+ AddChannelTab(id);
+ }
+}
+
+void ChannelTabSwitcherHandy::GoBackOnCurrent() {
+ AdvanceOnCurrent(-1);
+}
+
+void ChannelTabSwitcherHandy::GoForwardOnCurrent() {
+ AdvanceOnCurrent(1);
+}
+
+int ChannelTabSwitcherHandy::GetNumberOfTabs() const {
+ return hdy_tab_view_get_n_pages(m_tab_view);
+}
+
+void ChannelTabSwitcherHandy::CheckUnread(Snowflake id) {
+ if (auto it = m_pages.find(id); it != m_pages.end()) {
+ auto &discord = Abaddon::Get().GetDiscordClient();
+ const bool has_unreads = discord.GetUnreadStateForChannel(id) > -1;
+ const bool show_indicator = has_unreads && !discord.IsChannelMuted(id);
+ hdy_tab_page_set_needs_attention(it->second, show_indicator);
+ }
+}
+
+void ChannelTabSwitcherHandy::ClearPage(HdyTabPage *page) {
+ if (auto it = m_pages_rev.find(page); it != m_pages_rev.end()) {
+ m_pages.erase(it->second);
+ }
+ m_pages_rev.erase(page);
+ m_page_icons.erase(page);
+}
+
+void ChannelTabSwitcherHandy::OnPageIconLoad(HdyTabPage *page, const Glib::RefPtr<Gdk::Pixbuf> &pb) {
+ auto new_pb = pb->scale_simple(16, 16, Gdk::INTERP_BILINEAR);
+ m_page_icons[page] = new_pb;
+ hdy_tab_page_set_icon(page, G_ICON(new_pb->gobj()));
+}
+
+void ChannelTabSwitcherHandy::CheckPageIcon(HdyTabPage *page, const ChannelData &data) {
+ if (data.GuildID.has_value()) {
+ if (const auto guild = Abaddon::Get().GetDiscordClient().GetGuild(*data.GuildID); guild.has_value() && guild->HasIcon()) {
+ auto *child_widget = hdy_tab_page_get_child(page);
+ if (child_widget == nullptr) return; // probably wont happen :---)
+ // i think this works???
+ auto *trackable = Glib::wrap(GTK_WIDGET(child_widget));
+
+ Abaddon::Get().GetImageManager().LoadFromURL(
+ guild->GetIconURL("png", "16"),
+ sigc::track_obj([this, page](const Glib::RefPtr<Gdk::Pixbuf> &pb) { OnPageIconLoad(page, pb); },
+ *trackable));
+ return;
+ }
+ return;
+ }
+
+ hdy_tab_page_set_icon(page, nullptr);
+}
+
+void ChannelTabSwitcherHandy::AppendPageHistory(HdyTabPage *page, Snowflake channel) {
+ auto it = m_page_history.find(page);
+ if (it == m_page_history.end()) {
+ m_page_history[page] = PageHistory { { channel }, 0 };
+ return;
+ }
+
+ // drop everything beyond current position
+ it->second.Visited.resize(++it->second.CurrentVisitedIndex);
+ it->second.Visited.push_back(channel);
+}
+
+void ChannelTabSwitcherHandy::AdvanceOnCurrent(size_t by) {
+ auto *current = hdy_tab_view_get_selected_page(m_tab_view);
+ if (current == nullptr) return;
+ auto history = m_page_history.find(current);
+ if (history == m_page_history.end()) return;
+ if (by + history->second.CurrentVisitedIndex < 0 || by + history->second.CurrentVisitedIndex >= history->second.Visited.size()) return;
+
+ history->second.CurrentVisitedIndex += by;
+ const auto to_id = history->second.Visited.at(history->second.CurrentVisitedIndex);
+
+ // temporarily point current index to the end so that it doesnt fuck up the history
+ // remove it immediately after cuz the emit will call ReplaceActiveTab
+ const auto real = history->second.CurrentVisitedIndex;
+ history->second.CurrentVisitedIndex = history->second.Visited.size() - 1;
+ m_signal_channel_switched_to.emit(to_id);
+ // iterator might not be valid
+ history = m_page_history.find(current);
+ if (history != m_page_history.end()) {
+ history->second.Visited.pop_back();
+ }
+ history->second.CurrentVisitedIndex = real;
+}
+
+ChannelTabSwitcherHandy::type_signal_channel_switched_to ChannelTabSwitcherHandy::signal_channel_switched_to() {
+ return m_signal_channel_switched_to;
+}
+
+#endif
diff --git a/src/components/channeltabswitcherhandy.hpp b/src/components/channeltabswitcherhandy.hpp
new file mode 100644
index 0000000..561d463
--- /dev/null
+++ b/src/components/channeltabswitcherhandy.hpp
@@ -0,0 +1,66 @@
+#pragma once
+// perhaps this should be conditionally included within cmakelists?
+#ifdef WITH_LIBHANDY
+ #include <gtkmm/box.h>
+ #include <unordered_map>
+ #include <handy.h>
+ #include "discord/snowflake.hpp"
+ #include "state.hpp"
+
+class ChannelData;
+
+// thin wrapper over c api
+// HdyTabBar + invisible HdyTabView since it needs one
+class ChannelTabSwitcherHandy : public Gtk::Box {
+public:
+ ChannelTabSwitcherHandy();
+
+ // no-op if already added
+ void AddChannelTab(Snowflake id);
+ // switches to existing tab if it exists
+ void ReplaceActiveTab(Snowflake id);
+ TabsState GetTabsState();
+ void UseTabsState(const TabsState &state);
+
+ void GoBackOnCurrent();
+ void GoForwardOnCurrent();
+
+ [[nodiscard]] int GetNumberOfTabs() const;
+
+private:
+ void CheckUnread(Snowflake id);
+ void ClearPage(HdyTabPage *page);
+ void OnPageIconLoad(HdyTabPage *page, const Glib::RefPtr<Gdk::Pixbuf> &pb);
+ void CheckPageIcon(HdyTabPage *page, const ChannelData &data);
+ void AppendPageHistory(HdyTabPage *page, Snowflake channel);
+ void AdvanceOnCurrent(size_t by);
+
+ HdyTabBar *m_tab_bar;
+ Gtk::Widget *m_tab_bar_wrapped;
+ HdyTabView *m_tab_view;
+ Gtk::Widget *m_tab_view_wrapped;
+
+ std::unordered_map<Snowflake, HdyTabPage *> m_pages;
+ std::unordered_map<HdyTabPage *, Snowflake> m_pages_rev;
+ // need to hold a reference to the pixbuf data
+ std::unordered_map<HdyTabPage *, Glib::RefPtr<Gdk::Pixbuf>> m_page_icons;
+
+ struct PageHistory {
+ std::vector<Snowflake> Visited;
+ size_t CurrentVisitedIndex;
+ };
+
+ std::unordered_map<HdyTabPage *, PageHistory> m_page_history;
+
+ friend void selected_page_notify_cb(HdyTabView *, GParamSpec *, ChannelTabSwitcherHandy *);
+ friend gboolean close_page_cb(HdyTabView *, HdyTabPage *, ChannelTabSwitcherHandy *);
+
+public:
+ using type_signal_channel_switched_to = sigc::signal<void, Snowflake>;
+
+ type_signal_channel_switched_to signal_channel_switched_to();
+
+private:
+ type_signal_channel_switched_to m_signal_channel_switched_to;
+};
+#endif
diff --git a/src/components/chatwindow.cpp b/src/components/chatwindow.cpp
index 582343d..8667488 100644
--- a/src/components/chatwindow.cpp
+++ b/src/components/chatwindow.cpp
@@ -4,6 +4,9 @@
#include "ratelimitindicator.hpp"
#include "chatinput.hpp"
#include "chatlist.hpp"
+#ifdef WITH_LIBHANDY
+ #include "channeltabswitcherhandy.hpp"
+#endif
ChatWindow::ChatWindow() {
Abaddon::Get().GetDiscordClient().signal_message_send_fail().connect(sigc::mem_fun(*this, &ChatWindow::OnMessageSendFail));
@@ -15,6 +18,13 @@ ChatWindow::ChatWindow() {
m_rate_limit_indicator = Gtk::manage(new RateLimitIndicator);
m_meta = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
+#ifdef WITH_LIBHANDY
+ m_tab_switcher = Gtk::make_managed<ChannelTabSwitcherHandy>();
+ m_tab_switcher->signal_channel_switched_to().connect([this](Snowflake id) {
+ m_signal_action_channel_click.emit(id, false);
+ });
+#endif
+
m_rate_limit_indicator->set_margin_end(5);
m_rate_limit_indicator->set_hexpand(true);
m_rate_limit_indicator->set_halign(Gtk::ALIGN_END);
@@ -55,7 +65,7 @@ ChatWindow::ChatWindow() {
m_completer.show();
m_chat->signal_action_channel_click().connect([this](Snowflake id) {
- m_signal_action_channel_click.emit(id);
+ m_signal_action_channel_click.emit(id, true);
});
m_chat->signal_action_chat_load_history().connect([this](Snowflake id) {
m_signal_action_chat_load_history.emit(id);
@@ -88,6 +98,10 @@ ChatWindow::ChatWindow() {
m_meta->add(*m_input_indicator);
m_meta->add(*m_rate_limit_indicator);
// m_scroll->add(*m_list);
+#ifdef WITH_LIBHANDY
+ m_main->add(*m_tab_switcher);
+ m_tab_switcher->show();
+#endif
m_main->add(m_topic);
m_main->add(*m_chat);
m_main->add(m_completer);
@@ -115,6 +129,10 @@ void ChatWindow::SetActiveChannel(Snowflake id) {
m_rate_limit_indicator->SetActiveChannel(id);
if (m_is_replying)
StopReplying();
+
+#ifdef WITH_LIBHANDY
+ m_tab_switcher->ReplaceActiveTab(id);
+#endif
}
void ChatWindow::AddNewMessage(const Message &data) {
@@ -150,6 +168,32 @@ void ChatWindow::SetTopic(const std::string &text) {
m_topic.set_visible(text.length() > 0);
}
+#ifdef WITH_LIBHANDY
+void ChatWindow::OpenNewTab(Snowflake id) {
+ // open if its the first tab (in which case it really isnt a tab but whatever)
+ if (m_tab_switcher->GetNumberOfTabs() == 0) {
+ m_signal_action_channel_click.emit(id, false);
+ }
+ m_tab_switcher->AddChannelTab(id);
+}
+
+TabsState ChatWindow::GetTabsState() {
+ return m_tab_switcher->GetTabsState();
+}
+
+void ChatWindow::UseTabsState(const TabsState &state) {
+ m_tab_switcher->UseTabsState(state);
+}
+
+void ChatWindow::GoBack() {
+ m_tab_switcher->GoBackOnCurrent();
+}
+
+void ChatWindow::GoForward() {
+ m_tab_switcher->GoForwardOnCurrent();
+}
+#endif
+
Snowflake ChatWindow::GetActiveChannel() const {
return m_active_channel;
}
diff --git a/src/components/chatwindow.hpp b/src/components/chatwindow.hpp
index 0f40e88..1c0b7cc 100644
--- a/src/components/chatwindow.hpp
+++ b/src/components/chatwindow.hpp
@@ -4,6 +4,11 @@
#include <set>
#include "discord/discord.hpp"
#include "completer.hpp"
+#include "state.hpp"
+
+#ifdef WITH_LIBHANDY
+class ChannelTabSwitcherHandy;
+#endif
class ChatMessageHeader;
class ChatMessageItemContainer;
@@ -25,11 +30,19 @@ public:
void DeleteMessage(Snowflake id); // add [deleted] indicator
void UpdateMessage(Snowflake id); // add [edited] indicator
void AddNewHistory(const std::vector<Message> &msgs); // prepend messages
- void InsertChatInput(const std::string& text);
+ void InsertChatInput(const std::string &text);
Snowflake GetOldestListedMessage(); // oldest message that is currently in the ListBox
void UpdateReactions(Snowflake id);
void SetTopic(const std::string &text);
+#ifdef WITH_LIBHANDY
+ void OpenNewTab(Snowflake id);
+ TabsState GetTabsState();
+ void UseTabsState(const TabsState &state);
+ void GoBack();
+ void GoForward();
+#endif
+
protected:
bool m_is_replying = false;
Snowflake m_replying_to;
@@ -62,14 +75,18 @@ protected:
RateLimitIndicator *m_rate_limit_indicator;
Gtk::Box *m_meta;
+#ifdef WITH_LIBHANDY
+ ChannelTabSwitcherHandy *m_tab_switcher;
+#endif
+
public:
- typedef sigc::signal<void, Snowflake, Snowflake> type_signal_action_message_edit;
- typedef sigc::signal<void, std::string, Snowflake, Snowflake> type_signal_action_chat_submit;
- typedef sigc::signal<void, Snowflake> type_signal_action_chat_load_history;
- typedef sigc::signal<void, Snowflake> type_signal_action_channel_click;
- typedef sigc::signal<void, Snowflake> type_signal_action_insert_mention;
- typedef sigc::signal<void, Snowflake, Glib::ustring> type_signal_action_reaction_add;
- typedef sigc::signal<void, Snowflake, Glib::ustring> type_signal_action_reaction_remove;
+ using type_signal_action_message_edit = sigc::signal<void, Snowflake, Snowflake>;
+ using type_signal_action_chat_submit = sigc::signal<void, std::string, Snowflake, Snowflake>;
+ using type_signal_action_chat_load_history = sigc::signal<void, Snowflake>;
+ using type_signal_action_channel_click = sigc::signal<void, Snowflake, bool>;
+ using type_signal_action_insert_mention = sigc::signal<void, Snowflake>;
+ using type_signal_action_reaction_add = sigc::signal<void, Snowflake, Glib::ustring>;
+ using type_signal_action_reaction_remove = sigc::signal<void, Snowflake, Glib::ustring>;
type_signal_action_message_edit signal_action_message_edit();
type_signal_action_chat_submit signal_action_chat_submit();