From 5b806a25894bf183515c64ac8099911da8f4a0c7 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 8 Apr 2022 23:47:12 -0400 Subject: [PATCH] basic tabs system --- cmake/Findglib.cmake | 92 ++++++++++++---------- src/components/channels.cpp | 18 +++++ src/components/channels.hpp | 21 ++++- src/components/channeltabswitcherhandy.cpp | 58 ++++++++++++++ src/components/channeltabswitcherhandy.hpp | 37 +++++++++ src/components/chatwindow.cpp | 24 ++++++ src/components/chatwindow.hpp | 28 +++++-- src/windows/mainwindow.cpp | 6 ++ 8 files changed, 230 insertions(+), 54 deletions(-) create mode 100644 src/components/channeltabswitcherhandy.cpp create mode 100644 src/components/channeltabswitcherhandy.hpp diff --git a/cmake/Findglib.cmake b/cmake/Findglib.cmake index b2c730b..f60f75d 100644 --- a/cmake/Findglib.cmake +++ b/cmake/Findglib.cmake @@ -2,56 +2,64 @@ find_package(PkgConfig) pkg_check_modules(PC_GLIB2 QUIET glib-2.0) find_path(GLIB_INCLUDE_DIR - NAMES glib.h - HINTS ${PC_GLIB2_INCLUDEDIR} - ${PC_GLIB2_INCLUDE_DIRS} - $ENV{GLIB2_HOME}/include - $ENV{GLIB2_ROOT}/include - /usr/local/include - /usr/include - /glib2/include - /glib-2.0/include - PATH_SUFFIXES glib2 glib-2.0 glib-2.0/include -) + NAMES glib.h + HINTS ${PC_GLIB2_INCLUDEDIR} + ${PC_GLIB2_INCLUDE_DIRS} + $ENV{GLIB2_HOME}/include + $ENV{GLIB2_ROOT}/include + /usr/local/include + /usr/include + /glib2/include + /glib-2.0/include + PATH_SUFFIXES glib2 glib-2.0 glib-2.0/include + ) set(GLIB_INCLUDE_DIRS ${GLIB_INCLUDE_DIR}) find_library(GLIB_LIBRARIES - NAMES glib2 - glib-2.0 - HINTS ${PC_GLIB2_LIBDIR} - ${PC_GLIB2_LIBRARY_DIRS} - $ENV{GLIB2_HOME}/lib - $ENV{GLIB2_ROOT}/lib - /usr/local/lib - /usr/lib - /lib - /glib-2.0/lib - PATH_SUFFIXES glib2 glib-2.0 -) + NAMES glib2 + glib-2.0 + HINTS ${PC_GLIB2_LIBDIR} + ${PC_GLIB2_LIBRARY_DIRS} + $ENV{GLIB2_HOME}/lib + $ENV{GLIB2_ROOT}/lib + /usr/local/lib + /usr/lib + /lib + /glib-2.0/lib + PATH_SUFFIXES glib2 glib-2.0 + ) + +find_library(glib_GOBJECT_LIBRARIES + NAMES gobject-2.0 + HINTS ${PC_GLIB2_LIBDIR} + ${PC_GLIB2_LIBRARY_DIRS} + ) get_filename_component(_GLIB2_LIB_DIR "${GLIB_LIBRARIES}" PATH) find_path(GLIB_CONFIG_INCLUDE_DIR - NAMES glibconfig.h - HINTS ${PC_GLIB2_INCLUDEDIR} - ${PC_GLIB2_INCLUDE_DIRS} - $ENV{GLIB2_HOME}/include - $ENV{GLIB2_ROOT}/include - /usr/local/include - /usr/include - /glib2/include - /glib-2.0/include - ${_GLIB2_LIB_DIR} - ${CMAKE_SYSTEM_LIBRARY_PATH} - PATH_SUFFIXES glib2 glib-2.0 glib-2.0/include -) + NAMES glibconfig.h + HINTS ${PC_GLIB2_INCLUDEDIR} + ${PC_GLIB2_INCLUDE_DIRS} + $ENV{GLIB2_HOME}/include + $ENV{GLIB2_ROOT}/include + /usr/local/include + /usr/include + /glib2/include + /glib-2.0/include + ${_GLIB2_LIB_DIR} + ${CMAKE_SYSTEM_LIBRARY_PATH} + PATH_SUFFIXES glib2 glib-2.0 glib-2.0/include + ) if (GLIB_CONFIG_INCLUDE_DIR) set(GLIB_INCLUDE_DIRS ${GLIB_INCLUDE_DIRS} ${GLIB_CONFIG_INCLUDE_DIR}) -endif() +endif () + +set(GLIB_LIBRARIES ${GLIB_LIBRARIES} ${glib_GOBJECT_LIBRARIES}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(glib - REQUIRED_VARS - GLIB_LIBRARIES - GLIB_INCLUDE_DIRS - VERSION_VAR GLIB_VERSION) -mark_as_advanced(GLIB_INCLUDE_DIR GLIB_CONFIG_INCLUDE_DIR) + REQUIRED_VARS + GLIB_LIBRARIES + GLIB_INCLUDE_DIRS + VERSION_VAR GLIB_VERSION) +mark_as_advanced(GLIB_INCLUDE_DIR GLIB_CONFIG_INCLUDE_DIR glib_GOBJECT_LIBRARIES) 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((*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 m_tmp_channel_map; public: - typedef sigc::signal type_signal_action_channel_item_select; - typedef sigc::signal type_signal_action_guild_leave; - typedef sigc::signal type_signal_action_guild_settings; + using type_signal_action_channel_item_select = sigc::signal; + using type_signal_action_guild_leave = sigc::signal; + using type_signal_action_guild_settings = sigc::signal; + +#ifdef WITH_LIBHANDY + using type_signal_action_open_new_tab = sigc::signal; + 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..94d5c29 --- /dev/null +++ b/src/components/channeltabswitcherhandy.cpp @@ -0,0 +1,58 @@ +#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); + } +} + +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); + + hdy_tab_bar_set_view(m_tab_bar, m_tab_view); + add(*m_tab_bar_wrapped); + m_tab_bar_wrapped->show(); +} + +void ChannelTabSwitcherHandy::AddChannelTab(Snowflake id) { + auto &discord = Abaddon::Get().GetDiscordClient(); + const auto channel = discord.GetChannel(id); + if (!channel.has_value()) return; + + auto *dummy = Gtk::make_managed(); // minimal + auto *page = hdy_tab_view_append(m_tab_view, GTK_WIDGET(dummy->gobj())); + + hdy_tab_page_set_title(page, ("#" + *channel->Name).c_str()); + + m_pages[id] = page; + m_pages_rev[page] = id; +} + +void ChannelTabSwitcherHandy::ReplaceActiveTab(Snowflake id) { + auto *page = hdy_tab_view_get_selected_page(m_tab_view); + if (page == nullptr) { + AddChannelTab(id); + } else { + auto &discord = Abaddon::Get().GetDiscordClient(); + const auto channel = discord.GetChannel(id); + if (!channel.has_value()) return; + + hdy_tab_page_set_title(page, ("#" + *channel->Name).c_str()); + m_pages_rev[page] = id; + } +} + +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..53e1624 --- /dev/null +++ b/src/components/channeltabswitcherhandy.hpp @@ -0,0 +1,37 @@ +#pragma once +// perhaps this should be conditionally included within cmakelists? +#ifdef WITH_LIBHANDY + #include + #include + #include + #include "discord/snowflake.hpp" + +// thin wrapper over c api +// HdyTabBar + invisible HdyTabView since it needs one +class ChannelTabSwitcherHandy : public Gtk::Box { +public: + ChannelTabSwitcherHandy(); + + void AddChannelTab(Snowflake id); + void ReplaceActiveTab(Snowflake id); + +private: + HdyTabBar *m_tab_bar; + Gtk::Widget *m_tab_bar_wrapped; + HdyTabView *m_tab_view; + Gtk::Widget *m_tab_view_wrapped; + + std::unordered_map m_pages; + std::unordered_map m_pages_rev; + + friend void selected_page_notify_cb(HdyTabView *, GParamSpec *, ChannelTabSwitcherHandy *); + +public: + using type_signal_channel_switched_to = sigc::signal; + + 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(); + 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 &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 type_signal_action_message_edit; - typedef sigc::signal type_signal_action_chat_submit; - typedef sigc::signal type_signal_action_chat_load_history; - typedef sigc::signal type_signal_action_channel_click; - typedef sigc::signal type_signal_action_insert_mention; - typedef sigc::signal type_signal_action_reaction_add; - typedef sigc::signal type_signal_action_reaction_remove; + using type_signal_action_message_edit = sigc::signal; + using type_signal_action_chat_submit = sigc::signal; + using type_signal_action_chat_load_history = sigc::signal; + using type_signal_action_channel_click = sigc::signal; + using type_signal_action_insert_mention = sigc::signal; + using type_signal_action_reaction_add = sigc::signal; + using type_signal_action_reaction_remove = sigc::signal; type_signal_action_message_edit signal_action_message_edit(); type_signal_action_chat_submit signal_action_chat_submit(); diff --git a/src/windows/mainwindow.cpp b/src/windows/mainwindow.cpp index e12e9bd..b518b93 100644 --- a/src/windows/mainwindow.cpp +++ b/src/windows/mainwindow.cpp @@ -27,6 +27,12 @@ MainWindow::MainWindow() chat->set_hexpand(true); chat->show(); +#ifdef WITH_LIBHANDY + m_channel_list.signal_action_open_new_tab().connect([this](Snowflake id) { + m_chat.OpenNewTab(id); + }); +#endif + m_channel_list.set_vexpand(true); m_channel_list.set_size_request(-1, -1); m_channel_list.show();