diff options
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/channels.cpp | 18 | ||||
-rw-r--r-- | src/components/channels.hpp | 21 | ||||
-rw-r--r-- | src/components/channeltabswitcherhandy.cpp | 128 | ||||
-rw-r--r-- | src/components/channeltabswitcherhandy.hpp | 49 | ||||
-rw-r--r-- | src/components/chatwindow.cpp | 24 | ||||
-rw-r--r-- | src/components/chatwindow.hpp | 28 |
6 files changed, 256 insertions, 12 deletions
diff --git a/src/components/channels.cpp b/src/components/channels.cpp index 4a6b1bc..929eeb8 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -17,6 +17,9 @@ 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) +#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 +146,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); @@ -960,6 +972,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..e8032af 100644 --- a/src/components/channels.hpp +++ b/src/components/channels.hpp @@ -121,6 +121,10 @@ 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; @@ -149,16 +153,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..e359896 --- /dev/null +++ b/src/components/channeltabswitcherhandy.cpp @@ -0,0 +1,128 @@ +#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->Name).c_str()); + hdy_tab_page_set_tooltip(page, nullptr); + + m_pages[id] = page; + m_pages_rev[page] = id; + + CheckUnread(id); + CheckPageIcon(page, *channel); +} + +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); + } +} + +void ChannelTabSwitcherHandy::CheckUnread(Snowflake id) { + if (auto it = m_pages.find(id); it != m_pages.end()) { + hdy_tab_page_set_needs_attention(it->second, Abaddon::Get().GetDiscordClient().GetUnreadStateForChannel(id) > -1); + } +} + +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); +} + +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..6a2dbff --- /dev/null +++ b/src/components/channeltabswitcherhandy.hpp @@ -0,0 +1,49 @@ +#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" + +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); + +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); + + 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; + + 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..99ec8a0 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); + }); +#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); @@ -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,12 @@ void ChatWindow::SetTopic(const std::string &text) { m_topic.set_visible(text.length() > 0); } +#ifdef WITH_LIBHANDY +void ChatWindow::OpenNewTab(Snowflake id) { + m_tab_switcher->AddChannelTab(id); +} +#endif + Snowflake ChatWindow::GetActiveChannel() const { return m_active_channel; } diff --git a/src/components/chatwindow.hpp b/src/components/chatwindow.hpp index 0f40e88..d77afec 100644 --- a/src/components/chatwindow.hpp +++ b/src/components/chatwindow.hpp @@ -5,6 +5,10 @@ #include "discord/discord.hpp" #include "completer.hpp" +#ifdef WITH_LIBHANDY +class ChannelTabSwitcherHandy; +#endif + class ChatMessageHeader; class ChatMessageItemContainer; class ChatInput; @@ -25,11 +29,15 @@ 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); +#endif + protected: bool m_is_replying = false; Snowflake m_replying_to; @@ -62,14 +70,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>; + 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(); |