// SPDX-FileCopyrightText: 2021 Ernie Pasveer // // SPDX-License-Identifier: MIT #include "QDetachTabWidget.h" #include #include #include #include #include #include #include #include QDetachTabWidget::QDetachTabWidget(QWidget* parent) : QTabWidget(parent) { tabBar()->setContextMenuPolicy(Qt::CustomContextMenu); QObject::connect(tabBar(), &QTabBar::customContextMenuRequested, this, &QDetachTabWidget::handleShowContextMenu); QObject::connect(this, &QTabWidget::tabCloseRequested, this, &QDetachTabWidget::handleTabClosedRequested); } bool QDetachTabWidget::isDetached (int tabIndex) const { // Get the tab the user selected. QWidget* w = widget(tabIndex); if (w == nullptr) { return false; } // If it's the 'placeholder' it is detached. if (w->objectName() == "QDetachTabWidgetPlaceholder") { return true; } // Otherwise, it's still attached. return false; } QWidget* QDetachTabWidget::tabWidget (int tabIndex) const { if (tabIndex < 0 || tabIndex > _tabInfo.size()) { return nullptr; } return _tabInfo[tabIndex]._widget; } void QDetachTabWidget::detachTab (int tabIndex, bool minimized) { // Get the tab the user selected. QWidget* w = widget(tabIndex); if (w == nullptr) { return; } // It can't be a 'placeholder' as it was already detached. if (w->objectName() == "QDetachTabWidgetPlaceholder") { return; } // Create a blank 'placeholder' tab. QDetachTabWidgetPlaceholder* placeholder = new QDetachTabWidgetPlaceholder(tabIndex); // Create an entry for the tab cache. Remember the real tab and the 'placeholder' tab. Plus the tab title. QDetachTabInfo tabinfo(tabText(tabIndex), w, placeholder); _tabInfo.append(tabinfo); // Remove the real tab. removeTab(tabIndex); // Insert the 'placeholder' in the same position. insertTab(tabIndex, placeholder, tabinfo._title); // Tweak the detached tab to make sure it can't be destroyed as it is now a normal window. w->setParent(0); Qt::WindowFlags flags = w->windowFlags(); Qt::WindowFlags closeFlag = Qt::WindowCloseButtonHint; flags = flags & (~closeFlag); w->setWindowFlags(flags); w->setWindowTitle(tabinfo._title); w->setWindowIcon(windowIcon()); if (minimized) { w->showMinimized(); }else{ w->showNormal(); } // Connect the placeholder's 'reattach' signal to the slot. QObject::connect(placeholder, &QDetachTabWidgetPlaceholder::reattach, this, &QDetachTabWidget::handleTabClosedRequested); // Notify listeners the tab was detached. emit tabDetached(tabIndex); emit tabDetached(w); } void QDetachTabWidget::reattachTab (int tabIndex) { // Get the tab the user selected to re-attach. QWidget* w = widget(tabIndex); if (w == nullptr) { return; } // It must be a 'placeholder' if (w->objectName() != "QDetachTabWidgetPlaceholder") { return; } // Look for the 'placeholder' tab in the tab cache. // Note, we do a scan of the cache because a QTabWidget can have isMovable() enabled. // Using 'tabindex' is not always reliable. QList::iterator it = findPlaceholderWidget(w); if (it == _tabInfo.end()) { return; } // Remove the 'placeholder' tab. removeTab(tabIndex); delete w; // Insert the real tab in the same position. insertTab(tabIndex, it->_widget, it->_title); // Save the real tab's widget before we delete the entry. w = it->_widget; // Delete the entry from the tab cache. _tabInfo.erase(it); // Notify listeners the tab was reattached. emit tabReattached(tabIndex); emit tabReattached(w); } void QDetachTabWidget::closeEvent (QCloseEvent* e) { auto it = _tabInfo.begin(); while (it != _tabInfo.end()) { delete it->_widget; it = _tabInfo.erase(it); // Erase current tab and point to the next one. } QTabWidget::closeEvent(e); } void QDetachTabWidget::handleShowContextMenu (const QPoint& point) { // Don't do anything. if (point.isNull()) { return; } // Get the tab index at the RMB click point. int tabIndex = tabBar()->tabAt(point); // Create the menu. QMenu menu("Window Action", this); QAction* detachAction = menu.addAction(tr("Detach")); QAction* detachMinimizedAction = menu.addAction(tr("Detach Minimized")); QAction* reattachAction = menu.addAction(tr("Reattach")); // Enable/disable depending if it was already detached. QWidget* w = widget(tabIndex); if (w->objectName() == "QDetachTabWidgetPlaceholder") { detachAction->setEnabled(false); detachMinimizedAction->setEnabled(false); reattachAction->setEnabled(true); }else{ detachAction->setEnabled(true); detachMinimizedAction->setEnabled(true); reattachAction->setEnabled(false); } // Exec the menue QAction* action = menu.exec(QCursor::pos()); // // Handle detaching a tab. // if (action == detachAction) { // Detach the tab. detachTab(tabIndex, false); // Set the tabwidget to the placeholder tab. setCurrentIndex(tabIndex); } // // Handle detaching a tab. // if (action == detachMinimizedAction) { // Detach the tab. detachTab(tabIndex, true); // Set the tabwidget to the placeholder tab. setCurrentIndex(tabIndex); } // // Handle reattaching a tab. // if (action == reattachAction) { // Reattach the tab. reattachTab(tabIndex); // Set the tabwidget to the real tab. setCurrentIndex(tabIndex); } } // // The QDetachTabWidget listens for 'tabCloseRequests'. Only to // reattach the widget, if necessary. Normally, you derive from // QDetachTabWidget and add signal/connect to do the same. I'm // counting on mine happening first. // void QDetachTabWidget::handleTabClosedRequested (int tabIndex) { // Reattach the tab. reattachTab(tabIndex); // Set the tabwidget to the real tab. setCurrentIndex(tabIndex); } // // Look for the real widget in the tab cache. // QList::iterator QDetachTabWidget::findWidget (QWidget* widget) { QList::iterator it = _tabInfo.begin(); while (it != _tabInfo.end()) { if (it->_widget == widget) { return it; } it++; } return it; } // // Look for the 'placeholder' widget in the tab cache. // QList::iterator QDetachTabWidget::findPlaceholderWidget (QWidget* widget) { QList::iterator it = _tabInfo.begin(); while (it != _tabInfo.end()) { if (it->_placeholderWidget == widget) { return it; } it++; } return it; } // // A placeholder widget that has a 'reattach' pushbutton. // QDetachTabWidgetPlaceholder::QDetachTabWidgetPlaceholder(int tabIndex, QWidget* parent) : QWidget(parent) { // This the placeholder's object name. Users of this object can focus // on this name if they need too. setObjectName("QDetachTabWidgetPlaceholder"); // Create the 'Reattach' button and add a layout to it. _reattachPushButton = new QPushButton("Reattach", this); _reattachPushButton->setIcon(QIcon(":/qt-project.org/styles/commonstyle/images/right-16.png")); _tabIndex = tabIndex; QHBoxLayout* layout = new QHBoxLayout; layout->addStretch(); layout->addWidget(_reattachPushButton); layout->addStretch(); setLayout(layout); // We'll handle the 'click' signal. QObject::connect(_reattachPushButton, &QPushButton::clicked, this, &QDetachTabWidgetPlaceholder::handlePushButtonClicked); } // Handle the pushbutton 'click' by emitting the 'reattach' signal. void QDetachTabWidgetPlaceholder::handlePushButtonClicked () { emit reattach(_tabIndex); }