From 879aad519d31392cd4c554b387ee1cc7b26d2fe8 Mon Sep 17 00:00:00 2001 From: Ernie Pasveer Date: Sun, 13 Feb 2022 10:39:19 -0600 Subject: [PATCH] Add detachable tabs for the log windows. --- src/CMakeLists.txt | 2 + src/QDetachTabWidget.cpp | 187 +++++++++++++++++++++++++++++++++++++++ src/QDetachTabWidget.h | 36 ++++++++ src/SeerGdbWidget.ui | 8 +- 4 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 src/QDetachTabWidget.cpp create mode 100644 src/QDetachTabWidget.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5d5a615..ed74412 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -75,6 +75,7 @@ set(HEADER_FILES QProcessInfoWidget.h QProgressIndicator.h QColorButton.h + QDetachTabWidget.h ) set(SOURCE_FILES @@ -136,6 +137,7 @@ set(SOURCE_FILES QProcessInfoWidget.cpp QProgressIndicator.cpp QColorButton.cpp + QDetachTabWidget.cpp ) qt5_add_resources(SOURCE_FILES resource.qrc) diff --git a/src/QDetachTabWidget.cpp b/src/QDetachTabWidget.cpp new file mode 100644 index 0000000..90eeba4 --- /dev/null +++ b/src/QDetachTabWidget.cpp @@ -0,0 +1,187 @@ +#include "QDetachTabWidget.h" +#include +#include +#include +#include +#include + +QDetachTabWidget::QDetachTabWidget(QWidget* parent) : QTabWidget(parent) { + + QObject::connect(tabBar(), &QTabBar::tabBarDoubleClicked, this, &QDetachTabWidget::handleShowContextMenu); + QObject::connect(this, &QTabWidget::tabCloseRequested, this, &QDetachTabWidget::handleTabClosedRequested); +} + +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(int tabIndex) { + + QMenu menu("Window Action", this); + + QAction* detachAction = menu.addAction(tr("Detach")); + QAction* reattachAction = menu.addAction(tr("Reattach")); + + QAction* action = menu.exec(QCursor::pos()); + + // + // Handle detaching a tab. + // + if (action == detachAction) { + + // Get the tab the user selected. + QWidget* w = widget(tabIndex); + + // It can't be a 'placeholder' as it was already detached. + if (w->objectName() == "QDetachTabWidgetPlaceholder") { + return; + } + + // Create a blank 'placeholder' tab. + QWidget* placeholder = new QWidget; + placeholder->setObjectName("QDetachTabWidgetPlaceholder"); + + // 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->show(); + + // Set the tabwidget to the placeholder tab. + setCurrentIndex(tabIndex); + } + + // + // Handle reattaching a tab. + // + if (action == reattachAction) { + + // Get the tab the user selected to re-attach. + QWidget* w = widget(tabIndex); + + // 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); + + // Set the tabwidget to the real tab. + setCurrentIndex(tabIndex); + + // Delete the entry from the tab cache. + _tabInfo.erase(it); + } +} + +// +// 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) { + + // Get the widget to be closed. + QWidget* w = widget(tabIndex); + + //qDebug() << "QDetachTabWidget::handleTabClosedRequested:" << tabIndex << w->objectName(); + + // If the widget is a 'placeholder', reattach the real widget and delete the 'placeholder'. + if (w->objectName() == "QDetachTabWidgetPlaceholder") { + + //qDebug() << "Reattaching detached tab."; + + // 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); + + // Set the tabwidget to the real tab. + setCurrentIndex(tabIndex); + + // Delete the entry from the tab cache. + _tabInfo.erase(it); + } +} + +// +// 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; +} + diff --git a/src/QDetachTabWidget.h b/src/QDetachTabWidget.h new file mode 100644 index 0000000..af24909 --- /dev/null +++ b/src/QDetachTabWidget.h @@ -0,0 +1,36 @@ +#pragma once +#include +#include +#include + +struct QDetachTabInfo { + + QDetachTabInfo(QString title, QWidget* widget, QWidget* placeholderWidget) : _title(title), _widget(widget), _placeholderWidget(placeholderWidget) {} + + QWidget* _widget; + QWidget* _placeholderWidget; + QString _title; +}; + + +class QDetachTabWidget : public QTabWidget { + + Q_OBJECT + + public: + QDetachTabWidget(QWidget* parent = 0); + + protected: + void closeEvent (QCloseEvent* e); + + protected slots: + void handleShowContextMenu (int tabIndex); + void handleTabClosedRequested (int tabIndex); + + private: + QList::iterator findWidget (QWidget* widget); + QList::iterator findPlaceholderWidget (QWidget* widget); + + QList _tabInfo; +}; + diff --git a/src/SeerGdbWidget.ui b/src/SeerGdbWidget.ui index 3f7df3b..09b7a35 100644 --- a/src/SeerGdbWidget.ui +++ b/src/SeerGdbWidget.ui @@ -80,7 +80,7 @@ - + 0 @@ -154,6 +154,12 @@
SeerVariableManagerWidget.h
1 + + QDetachTabWidget + QTabWidget +
QDetachTabWidget.h
+ 1 +