diff --git a/CHANGELOG.md b/CHANGELOG.md index 53492ad57..c6f72ba59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ - Minor: Made username autocompletion truecase (#1199, #1883) - Minor: Update the listing of top-level domains. (#2345) - Minor: Properly respect RECONNECT messages from Twitch (#2347) +- Minor: Added command line option to attach chatterino to another window. - Minor: Hide "Case-sensitive" column for user highlights. (#2404) - Minor: Added human-readable formatting to remaining timeout duration. (#2398) - Minor: Update emojis version to 13 (2020). (#1555) diff --git a/chatterino.pro b/chatterino.pro index 7268f78f3..e34df70b4 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -259,6 +259,7 @@ SOURCES += \ src/widgets/BasePopup.cpp \ src/widgets/BaseWidget.cpp \ src/widgets/BaseWindow.cpp \ + src/widgets/FramelessEmbedWindow.cpp \ src/widgets/dialogs/ChannelFilterEditorDialog.cpp \ src/widgets/dialogs/ColorPickerDialog.cpp \ src/widgets/dialogs/EmotePopup.cpp \ @@ -512,6 +513,7 @@ HEADERS += \ src/widgets/BasePopup.hpp \ src/widgets/BaseWidget.hpp \ src/widgets/BaseWindow.hpp \ + src/widgets/FramelessEmbedWindow.hpp \ src/widgets/dialogs/ChannelFilterEditorDialog.hpp \ src/widgets/dialogs/ColorPickerDialog.hpp \ src/widgets/dialogs/EmotePopup.hpp \ diff --git a/src/Application.cpp b/src/Application.cpp index 08bb0c998..3371469fd 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -76,7 +76,8 @@ void Application::initialize(Settings &settings, Paths &paths) isAppInitialized = true; // Show changelog - if (getSettings()->currentVersion.getValue() != "" && + if (!getArgs().isFramelessEmbed && + getSettings()->currentVersion.getValue() != "" && getSettings()->currentVersion.getValue() != CHATTERINO_VERSION) { auto box = new QMessageBox(QMessageBox::Information, "Chatterino 2", @@ -90,11 +91,14 @@ void Application::initialize(Settings &settings, Paths &paths) } } - getSettings()->currentVersion.setValue(CHATTERINO_VERSION); - - if (getSettings()->enableExperimentalIrc) + if (!getArgs().isFramelessEmbed) { - Irc::instance().load(); + getSettings()->currentVersion.setValue(CHATTERINO_VERSION); + + if (getSettings()->enableExperimentalIrc) + { + Irc::instance().load(); + } } for (auto &singleton : this->singletons_) @@ -103,7 +107,7 @@ void Application::initialize(Settings &settings, Paths &paths) } // add crash message - if (getArgs().crashRecovery) + if (!getArgs().isFramelessEmbed && getArgs().crashRecovery) { if (auto selected = this->windows->getMainWindow().getNotebook().getSelectedPage()) @@ -126,7 +130,10 @@ void Application::initialize(Settings &settings, Paths &paths) this->windows->updateWordTypeMask(); - this->initNm(paths); + if (!getArgs().isFramelessEmbed) + { + this->initNm(paths); + } this->initPubsub(); } @@ -136,7 +143,10 @@ int Application::run(QApplication &qtApp) this->twitch.server->connect(); - this->windows->getMainWindow().show(); + if (!getArgs().isFramelessEmbed) + { + this->windows->getMainWindow().show(); + } getSettings()->betaUpdates.connect( [] { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 872089ee3..da7779cb7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -302,6 +302,8 @@ set(SOURCE_FILES main.cpp widgets/BaseWidget.hpp widgets/BaseWindow.cpp widgets/BaseWindow.hpp + widgets/FramelessEmbedWindow.cpp + widgets/FramelessEmbedWindow.hpp widgets/Label.cpp widgets/Label.hpp widgets/Notebook.cpp diff --git a/src/common/Args.cpp b/src/common/Args.cpp index e6960e824..c64369ffe 100644 --- a/src/common/Args.cpp +++ b/src/common/Args.cpp @@ -26,6 +26,9 @@ Args::Args(const QApplication &app) // Added to ignore the parent-window option passed during native messaging QCommandLineOption parentWindowOption("parent-window"); parentWindowOption.setFlags(QCommandLineOption::HiddenFromHelp); + QCommandLineOption parentWindowIdOption("x-attach-split-to-window", "", + "window-id"); + parentWindowIdOption.setFlags(QCommandLineOption::HiddenFromHelp); // Verbose QCommandLineOption verboseOption({{"v", "verbose"}, @@ -37,6 +40,7 @@ Args::Args(const QApplication &app) {{"V", "version"}, "Displays version information."}, crashRecoveryOption, parentWindowOption, + parentWindowIdOption, verboseOption, }); parser.addOption(QCommandLineOption( @@ -73,6 +77,15 @@ Args::Args(const QApplication &app) this->printVersion = parser.isSet("V"); this->crashRecovery = parser.isSet("crash-recovery"); + + if (parser.isSet(parentWindowIdOption)) + { + this->isFramelessEmbed = true; + this->dontSaveSettings = true; + this->dontLoadMainWindow = true; + + this->parentWindowId = parser.value(parentWindowIdOption).toULongLong(); + } } void Args::applyCustomChannelLayout(const QString &argValue) diff --git a/src/common/Args.hpp b/src/common/Args.hpp index 80c37ebbb..4087b336f 100644 --- a/src/common/Args.hpp +++ b/src/common/Args.hpp @@ -15,7 +15,13 @@ public: bool printVersion{}; bool crashRecovery{}; bool shouldRunBrowserExtensionHost{}; + // Shows a single chat. Used on windows to embed in another application. + bool isFramelessEmbed{}; + boost::optional parentWindowId{}; + + // Not settings directly bool dontSaveSettings{}; + bool dontLoadMainWindow{}; boost::optional customChannelLayout; bool verbose{}; diff --git a/src/singletons/WindowManager.cpp b/src/singletons/WindowManager.cpp index f4cd93a5a..51420927d 100644 --- a/src/singletons/WindowManager.cpp +++ b/src/singletons/WindowManager.cpp @@ -28,6 +28,7 @@ #include "util/Clamp.hpp" #include "util/CombinePath.hpp" #include "widgets/AccountSwitchPopup.hpp" +#include "widgets/FramelessEmbedWindow.hpp" #include "widgets/Notebook.hpp" #include "widgets/Window.hpp" #include "widgets/dialogs/SettingsDialog.hpp" @@ -130,6 +131,8 @@ WindowManager::WindowManager() }); } +WindowManager::~WindowManager() = default; + MessageElementFlags WindowManager::getWordFlags() { return this->wordFlags_; @@ -269,22 +272,14 @@ Window &WindowManager::createWindow(WindowType type, bool show) return *window; } -int WindowManager::windowCount() +void WindowManager::select(Split *split) { - return this->windows_.size(); + this->selectSplit.invoke(split); } -Window *WindowManager::windowAt(int index) +void WindowManager::select(SplitContainer *container) { - assertInGuiThread(); - - if (index < 0 || (size_t)index >= this->windows_.size()) - { - return nullptr; - } - qCDebug(chatterinoWindowmanager) << "getting window at bad index" << index; - - return this->windows_.at(index); + this->selectSplitContainer.invoke(container); } QPoint WindowManager::emotePopupPos() @@ -324,11 +319,23 @@ void WindowManager::initialize(Settings &settings, Paths &paths) this->applyWindowLayout(windowLayout); } + if (getArgs().isFramelessEmbed) + { + this->framelessEmbedWindow_.reset(new FramelessEmbedWindow); + this->framelessEmbedWindow_->show(); + } + // No main window has been created from loading, create an empty one if (this->mainWindow_ == nullptr) { this->mainWindow_ = &this->createWindow(WindowType::Main); this->mainWindow_->getNotebook().addPage(true); + + // TODO: don't create main window if it's a frameless embed + if (getArgs().isFramelessEmbed) + { + this->mainWindow_->hide(); + } } settings.timestampFormat.connect([this](auto, auto) { @@ -634,6 +641,11 @@ WindowLayout WindowManager::loadWindowLayoutFromFile() const void WindowManager::applyWindowLayout(const WindowLayout &layout) { + if (getArgs().dontLoadMainWindow) + { + return; + } + // Set emote popup position this->emotePopupPos_ = layout.emotePopupPos_; diff --git a/src/singletons/WindowManager.hpp b/src/singletons/WindowManager.hpp index d4f6d8de7..42a6f5d51 100644 --- a/src/singletons/WindowManager.hpp +++ b/src/singletons/WindowManager.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include "common/Channel.hpp" #include "common/FlagsEnum.hpp" #include "common/Singleton.hpp" @@ -19,6 +20,7 @@ using MessageElementFlags = FlagsEnum; enum class WindowType; enum class SettingsDialogPreference; +class FramelessEmbedWindow; class WindowManager final : public Singleton { @@ -26,6 +28,7 @@ public: static const QString WINDOW_LAYOUT_FILENAME; WindowManager(); + ~WindowManager() override; static void encodeChannel(IndirectChannel channel, QJsonObject &obj); static void encodeFilters(Split *split, QJsonArray &arr); @@ -53,8 +56,8 @@ public: Window &getSelectedWindow(); Window &createWindow(WindowType type, bool show = true); - int windowCount(); - Window *windowAt(int index); + void select(Split *split); + void select(SplitContainer *container); QPoint emotePopupPos(); void setEmotePopupPos(QPoint pos); @@ -92,6 +95,9 @@ public: // It is currently being used by the "Tooltip Preview Image" system to recheck if an image is ready to be rendered. pajlada::Signals::NoArgSignal miscUpdate; + pajlada::Signals::Signal selectSplit; + pajlada::Signals::Signal selectSplitContainer; + private: void encodeNodeRecursively(SplitContainer::Node *node, QJsonObject &obj); @@ -112,6 +118,7 @@ private: std::vector windows_; + std::unique_ptr framelessEmbedWindow_; Window *mainWindow_{}; Window *selectedWindow_{}; diff --git a/src/widgets/FramelessEmbedWindow.cpp b/src/widgets/FramelessEmbedWindow.cpp new file mode 100644 index 000000000..263daf78b --- /dev/null +++ b/src/widgets/FramelessEmbedWindow.cpp @@ -0,0 +1,100 @@ +#include "FramelessEmbedWindow.hpp" + +#include +#include "Application.hpp" +#include "QJsonDocument" +#include "QMessageBox" +#include "providers/twitch/TwitchIrcServer.hpp" +//#include "widgets/helper/ChannelView.hpp" +#include "common/Args.hpp" +#include "widgets/splits/Split.hpp" + +#ifdef USEWINSDK +# include "Windows.h" +#endif + +namespace chatterino { + +FramelessEmbedWindow::FramelessEmbedWindow() + : BaseWindow(BaseWindow::Frameless) +{ + this->split_ = new Split((QWidget *)nullptr); + auto layout = new QHBoxLayout; + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(this->split_); + + this->getLayoutContainer()->setLayout(layout); +} + +#ifdef USEWINSDK +bool FramelessEmbedWindow::nativeEvent(const QByteArray &eventType, + void *message, long *result) +{ +# if (QT_VERSION == QT_VERSION_CHECK(5, 11, 1)) + MSG *msg = *reinterpret_cast(message); +# else + MSG *msg = reinterpret_cast(message); +# endif + + if (msg->message == WM_COPYDATA) + { + auto data = reinterpret_cast(msg->lParam); + + // no idea why I have to read it to a string and then encode it back to utf-8 + auto str = QString::fromUtf8(reinterpret_cast(data->lpData), + int(data->cbData)); + auto doc = QJsonDocument::fromJson(str.toUtf8()); + + auto root = doc.object(); + if (root.value("type").toString() == "set-channel") + { + if (root.value("provider").toString() == "twitch") + { + auto channelName = root.value("channel-name").toString(); + + this->split_->setChannel( + getApp()->twitch2->getOrAddChannel(channelName)); + } + } + } + + return BaseWidget::nativeEvent(eventType, message, result); +} + +void FramelessEmbedWindow::showEvent(QShowEvent *) +{ + if (!getArgs().parentWindowId) + { + return; + } + + if (auto parentHwnd = + reinterpret_cast(getArgs().parentWindowId.get())) + { + auto handle = reinterpret_cast(this->winId()); + if (!::SetParent(handle, parentHwnd)) + { + qApp->exit(1); + } + + QJsonDocument doc; + QJsonObject root; + root.insert("type", "created-window"); + root.insert( + "window-id", + QString::number(reinterpret_cast(handle))); + doc.setObject(root); + auto json = doc.toJson(); + json.append('\0'); + + COPYDATASTRUCT cds; + cds.cbData = static_cast(json.size()); + cds.lpData = json.data(); + + ::SendMessage(parentHwnd, WM_COPYDATA, reinterpret_cast(handle), + reinterpret_cast(&cds)); + } +} +#endif + +} // namespace chatterino diff --git a/src/widgets/FramelessEmbedWindow.hpp b/src/widgets/FramelessEmbedWindow.hpp new file mode 100644 index 000000000..9d371afc6 --- /dev/null +++ b/src/widgets/FramelessEmbedWindow.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "widgets/BaseWindow.hpp" + +namespace chatterino { + +class Split; + +class FramelessEmbedWindow : public BaseWindow +{ +public: + FramelessEmbedWindow(); + +protected: +#ifdef USEWINSDK + bool nativeEvent(const QByteArray &eventType, void *message, + long *result) override; + void showEvent(QShowEvent *event) override; +#endif + +private: + Split *split_{}; +}; + +} // namespace chatterino diff --git a/src/widgets/Notebook.cpp b/src/widgets/Notebook.cpp index 999487f1a..a3663a2d5 100644 --- a/src/widgets/Notebook.cpp +++ b/src/widgets/Notebook.cpp @@ -631,6 +631,29 @@ SplitNotebook::SplitNotebook(Window *parent) { this->addCustomButtons(); } + + this->signalHolder_.managedConnect( + getApp()->windows->selectSplit, [this](Split *split) { + for (auto &&item : this->items()) + { + if (auto sc = dynamic_cast(item.page)) + { + auto &&splits = sc->getSplits(); + if (std::find(splits.begin(), splits.end(), split) != + splits.end()) + { + this->select(item.page); + split->setFocus(); + break; + } + } + } + }); + + this->signalHolder_.managedConnect(getApp()->windows->selectSplitContainer, + [this](SplitContainer *sc) { + this->select(sc); + }); } void SplitNotebook::showEvent(QShowEvent *) diff --git a/src/widgets/Notebook.hpp b/src/widgets/Notebook.hpp index 4f4c59c57..1169593bb 100644 --- a/src/widgets/Notebook.hpp +++ b/src/widgets/Notebook.hpp @@ -64,13 +64,18 @@ protected: NotebookButton *getAddButton(); NotebookButton *addCustomButton(); -private: struct Item { NotebookTab *tab{}; QWidget *page{}; QWidget *selectedWidget{}; }; + const QList items() + { + return items_; + } + +private: bool containsPage(QWidget *page); Item &findItem(QWidget *page); diff --git a/src/widgets/dialogs/switcher/QuickSwitcherPopup.cpp b/src/widgets/dialogs/switcher/QuickSwitcherPopup.cpp index 9a8fe08ca..4d7d56bd3 100644 --- a/src/widgets/dialogs/switcher/QuickSwitcherPopup.cpp +++ b/src/widgets/dialogs/switcher/QuickSwitcherPopup.cpp @@ -94,7 +94,7 @@ void QuickSwitcherPopup::updateSuggestions(const QString &text) if (split->getChannel()->getName().contains(text, Qt::CaseInsensitive)) { - auto item = std::make_unique(split); + auto item = std::make_unique(sc, split); this->switcherModel_.addItem(std::move(item)); // We want to continue the outer loop so we need a goto diff --git a/src/widgets/dialogs/switcher/SwitchSplitItem.cpp b/src/widgets/dialogs/switcher/SwitchSplitItem.cpp index f110eb55d..480261125 100644 --- a/src/widgets/dialogs/switcher/SwitchSplitItem.cpp +++ b/src/widgets/dialogs/switcher/SwitchSplitItem.cpp @@ -7,31 +7,23 @@ namespace chatterino { -SwitchSplitItem::SwitchSplitItem(Split *split) - : AbstractSwitcherItem(QIcon(":switcher/switch.svg")) - , split_(split) - , container_(split->getContainer()) -{ -} - -SwitchSplitItem::SwitchSplitItem(SplitContainer *container) +SwitchSplitItem::SwitchSplitItem(SplitContainer *container, Split *split) : AbstractSwitcherItem(QIcon(":switcher/switch.svg")) , container_(container) + , split_(split) { + assert(this->container_ != nullptr); } void SwitchSplitItem::action() { - auto &nb = getApp()->windows->getMainWindow().getNotebook(); - nb.select(this->container_); - - /* - * If the item is referring to a specific channel, select the - * corresponding split. - */ if (this->split_) { - this->container_->setSelected(this->split_); + getApp()->windows->select(this->split_); + } + else if (this->container_) + { + getApp()->windows->select(this->container_); } } diff --git a/src/widgets/dialogs/switcher/SwitchSplitItem.hpp b/src/widgets/dialogs/switcher/SwitchSplitItem.hpp index abcf119af..4baf421fd 100644 --- a/src/widgets/dialogs/switcher/SwitchSplitItem.hpp +++ b/src/widgets/dialogs/switcher/SwitchSplitItem.hpp @@ -13,8 +13,7 @@ namespace chatterino { class SwitchSplitItem : public AbstractSwitcherItem { public: - SwitchSplitItem(Split *split); - SwitchSplitItem(SplitContainer *container); + SwitchSplitItem(SplitContainer *container, Split *split = nullptr); virtual void action() override; @@ -22,8 +21,8 @@ public: virtual QSize sizeHint(const QRect &rect) const override; private: - Split *split_{}; SplitContainer *container_{}; + Split *split_{}; }; } // namespace chatterino diff --git a/src/widgets/splits/Split.cpp b/src/widgets/splits/Split.cpp index 77ed3cdd3..7a4732271 100644 --- a/src/widgets/splits/Split.cpp +++ b/src/widgets/splits/Split.cpp @@ -31,7 +31,6 @@ #include "widgets/helper/NotebookTab.hpp" #include "widgets/helper/ResizingTextEdit.hpp" #include "widgets/helper/SearchPopup.hpp" -#include "widgets/splits/ClosedSplits.hpp" #include "widgets/splits/SplitContainer.hpp" #include "widgets/splits/SplitHeader.hpp" #include "widgets/splits/SplitInput.hpp" @@ -78,15 +77,8 @@ namespace { pajlada::Signals::Signal Split::modifierStatusChanged; Qt::KeyboardModifiers Split::modifierStatus = Qt::NoModifier; -Split::Split(SplitContainer *parent) - : Split(static_cast(parent)) -{ - this->container_ = parent; -} - Split::Split(QWidget *parent) : BaseWidget(parent) - , container_(nullptr) , channel_(Channel::getEmpty()) , vbox_(new QVBoxLayout(this)) , header_(new SplitHeader(this)) @@ -173,7 +165,7 @@ Split::Split(QWidget *parent) }); this->view_->joinToChannel.connect([this](QString twitchChannel) { - this->container_->appendNewSplit(false)->setChannel( + this->openSplitRequested.invoke( getApp()->twitch.server->getOrAddChannel(twitchChannel)); }); @@ -294,21 +286,6 @@ ChannelView &Split::getChannelView() return *this->view_; } -SplitContainer *Split::getContainer() -{ - return this->container_; -} - -bool Split::isInContainer() const -{ - return this->container_ != nullptr; -} - -void Split::setContainer(SplitContainer *container) -{ - this->container_ = container; -} - void Split::updateInputPlaceholder() { if (!this->getChannel()->isTwitchChannel()) @@ -388,7 +365,7 @@ void Split::setChannel(IndirectChannel newChannel) } this->channel_.get()->displayNameChanged.connect([this] { - this->container_->refreshTab(); + this->actionRequested.invoke(Action::RefreshTab); }); this->channelChanged.invoke(); @@ -435,10 +412,7 @@ void Split::showChangeChannelPopup(const char *dialogTitle, bool empty, if (dialog->hasSeletedChannel()) { this->setChannel(dialog->getSelectedChannel()); - if (this->isInContainer()) - { - this->container_->refreshTab(); - } + this->actionRequested.invoke(Action::RefreshTab); } callback(dialog->hasSeletedChannel()); @@ -514,10 +488,7 @@ void Split::enterEvent(QEvent *event) this->overlay_->show(); } - if (this->container_ != nullptr) - { - this->container_->resetMouseStatus(); - } + this->actionRequested.invoke(Action::ResetMouseStatus); } void Split::leaveEvent(QEvent *event) @@ -554,23 +525,12 @@ void Split::setIsTopRightSplit(bool value) /// Slots void Split::addSibling() { - if (this->container_) - { - this->container_->appendNewSplit(true); - } + this->actionRequested.invoke(Action::AppendNewSplit); } void Split::deleteFromContainer() { - if (this->container_) - { - this->container_->deleteSplit(this); - auto *tab = this->getContainer()->getTab(); - tab->connect(tab, &QWidget::destroyed, [tab]() mutable { - ClosedSplits::invalidateTab(tab); - }); - ClosedSplits::push({this->getChannel()->getName(), tab}); - } + this->actionRequested.invoke(Action::Delete); } void Split::changeChannel() diff --git a/src/widgets/splits/Split.hpp b/src/widgets/splits/Split.hpp index c799c19db..8ba4b5b7c 100644 --- a/src/widgets/splits/Split.hpp +++ b/src/widgets/splits/Split.hpp @@ -38,7 +38,6 @@ class Split : public BaseWidget, pajlada::Signals::SignalHolder Q_OBJECT public: - explicit Split(SplitContainer *parent); explicit Split(QWidget *parent); ~Split() override; @@ -48,7 +47,6 @@ public: pajlada::Signals::NoArgSignal focusLost; ChannelView &getChannelView(); - SplitContainer *getContainer(); IndirectChannel getIndirectChannel(); ChannelPtr getChannel(); @@ -80,6 +78,24 @@ public: modifierStatusChanged; static Qt::KeyboardModifiers modifierStatus; + enum class Action { + RefreshTab, + ResetMouseStatus, + AppendNewSplit, + Delete, + + SelectSplitLeft, + SelectSplitRight, + SelectSplitAbove, + SelectSplitBelow, + }; + + pajlada::Signals::Signal actionRequested; + pajlada::Signals::Signal openSplitRequested; + + // args: (SplitContainer::Direction dir, Split* parent) + pajlada::Signals::Signal insertSplitRequested; + protected: void paintEvent(QPaintEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; @@ -98,7 +114,6 @@ private: void handleModifiers(Qt::KeyboardModifiers modifiers); void updateInputPlaceholder(); - SplitContainer *container_; IndirectChannel channel_; bool moderationMode_{}; diff --git a/src/widgets/splits/SplitContainer.cpp b/src/widgets/splits/SplitContainer.cpp index 530133cd7..4a04ba199 100644 --- a/src/widgets/splits/SplitContainer.cpp +++ b/src/widgets/splits/SplitContainer.cpp @@ -11,6 +11,7 @@ #include "widgets/Notebook.hpp" #include "widgets/helper/ChannelView.hpp" #include "widgets/helper/NotebookTab.hpp" +#include "widgets/splits/ClosedSplits.hpp" #include "widgets/splits/Split.hpp" #include @@ -160,8 +161,6 @@ void SplitContainer::insertSplit(Split *split, Direction direction, assertInGuiThread(); - split->setContainer(this); - if (relativeTo == nullptr) { if (this->baseNode_.type_ == Node::EmptyRoot) @@ -211,7 +210,9 @@ void SplitContainer::addSplit(Split *split) this->refreshTab(); - this->managedConnect(split->getChannelView().tabHighlightRequested, + auto &&conns = this->connectionsPerSplit_[split]; + + conns.managedConnect(split->getChannelView().tabHighlightRequested, [this](HighlightState state) { if (this->tab_ != nullptr) { @@ -219,14 +220,64 @@ void SplitContainer::addSplit(Split *split) } }); - this->managedConnect(split->getChannelView().liveStatusChanged, [this]() { + conns.managedConnect(split->getChannelView().liveStatusChanged, [this]() { this->refreshTabLiveStatus(); }); - this->managedConnect(split->focused, [this, split] { + conns.managedConnect(split->focused, [this, split] { this->setSelected(split); }); + conns.managedConnect(split->openSplitRequested, [this](auto channel) { + this->appendNewSplit(false)->setChannel(channel); + }); + + conns.managedConnect( + split->actionRequested, [this, split](Split::Action action) { + switch (action) + { + case Split::Action::RefreshTab: + this->refreshTab(); + break; + + case Split::Action::ResetMouseStatus: + this->resetMouseStatus(); + break; + + case Split::Action::AppendNewSplit: + this->appendNewSplit(true); + break; + + case Split::Action::Delete: { + this->deleteSplit(split); + auto *tab = this->getTab(); + tab->connect(tab, &QWidget::destroyed, [tab]() mutable { + ClosedSplits::invalidateTab(tab); + }); + ClosedSplits::push({split->getChannel()->getName(), tab}); + } + break; + + case Split::Action::SelectSplitLeft: + this->selectNextSplit(SplitContainer::Left); + break; + case Split::Action::SelectSplitRight: + this->selectNextSplit(SplitContainer::Right); + break; + case Split::Action::SelectSplitAbove: + this->selectNextSplit(SplitContainer::Above); + break; + case Split::Action::SelectSplitBelow: + this->selectNextSplit(SplitContainer::Below); + break; + } + }); + + conns.managedConnect(split->insertSplitRequested, [this](int dir, + Split *parent) { + this->insertSplit(new Split(this), static_cast(dir), parent); + }); + this->layout(); } @@ -282,10 +333,7 @@ SplitContainer::Position SplitContainer::releaseSplit(Split *split) this->refreshTab(); - // fourtf: really bad - split->getChannelView().tabHighlightRequested.disconnectAll(); - - split->getChannelView().tabHighlightRequested.disconnectAll(); + this->connectionsPerSplit_.erase(this->connectionsPerSplit_.find(split)); return position; } diff --git a/src/widgets/splits/SplitContainer.hpp b/src/widgets/splits/SplitContainer.hpp index d6bb62f04..bb26e43dc 100644 --- a/src/widgets/splits/SplitContainer.hpp +++ b/src/widgets/splits/SplitContainer.hpp @@ -255,6 +255,9 @@ private: NotebookTab *tab_; std::vector splits_; + std::unordered_map + connectionsPerSplit_; + bool isDragging_ = false; }; diff --git a/src/widgets/splits/SplitInput.cpp b/src/widgets/splits/SplitInput.cpp index c836f698a..fe75ec806 100644 --- a/src/widgets/splits/SplitInput.cpp +++ b/src/widgets/splits/SplitInput.cpp @@ -247,12 +247,8 @@ void SplitInput::installKeyPressedEvent() } if (event->modifiers() == Qt::AltModifier) { - SplitContainer *page = this->split_->getContainer(); - - if (page != nullptr) - { - page->selectNextSplit(SplitContainer::Above); - } + this->split_->actionRequested.invoke( + Split::Action::SelectSplitAbove); } else { @@ -312,49 +308,37 @@ void SplitInput::installKeyPressedEvent() event->modifiers() == Qt::AltModifier) { // h: vim binding for left - SplitContainer *page = this->split_->getContainer(); - event->accept(); + this->split_->actionRequested.invoke( + Split::Action::SelectSplitLeft); - if (page != nullptr) - { - page->selectNextSplit(SplitContainer::Left); - } + event->accept(); } else if (event->key() == Qt::Key_J && event->modifiers() == Qt::AltModifier) { // j: vim binding for down - SplitContainer *page = this->split_->getContainer(); - event->accept(); + this->split_->actionRequested.invoke( + Split::Action::SelectSplitBelow); - if (page != nullptr) - { - page->selectNextSplit(SplitContainer::Below); - } + event->accept(); } else if (event->key() == Qt::Key_K && event->modifiers() == Qt::AltModifier) { // k: vim binding for up - SplitContainer *page = this->split_->getContainer(); - event->accept(); + this->split_->actionRequested.invoke( + Split::Action::SelectSplitAbove); - if (page != nullptr) - { - page->selectNextSplit(SplitContainer::Above); - } + event->accept(); } else if (event->key() == Qt::Key_L && event->modifiers() == Qt::AltModifier) { // l: vim binding for right - SplitContainer *page = this->split_->getContainer(); - event->accept(); + this->split_->actionRequested.invoke( + Split::Action::SelectSplitRight); - if (page != nullptr) - { - page->selectNextSplit(SplitContainer::Right); - } + event->accept(); } else if (event->key() == Qt::Key_Down) { @@ -364,12 +348,8 @@ void SplitInput::installKeyPressedEvent() } if (event->modifiers() == Qt::AltModifier) { - SplitContainer *page = this->split_->getContainer(); - - if (page != nullptr) - { - page->selectNextSplit(SplitContainer::Below); - } + this->split_->actionRequested.invoke( + Split::Action::SelectSplitBelow); } else { @@ -422,24 +402,16 @@ void SplitInput::installKeyPressedEvent() { if (event->modifiers() == Qt::AltModifier) { - SplitContainer *page = this->split_->getContainer(); - - if (page != nullptr) - { - page->selectNextSplit(SplitContainer::Left); - } + this->split_->actionRequested.invoke( + Split::Action::SelectSplitLeft); } } else if (event->key() == Qt::Key_Right) { if (event->modifiers() == Qt::AltModifier) { - SplitContainer *page = this->split_->getContainer(); - - if (page != nullptr) - { - page->selectNextSplit(SplitContainer::Right); - } + this->split_->actionRequested.invoke( + Split::Action::SelectSplitRight); } } else if ((event->key() == Qt::Key_C || diff --git a/src/widgets/splits/SplitOverlay.cpp b/src/widgets/splits/SplitOverlay.cpp index 981601c70..065383982 100644 --- a/src/widgets/splits/SplitOverlay.cpp +++ b/src/widgets/splits/SplitOverlay.cpp @@ -220,18 +220,13 @@ bool SplitOverlay::ButtonEventFilter::eventFilter(QObject *watched, case QEvent::MouseButtonRelease: { if (this->hoveredElement != HoveredElement::SplitMove) { - SplitContainer *container = - this->parent->split_->getContainer(); + auto dir = SplitContainer::Direction( + this->hoveredElement + SplitContainer::Left - SplitLeft); - if (container != nullptr) - { - auto *_split = new Split(container); - auto dir = SplitContainer::Direction(this->hoveredElement + - SplitContainer::Left - - SplitLeft); - container->insertSplit(_split, dir, this->parent->split_); - this->parent->hide(); - } + this->parent->split_->insertSplitRequested.invoke( + static_cast(dir), this->parent->split_); + + this->parent->hide(); } } break;