From d1c67c971e8d81fbfa1b9a25078c0614e344581c Mon Sep 17 00:00:00 2001 From: Jon Beniston Date: Tue, 20 Dec 2022 16:10:11 +0000 Subject: [PATCH] Add buttons to stack MDI windows vertically and put in tabs. Use right click to auto-stack sub-windows, rather than having a dedicated button. Allow maximize button to make window full screen, if already maximized. Add title to device windows, for when displayed in tabs. Add menu button to workspace toolbar, for Android only, to avoid having menu bar, which takes up a lot of space. Add configuration presets button to workspace toolbar. Add icons for window arangement --- sdrbase/settings/configuration.cpp | 16 ++ sdrbase/settings/configuration.h | 3 + sdrgui/channel/channelgui.cpp | 62 ++++- sdrgui/channel/channelgui.h | 1 + sdrgui/device/devicegui.cpp | 3 +- sdrgui/device/devicegui.h | 5 +- sdrgui/feature/featuregui.cpp | 57 ++++- sdrgui/feature/featuregui.h | 1 + sdrgui/gui/workspace.cpp | 368 +++++++++++++++++++++++++---- sdrgui/gui/workspace.h | 26 +- 10 files changed, 464 insertions(+), 78 deletions(-) diff --git a/sdrbase/settings/configuration.cpp b/sdrbase/settings/configuration.cpp index e73ef2e6a..c5754fe28 100644 --- a/sdrbase/settings/configuration.cpp +++ b/sdrbase/settings/configuration.cpp @@ -65,6 +65,13 @@ QByteArray Configuration::serialize() const s.writeBool(301 + i, m_workspaceAutoStackOptions[i]); } + nitems = m_workspaceTabSubWindowsOptions.size() < 99 ? m_workspaceTabSubWindowsOptions.size() : 99; + s.writeS32(400, nitems); + + for (int i = 0; i < nitems; i++) { + s.writeBool(401 + i, m_workspaceTabSubWindowsOptions[i]); + } + return s.final(); } @@ -113,6 +120,14 @@ bool Configuration::deserialize(const QByteArray& data) d.readBool(301 + i, &m_workspaceAutoStackOptions.back()); } + d.readS32(400, &nitems, 0); + + for (int i = 0; i < nitems; i++) + { + m_workspaceTabSubWindowsOptions.push_back(true); + d.readBool(401 + i, &m_workspaceTabSubWindowsOptions.back()); + } + return true; } else @@ -128,4 +143,5 @@ void Configuration::clearData() m_featureSetPreset.clearFeatures(); m_workspaceGeometries.clear(); m_workspaceAutoStackOptions.clear(); + m_workspaceTabSubWindowsOptions.clear(); } diff --git a/sdrbase/settings/configuration.h b/sdrbase/settings/configuration.h index 746d4360a..a6b126ecd 100644 --- a/sdrbase/settings/configuration.h +++ b/sdrbase/settings/configuration.h @@ -52,6 +52,8 @@ public: const QList& getWorkspaceGeometries() const { return m_workspaceGeometries; } QList& getWorkspaceAutoStackOptions() { return m_workspaceAutoStackOptions; } const QList& getWorkspaceAutoStackOptions() const { return m_workspaceAutoStackOptions; } + QList& getWorkspaceTabSubWindowsOptions() { return m_workspaceTabSubWindowsOptions; } + const QList& getWorkspaceTabSubWindowsOptions() const { return m_workspaceTabSubWindowsOptions; } FeatureSetPreset& getFeatureSetPreset() { return m_featureSetPreset; } const FeatureSetPreset& getFeatureSetPreset() const { return m_featureSetPreset; } QList& getDeviceSetPresets() { return m_deviceSetPresets; } @@ -76,6 +78,7 @@ private: QString m_description; QList m_workspaceGeometries; QList m_workspaceAutoStackOptions; + QList m_workspaceTabSubWindowsOptions; FeatureSetPreset m_featureSetPreset; QList m_deviceSetPresets; }; diff --git a/sdrgui/channel/channelgui.cpp b/sdrgui/channel/channelgui.cpp index 34a51aa27..9d4ebccb3 100644 --- a/sdrgui/channel/channelgui.cpp +++ b/sdrgui/channel/channelgui.cpp @@ -26,11 +26,13 @@ #include #include #include +#include #include "mainwindow.h" #include "gui/workspaceselectiondialog.h" #include "gui/devicesetselectiondialog.h" #include "gui/rollupcontents.h" +#include "gui/dialogpositioner.h" #include "channelgui.h" @@ -42,7 +44,8 @@ ChannelGUI::ChannelGUI(QWidget *parent) : m_contextMenuType(ContextMenuNone), m_drag(false), m_resizer(this), - m_disableResize(false) + m_disableResize(false), + m_mdi(nullptr) { qDebug("ChannelGUI::ChannelGUI"); setWindowFlags(windowFlags() | Qt::FramelessWindowHint); @@ -92,7 +95,7 @@ ChannelGUI::ChannelGUI(QWidget *parent) : m_maximizeButton->setFixedSize(20, 20); QIcon maximizeIcon(":/maximize.png"); m_maximizeButton->setIcon(maximizeIcon); - m_maximizeButton->setToolTip("Adjust window to maximum size"); + m_maximizeButton->setToolTip("Adjust window to maximum size in workspace"); m_hideButton = new QPushButton(); m_hideButton->setFixedSize(20, 20); @@ -311,7 +314,9 @@ void ChannelGUI::onWidgetRolled(QWidget *widget, bool show) // onWidgetRolled being called twice. // We need to make sure we don't save widget heights while this occurs. The // window manager will take care of maximizing/restoring the window size. - if (!m_disableResize) + // We do need to resize when a widget is rolled up, but we also need to avoid + // resizing when a window is maximized when first shown in tabbed layout + if (!m_disableResize && !isMaximized()) { if (show) { @@ -379,6 +384,9 @@ void ChannelGUI::sizeToContents() size.setHeight(size.height() + getAdditionalHeight()); size.setWidth(size.width() + m_resizer.m_gripSize * 2); setMinimumSize(size); + + // Restrict size of window to size of desktop + DialogPositioner::sizeToDesktop(this); } void ChannelGUI::duplicateChannel() @@ -398,24 +406,54 @@ void ChannelGUI::openMoveToDeviceSetDialog() void ChannelGUI::maximizeWindow() { - m_disableResize = true; - showMaximized(); - m_disableResize = false; - // QOpenGLWidget widgets don't always paint properly first time after being maximized, - // so force an update. Should really fix why they aren't painted properly in the first place - QList widgets = findChildren(); - for (auto widget : widgets) { - widget->update(); + // If maximize is pressed when maximized, go full screen + if (isMaximized()) + { + m_mdi = mdiArea(); + if (m_mdi) { + m_mdi->removeSubWindow(this); + } + showNormal(); // If we don't go back to normal first, window doesn't get bigger + showFullScreen(); + m_shrinkButton->setToolTip("Adjust window to maximum size in workspace"); + } + else + { + m_disableResize = true; + showMaximized(); + m_shrinkButton->setToolTip("Restore window to normal"); + m_maximizeButton->setToolTip("Make window full screen"); + m_disableResize = false; + // QOpenGLWidget widgets don't always paint properly first time after being maximized, + // so force an update. Should really fix why they aren't painted properly in the first place + QList widgets = findChildren(); + for (auto widget : widgets) { + widget->update(); + } } } void ChannelGUI::shrinkWindow() { qDebug("ChannelGUI::shrinkWindow"); - if (isMaximized()) + // If m_normalParentWidget, window was made full screen + if (m_mdi) { m_disableResize = true; showNormal(); + m_mdi->addSubWindow(this); + show(); + showMaximized(); + m_shrinkButton->setToolTip("Restore window to normal"); + m_disableResize = false; + m_mdi = nullptr; + } + else if (isMaximized()) + { + m_disableResize = true; + showNormal(); + m_shrinkButton->setToolTip("Adjust window to minimum size"); + m_maximizeButton->setToolTip("Adjust window to maximum size in workspace"); m_disableResize = false; } else diff --git a/sdrgui/channel/channelgui.h b/sdrgui/channel/channelgui.h index 1e482d36a..eedc0ed3e 100644 --- a/sdrgui/channel/channelgui.h +++ b/sdrgui/channel/channelgui.h @@ -141,6 +141,7 @@ private: QMap m_heightsMap; FramelessWindowResizer m_resizer; bool m_disableResize; + QMdiArea *m_mdi; // Saved pointer to MDI when in full screen mode private slots: void activateSettingsDialog(); diff --git a/sdrgui/device/devicegui.cpp b/sdrgui/device/devicegui.cpp index 0d5308a77..5d8a06df5 100644 --- a/sdrgui/device/devicegui.cpp +++ b/sdrgui/device/devicegui.cpp @@ -162,7 +162,7 @@ DeviceGUI::DeviceGUI(QWidget *parent) : m_topLayout->addWidget(m_maximizeButton); m_topLayout->addWidget(m_closeButton); - m_centerLayout = new QHBoxLayout(); + m_centerLayout = new QVBoxLayout(); m_centerLayout->setContentsMargins(0, 0, 0, 0); m_contents = new QWidget(); // Do not delete! Done in child's destructor with "delete ui" m_centerLayout->addWidget(m_contents); @@ -412,6 +412,7 @@ void DeviceGUI::deviceSetPresetsDialog() void DeviceGUI::setTitle(const QString& title) { + setWindowTitle(title + " Device"); m_titleLabel->setText(title); } diff --git a/sdrgui/device/devicegui.h b/sdrgui/device/devicegui.h index bd272b06a..1a392851d 100644 --- a/sdrgui/device/devicegui.h +++ b/sdrgui/device/devicegui.h @@ -24,6 +24,7 @@ #include #include #include +#include #include "gui/channeladddialog.h" #include "gui/framelesswindowresizer.h" @@ -32,7 +33,6 @@ class QCloseEvent; class Message; class MessageQueue; -class QLabel; class QPushButton; class QVBoxLayout; class QHBoxLayout; @@ -87,6 +87,7 @@ protected: void mouseMoveEvent(QMouseEvent* event) override; void resetContextMenuType() { m_contextMenuType = ContextMenuNone; } int getAdditionalHeight() const { return 22 + 22; } + void setStatus(const QString &status) { m_statusLabel->setText(status); } DeviceUISet* m_deviceUISet; DeviceType m_deviceType; @@ -122,7 +123,7 @@ private: QLabel *m_statusLabel; QVBoxLayout *m_layouts; QHBoxLayout *m_topLayout; - QHBoxLayout *m_centerLayout; + QVBoxLayout *m_centerLayout; QHBoxLayout *m_bottomLayout; QSizeGrip *m_sizeGripBottomRight; bool m_drag; diff --git a/sdrgui/feature/featuregui.cpp b/sdrgui/feature/featuregui.cpp index e00c84cda..55c31019d 100644 --- a/sdrgui/feature/featuregui.cpp +++ b/sdrgui/feature/featuregui.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "mainwindow.h" #include "gui/workspaceselectiondialog.h" @@ -37,7 +38,8 @@ FeatureGUI::FeatureGUI(QWidget *parent) : m_contextMenuType(ContextMenuNone), m_drag(false), m_resizer(this), - m_disableResize(false) + m_disableResize(false), + m_mdi(nullptr) { qDebug("FeatureGUI::FeatureGUI"); setWindowFlags(windowFlags() | Qt::FramelessWindowHint); @@ -87,7 +89,7 @@ FeatureGUI::FeatureGUI(QWidget *parent) : m_maximizeButton->setFixedSize(20, 20); QIcon maximizeIcon(":/maximize.png"); m_maximizeButton->setIcon(maximizeIcon); - m_maximizeButton->setToolTip("Adjust window to maximum size"); + m_maximizeButton->setToolTip("Adjust window to maximum size in workspace"); m_closeButton = new QPushButton(); m_closeButton->setFixedSize(20, 20); @@ -264,7 +266,9 @@ void FeatureGUI::onWidgetRolled(QWidget *widget, bool show) // onWidgetRolled being called twice. // We need to make sure we don't save widget heights while this occurs. The // window manager will take care of maximizing/restoring the window size. - if (!m_disableResize) + // We do need to resize when a widget is rolled up, but we also need to avoid + // resizing when a window is maximized when first shown in tabbed layout + if (!m_disableResize && !isMaximized()) { if (show) { @@ -336,24 +340,53 @@ void FeatureGUI::sizeToContents() void FeatureGUI::maximizeWindow() { - m_disableResize = true; - showMaximized(); - m_disableResize = false; - // QOpenGLWidget widgets don't always paint properly first time after being maximized, - // so force an update. Should really fix why they aren't painted properly in the first place - QList widgets = findChildren(); - for (auto widget : widgets) { - widget->update(); + // If maximize is pressed when maximized, go full screen + if (isMaximized()) + { + m_mdi = mdiArea(); + if (m_mdi) { + m_mdi->removeSubWindow(this); + } + showNormal(); // If we don't go back to normal first, window doesn't get bigger + showFullScreen(); + m_shrinkButton->setToolTip("Adjust window to maximum size in workspace"); + } + else + { + m_disableResize = true; + showMaximized(); + m_shrinkButton->setToolTip("Restore window to normal"); + m_maximizeButton->setToolTip("Make window full screen"); + m_disableResize = false; + // QOpenGLWidget widgets don't always paint properly first time after being maximized, + // so force an update. Should really fix why they aren't painted properly in the first place + QList widgets = findChildren(); + for (auto widget : widgets) { + widget->update(); + } } } void FeatureGUI::shrinkWindow() { qDebug("FeatureGUI::shrinkWindow"); - if (isMaximized()) + if (m_mdi) { m_disableResize = true; showNormal(); + m_mdi->addSubWindow(this); + show(); + showMaximized(); + m_shrinkButton->setToolTip("Restore window to normal"); + m_disableResize = false; + m_mdi = nullptr; + } + else if (isMaximized()) + { + m_disableResize = true; + showNormal(); + m_shrinkButton->setToolTip("Adjust window to minimum size"); + m_maximizeButton->setToolTip("Adjust window to maximum size in workspace"); m_disableResize = false; } else diff --git a/sdrgui/feature/featuregui.h b/sdrgui/feature/featuregui.h index 09ea2f83d..bc53e6079 100644 --- a/sdrgui/feature/featuregui.h +++ b/sdrgui/feature/featuregui.h @@ -110,6 +110,7 @@ private: QMap m_heightsMap; FramelessWindowResizer m_resizer; bool m_disableResize; + QMdiArea *m_mdi; // Saved pointer to MDI when in full screen mode private slots: void activateSettingsDialog(); diff --git a/sdrgui/gui/workspace.cpp b/sdrgui/gui/workspace.cpp index 45d7ea366..e0e9cc8be 100644 --- a/sdrgui/gui/workspace.cpp +++ b/sdrgui/gui/workspace.cpp @@ -27,10 +27,13 @@ #include #include #include +#include +#include #include "gui/samplingdevicedialog.h" #include "gui/rollupcontents.h" #include "gui/buttonswitch.h" +#include "gui/crightclickenabler.h" #include "channel/channelgui.h" #include "feature/featuregui.h" #include "device/devicegui.h" @@ -42,8 +45,10 @@ Workspace::Workspace(int index, QWidget *parent, Qt::WindowFlags flags) : QDockWidget(parent, flags), m_index(index), + m_menuButton(nullptr), m_featureAddDialog(this), m_stacking(false), + m_autoStack(false), m_userChannelMinWidth(0) { m_mdi = new QMdiArea(this); @@ -64,6 +69,29 @@ Workspace::Workspace(int index, QWidget *parent, Qt::WindowFlags flags) : m_titleLabel->setStyleSheet("QLabel { background-color: rgb(128, 128, 128); qproperty-alignment: AlignCenter; }"); m_titleLabel->setText(windowTitle()); +#ifdef ANDROID + m_menuButton = new QToolButton(); + QIcon menuIcon(":/listing.png"); + m_menuButton->setIcon(menuIcon); + m_menuButton->setFixedSize(20, 20); + m_menuButton->setPopupMode(QToolButton::InstantPopup); +#endif + + m_configurationPresetsButton = new QPushButton(); + QIcon configurationPresetsIcon(":/star.png"); + m_configurationPresetsButton->setIcon(configurationPresetsIcon); + m_configurationPresetsButton->setToolTip("Configuration presets"); + m_configurationPresetsButton->setFixedSize(20, 20); + + m_startStopButton = new ButtonSwitch(); + m_startStopButton->setCheckable(true); + updateStartStopButton(false); + m_startStopButton->setFixedSize(20, 20); + + m_vline1 = new QFrame(); + m_vline1->setFrameShape(QFrame::VLine); + m_vline1->setFrameShadow(QFrame::Sunken); + m_addRxDeviceButton = new QPushButton(); QIcon addRxIcon(":/rx.png"); m_addRxDeviceButton->setIcon(addRxIcon); @@ -82,14 +110,9 @@ Workspace::Workspace(int index, QWidget *parent, Qt::WindowFlags flags) : m_addMIMODeviceButton->setToolTip("Add MIMO device"); m_addMIMODeviceButton->setFixedSize(20, 20); - m_startStopButton = new ButtonSwitch(); - m_startStopButton->setCheckable(true); - updateStartStopButton(false); - m_startStopButton->setFixedSize(20, 20); - - m_vline1 = new QFrame(); - m_vline1->setFrameShape(QFrame::VLine); - m_vline1->setFrameShadow(QFrame::Sunken); + m_vline2 = new QFrame(); + m_vline2->setFrameShape(QFrame::VLine); + m_vline2->setFrameShadow(QFrame::Sunken); m_addFeatureButton = new QPushButton(); QIcon addFeatureIcon(":/tool_add.png"); @@ -103,9 +126,9 @@ Workspace::Workspace(int index, QWidget *parent, Qt::WindowFlags flags) : m_featurePresetsButton->setToolTip("Feature presets"); m_featurePresetsButton->setFixedSize(20, 20); - m_vline2 = new QFrame(); - m_vline2->setFrameShape(QFrame::VLine); - m_vline2->setFrameShadow(QFrame::Sunken); + m_vline3 = new QFrame(); + m_vline3->setFrameShape(QFrame::VLine); + m_vline3->setFrameShadow(QFrame::Sunken); m_cascadeSubWindows = new QPushButton(); QIcon cascadeSubWindowsIcon(":/cascade.png"); @@ -119,19 +142,26 @@ Workspace::Workspace(int index, QWidget *parent, Qt::WindowFlags flags) : m_tileSubWindows->setToolTip("Tile sub windows"); m_tileSubWindows->setFixedSize(20, 20); - m_stackSubWindows = new QPushButton("S"); - //QIcon stackSubWindowsIcon(":/stack.png"); // FIXME - //m_stackSubWindows->setIcon(stackSubWindowsIcon); - m_stackSubWindows->setToolTip("Stack sub windows"); - m_stackSubWindows->setFixedSize(20, 20); + m_stackVerticalSubWindows = new QPushButton(); + QIcon stackVerticalSubWindowsIcon(":/stackvertical.png"); + m_stackVerticalSubWindows->setIcon(stackVerticalSubWindowsIcon); + m_stackVerticalSubWindows->setToolTip("Stack sub windows vertically"); + m_stackVerticalSubWindows->setFixedSize(20, 20); - m_autoStackSubWindows = new ButtonSwitch(); - m_autoStackSubWindows->setText("AS"); - m_autoStackSubWindows->setCheckable(true); - //QIcon autoStackSubWindowsIcon(":/autostack.png"); // FIXME - //m_autoStackSubWindows->setIcon(autoStackSubWindowsIcon); - m_autoStackSubWindows->setToolTip("Automatically stack sub windows"); - m_autoStackSubWindows->setFixedSize(20, 20); + m_stackSubWindows = new QPushButton(); + QIcon stackSubWindowsIcon(":/stackcolumns.png"); + m_stackSubWindows->setIcon(stackSubWindowsIcon); + m_stackSubWindows->setToolTip("Stack sub windows in columns. Right click to stack automatically."); + m_stackSubWindows->setFixedSize(20, 20); + CRightClickEnabler *stackSubWindowsRightClickEnabler = new CRightClickEnabler(m_stackSubWindows); + connect(stackSubWindowsRightClickEnabler, &CRightClickEnabler::rightClick, this, &Workspace::autoStackSubWindows); + + m_tabSubWindows = new ButtonSwitch(); + QIcon tabSubWindowsIcon(":/tab.png"); + m_tabSubWindows->setIcon(tabSubWindowsIcon); + m_tabSubWindows->setCheckable(true); + m_tabSubWindows->setToolTip("Display sub windows in tabs"); + m_tabSubWindows->setFixedSize(20, 20); m_normalButton = new QPushButton(); QIcon normalIcon(":/dock.png"); @@ -146,21 +176,34 @@ Workspace::Workspace(int index, QWidget *parent, Qt::WindowFlags flags) : m_closeButton->setFixedSize(20, 20); m_titleBarLayout->addWidget(m_titleLabel); + if (m_menuButton) { + m_titleBarLayout->addWidget(m_menuButton); + } + m_titleBarLayout->addWidget(m_configurationPresetsButton); + m_titleBarLayout->addWidget(m_startStopButton); + m_titleBarLayout->addWidget(m_vline1); m_titleBarLayout->addWidget(m_addRxDeviceButton); m_titleBarLayout->addWidget(m_addTxDeviceButton); m_titleBarLayout->addWidget(m_addMIMODeviceButton); - m_titleBarLayout->addWidget(m_startStopButton); - m_titleBarLayout->addWidget(m_vline1); + m_titleBarLayout->addWidget(m_vline2); m_titleBarLayout->addWidget(m_addFeatureButton); m_titleBarLayout->addWidget(m_featurePresetsButton); - m_titleBarLayout->addWidget(m_vline2); + m_titleBarLayout->addWidget(m_vline3); m_titleBarLayout->addWidget(m_cascadeSubWindows); m_titleBarLayout->addWidget(m_tileSubWindows); + m_titleBarLayout->addWidget(m_stackVerticalSubWindows); m_titleBarLayout->addWidget(m_stackSubWindows); - m_titleBarLayout->addWidget(m_autoStackSubWindows); + m_titleBarLayout->addWidget(m_tabSubWindows); m_titleBarLayout->addStretch(1); +#ifndef ANDROID + // Can't undock on Android, as windows don't have title bars to allow them to be moved m_titleBarLayout->addWidget(m_normalButton); + // Don't allow workspaces to be hidden on Android, as if all are hidden, they'll + // be no way to redisplay them, as we currently don't have a main menu bar m_titleBarLayout->addWidget(m_closeButton); +#else + setFeatures(QDockWidget::NoDockWidgetFeatures); +#endif setTitleBarWidget(m_titleBar); QObject::connect( @@ -198,6 +241,13 @@ Workspace::Workspace(int index, QWidget *parent, Qt::WindowFlags flags) : &Workspace::featurePresetsDialog ); + QObject::connect( + m_configurationPresetsButton, + &QPushButton::clicked, + this, + &Workspace::configurationPresetsDialog + ); + QObject::connect( m_cascadeSubWindows, &QPushButton::clicked, @@ -212,6 +262,13 @@ Workspace::Workspace(int index, QWidget *parent, Qt::WindowFlags flags) : &Workspace::tileSubWindows ); + QObject::connect( + m_stackVerticalSubWindows, + &QPushButton::clicked, + this, + &Workspace::stackVerticalSubWindows + ); + QObject::connect( m_stackSubWindows, &QPushButton::clicked, @@ -227,10 +284,10 @@ Workspace::Workspace(int index, QWidget *parent, Qt::WindowFlags flags) : ); QObject::connect( - m_autoStackSubWindows, + m_tabSubWindows, &QPushButton::clicked, this, - &Workspace::autoStackSubWindows + &Workspace::tabSubWindows ); QObject::connect( @@ -256,6 +313,17 @@ Workspace::Workspace(int index, QWidget *parent, Qt::WindowFlags flags) : &Workspace::deviceStateChanged ); + QObject::connect( + m_mdi, + &QMdiArea::subWindowActivated, + this, + &Workspace::subWindowActivated + ); + +#ifdef ANDROID + m_tabSubWindows->setChecked(true); + tabSubWindows(); +#endif } Workspace::~Workspace() @@ -263,13 +331,17 @@ Workspace::~Workspace() qDebug("Workspace::~Workspace"); delete m_closeButton; delete m_normalButton; - delete m_autoStackSubWindows; + delete m_tabSubWindows; delete m_stackSubWindows; + delete m_stackVerticalSubWindows; delete m_tileSubWindows; delete m_cascadeSubWindows; + delete m_vline3; delete m_vline2; delete m_vline1; delete m_startStopButton; + delete m_configurationPresetsButton; + delete m_menuButton; delete m_addRxDeviceButton; delete m_addTxDeviceButton; delete m_addMIMODeviceButton; @@ -346,18 +418,126 @@ void Workspace::featurePresetsDialog() emit featurePresetsDialogRequested(p, this); } +void Workspace::configurationPresetsDialog() +{ + emit configurationPresetsDialogRequested(); +} + void Workspace::cascadeSubWindows() { - m_autoStackSubWindows->setChecked(false); + setAutoStackOption(false); + m_tabSubWindows->setChecked(false); + m_mdi->setViewMode(QMdiArea::SubWindowView); m_mdi->cascadeSubWindows(); } void Workspace::tileSubWindows() { - m_autoStackSubWindows->setChecked(false); + setAutoStackOption(false); + m_tabSubWindows->setChecked(false); + m_mdi->setViewMode(QMdiArea::SubWindowView); m_mdi->tileSubWindows(); } +void Workspace::stackVerticalSubWindows() +{ + setAutoStackOption(false); + unmaximizeSubWindows(); + m_mdi->setViewMode(QMdiArea::SubWindowView); + + // Spacing between windows + const int spacing = 2; + + // Categorise windows according to type and calculate min size needed + QList windows = m_mdi->subWindowList(QMdiArea::CreationOrder); + QList devices; + QList spectrums; + QList channels; + QList features; + int minHeight = 0; + int minWidth = 0; + int nonFixedWindows = 0; + + for (auto window : windows) + { + if (window->isVisible() && !window->isMaximized()) + { + if (window->inherits("DeviceGUI")) { + devices.append(qobject_cast(window)); + } else if (window->inherits("MainSpectrumGUI")) { + spectrums.append(qobject_cast(window)); + } else if (window->inherits("ChannelGUI")) { + channels.append(qobject_cast(window)); + } else if (window->inherits("FeatureGUI")) { + features.append(qobject_cast(window)); + } + minHeight += window->minimumSizeHint().height() + spacing; + minWidth = std::max(minWidth, window->minimumSizeHint().width()); + if (window->sizePolicy().verticalPolicy() != QSizePolicy::Fixed) { + nonFixedWindows++; + } + } + } + + // Order windows by device/feature/channel index + orderByIndex(devices); + orderByIndex(spectrums); + orderByIndex(channels); + orderByIndex(features); + + // Will we need scroll bars? + QSize mdiSize = m_mdi->size(); + bool requiresHScrollBar = minWidth > mdiSize.width(); + bool requiresVScrollBar = minHeight > mdiSize.height(); + + // Reduce available size if scroll bars needed + int sbWidth = qApp->style()->pixelMetric(QStyle::PM_ScrollBarExtent); + if (requiresVScrollBar) { + mdiSize.setWidth(mdiSize.width() - sbWidth); + } + if (requiresHScrollBar) { + mdiSize.setHeight(mdiSize.height() - sbWidth); + } + + // Calculate spare vertical space, to be shared between non-fixed windows + int spareSpacePerWindow; + if (requiresVScrollBar) { + spareSpacePerWindow = 0; + } else { + spareSpacePerWindow = (mdiSize.height() - minHeight) / nonFixedWindows; + } + + // Now position the windows + int x = 0; + int y = 0; + + for (auto window : devices) + { + window->move(x, y); + y += window->size().height() + spacing; + } + for (auto window : spectrums) + { + window->move(x, y); + window->resize(mdiSize.width(), window->minimumSizeHint().height() + spareSpacePerWindow); + y += window->size().height() + spacing; + } + for (auto window : channels) + { + window->move(x, y); + int extra = (window->sizePolicy().verticalPolicy() == QSizePolicy::Fixed) ? 0 : spareSpacePerWindow; + window->resize(mdiSize.width(), window->minimumSizeHint().height() + extra); + y += window->size().height() + spacing; + } + for (auto window : features) + { + window->move(x, y); + int extra = (window->sizePolicy().verticalPolicy() == QSizePolicy::Fixed) ? 0 : spareSpacePerWindow; + window->resize(mdiSize.width(), window->minimumSizeHint().height() + extra); + y += window->size().height() + spacing; + } +} + void Workspace::orderByIndex(QList &list) { std::sort(list.begin(), list.end(), @@ -398,16 +578,36 @@ void Workspace::orderByIndex(QList &list) }); } +void Workspace::unmaximizeSubWindows() +{ + if (m_tabSubWindows->isChecked()) + { + m_tabSubWindows->setChecked(false); + // Unmaximize any maximized windows + QList windows = m_mdi->subWindowList(QMdiArea::CreationOrder); + for (auto window : windows) + { + if (window->isMaximized()) { + window->showNormal(); + } + } + } +} + // Try to arrange windows somewhat like in earlier versions of SDRangel // Devices and fixed size features stacked on left // Spectrum and expandable features stacked in centre // Channels stacked on right void Workspace::stackSubWindows() { + unmaximizeSubWindows(); + // Set a flag so event handler knows if it's this code or the user that // resizes a window m_stacking = true; + m_mdi->setViewMode(QMdiArea::SubWindowView); + // Categorise windows according to type QList windows = m_mdi->subWindowList(QMdiArea::CreationOrder); QList devices; @@ -653,7 +853,54 @@ void Workspace::stackSubWindows() void Workspace::autoStackSubWindows() { - if (m_autoStackSubWindows->isChecked()) { + setAutoStackOption(!m_autoStack); +} + +void Workspace::tabSubWindows() +{ + if (m_tabSubWindows->isChecked()) + { + // Disable autostack + setAutoStackOption(false); + + // Move sub windows out of view, so they can't be seen next to a non-expandible window + // Perhaps there's a better way to do this - showMinimized didn't work + QList windows = m_mdi->subWindowList(QMdiArea::CreationOrder); + for (auto window : windows) + { + if ((window != m_mdi->activeSubWindow()) && ((window->x() != 5000) || (window->y() != 0))) { + window->move(5000, 0); + } + } + + m_mdi->setViewMode(QMdiArea::TabbedView); + } + else + { + m_mdi->setViewMode(QMdiArea::SubWindowView); + } +} + +void Workspace::subWindowActivated(QMdiSubWindow *activatedWindow) +{ + if (activatedWindow && m_tabSubWindows->isChecked()) + { + // Move other windows out of the way + QList windows = m_mdi->subWindowList(QMdiArea::CreationOrder); + for (auto window : windows) + { + if ((window != activatedWindow) && ((window->x() != 5000) || (window->y() != 0))) { + window->move(5000, 0); + } else if ((window == activatedWindow) && ((window->x() != 0) || (window->y() != 0))) { + window->move(0, 0); + } + } + } +} + +void Workspace::layoutSubWindows() +{ + if (m_autoStack) { stackSubWindows(); } } @@ -716,7 +963,7 @@ void Workspace::deviceStateChanged(int index, DeviceAPI *deviceAPI) void Workspace::resizeEvent(QResizeEvent *event) { QDockWidget::resizeEvent(event); - autoStackSubWindows(); + layoutSubWindows(); } void Workspace::addToMdiArea(QMdiSubWindow *sub) @@ -725,17 +972,20 @@ void Workspace::addToMdiArea(QMdiSubWindow *sub) sub->installEventFilter(this); // Can't use Close event, as it's before window is closed, so // catch sub-window destroyed signal instead - connect(sub, &QObject::destroyed, this, &Workspace::autoStackSubWindows); + connect(sub, &QObject::destroyed, this, &Workspace::layoutSubWindows); m_mdi->addSubWindow(sub); sub->show(); - // Auto-stack when sub-window's widgets are rolled up + // Auto-stack when sub-window's widgets are rolled up ChannelGUI *channel = qobject_cast(sub); if (channel) { - connect(channel->getRollupContents(), &RollupContents::widgetRolled, this, &Workspace::autoStackSubWindows); + connect(channel->getRollupContents(), &RollupContents::widgetRolled, this, &Workspace::layoutSubWindows); } FeatureGUI *feature = qobject_cast(sub); if (feature) { - connect(feature->getRollupContents(), &RollupContents::widgetRolled, this, &Workspace::autoStackSubWindows); + connect(feature->getRollupContents(), &RollupContents::widgetRolled, this, &Workspace::layoutSubWindows); + } + if (m_tabSubWindows->isChecked()) { + sub->showMaximized(); } } @@ -743,14 +993,14 @@ void Workspace::removeFromMdiArea(QMdiSubWindow *sub) { m_mdi->removeSubWindow(sub); sub->removeEventFilter(this); - disconnect(sub, &QObject::destroyed, this, &Workspace::autoStackSubWindows); + disconnect(sub, &QObject::destroyed, this, &Workspace::layoutSubWindows); ChannelGUI *channel = qobject_cast(sub); if (channel) { - disconnect(channel->getRollupContents(), &RollupContents::widgetRolled, this, &Workspace::autoStackSubWindows); + disconnect(channel->getRollupContents(), &RollupContents::widgetRolled, this, &Workspace::layoutSubWindows); } FeatureGUI *feature = qobject_cast(sub); if (feature) { - disconnect(feature->getRollupContents(), &RollupContents::widgetRolled, this, &Workspace::autoStackSubWindows); + disconnect(feature->getRollupContents(), &RollupContents::widgetRolled, this, &Workspace::layoutSubWindows); } } @@ -760,19 +1010,19 @@ bool Workspace::eventFilter(QObject *obj, QEvent *event) { QWidget *widget = qobject_cast(obj); if (!widget->isMaximized()) { - autoStackSubWindows(); + layoutSubWindows(); } } else if (event->type() == QEvent::Hide) { QWidget *widget = qobject_cast(obj); if (!widget->isMaximized()) { - autoStackSubWindows(); + layoutSubWindows(); } } else if (event->type() == QEvent::Resize) { - if (!m_stacking && m_autoStackSubWindows->isChecked()) + if (!m_stacking && m_autoStack) { QWidget *widget = qobject_cast(obj); QResizeEvent *resizeEvent = static_cast(event); @@ -812,12 +1062,38 @@ void Workspace::restoreMdiGeometry(const QByteArray& blob) bool Workspace::getAutoStackOption() const { - return m_autoStackSubWindows->isChecked(); + return m_autoStack; } void Workspace::setAutoStackOption(bool autoStack) { - m_autoStackSubWindows->doToggle(autoStack); + m_autoStack = autoStack; + if (!m_autoStack) + { + m_stackSubWindows->setStyleSheet(QString("QPushButton{ background-color: %1; }") + .arg(palette().button().color().name())); + } + else + { + m_stackSubWindows->setStyleSheet(QString("QPushButton{ background-color: %1; }") + .arg(palette().highlight().color().darker(150).name())); + stackSubWindows(); + } +} + +bool Workspace::getTabSubWindowsOption() const +{ + return m_tabSubWindows->isChecked(); +} + +void Workspace::setTabSubWindowsOption(bool tab) +{ + m_tabSubWindows->doToggle(tab); + if (tab) { + tabSubWindows(); + } else { + m_mdi->setViewMode(QMdiArea::SubWindowView); + } } void Workspace::adjustSubWindowsAfterRestore() diff --git a/sdrgui/gui/workspace.h b/sdrgui/gui/workspace.h index ab4d05b6b..282102f48 100644 --- a/sdrgui/gui/workspace.h +++ b/sdrgui/gui/workspace.h @@ -28,6 +28,7 @@ class QHBoxLayout; class QLabel; +class QToolButton; class QPushButton; class QMdiArea; class QMdiSubWindow; @@ -37,7 +38,6 @@ class ChannelGUI; class FeatureGUI; class DeviceGUI; class MainSpectrumGUI; - class SDRGUI_API Workspace : public QDockWidget { Q_OBJECT @@ -56,6 +56,8 @@ public: void restoreMdiGeometry(const QByteArray& blob); bool getAutoStackOption() const; void setAutoStackOption(bool autoStack); + bool getTabSubWindowsOption() const; + void setTabSubWindowsOption(bool tab); QList getSubWindowList() const; void orderByIndex(QList &list); void orderByIndex(QList &list); @@ -63,21 +65,26 @@ public: void orderByIndex(QList &list); void adjustSubWindowsAfterRestore(); void updateStartStopButton(bool checked); + QToolButton *getMenuButton() const { return m_menuButton; } private: int m_index; + QToolButton *m_menuButton; + QPushButton *m_configurationPresetsButton; + ButtonSwitch *m_startStopButton; + QFrame *m_vline1; QPushButton *m_addRxDeviceButton; QPushButton *m_addTxDeviceButton; QPushButton *m_addMIMODeviceButton; - ButtonSwitch *m_startStopButton; - QFrame *m_vline1; + QFrame *m_vline2; QPushButton *m_addFeatureButton; QPushButton *m_featurePresetsButton; - QFrame *m_vline2; + QFrame *m_vline3; QPushButton *m_cascadeSubWindows; QPushButton *m_tileSubWindows; + QPushButton *m_stackVerticalSubWindows; QPushButton *m_stackSubWindows; - ButtonSwitch *m_autoStackSubWindows; + ButtonSwitch *m_tabSubWindows; QWidget *m_titleBar; QHBoxLayout *m_titleBarLayout; QLabel *m_titleLabel; @@ -86,8 +93,11 @@ private: FeatureAddDialog m_featureAddDialog; QMdiArea *m_mdi; bool m_stacking; // Set when stackSubWindows() is running + bool m_autoStack; // Automatically stack int m_userChannelMinWidth; // Minimum width of channels column for stackSubWindows(), set by user resizing a channel window + void unmaximizeSubWindows(); + protected: void resizeEvent(QResizeEvent *event) override; bool eventFilter(QObject *obj, QEvent *event) override; @@ -98,14 +108,19 @@ private slots: void addMIMODeviceClicked(); void addFeatureDialog(); void featurePresetsDialog(); + void configurationPresetsDialog(); void cascadeSubWindows(); void tileSubWindows(); + void stackVerticalSubWindows(); void stackSubWindows(); void autoStackSubWindows(); + void tabSubWindows(); + void layoutSubWindows(); void startStopClicked(bool checked = false); void addFeatureEmitted(int featureIndex); void toggleFloating(); void deviceStateChanged(int index, DeviceAPI *deviceAPI); + void subWindowActivated(QMdiSubWindow *window); signals: void addRxDevice(Workspace *inWorkspace, int deviceIndex); @@ -113,6 +128,7 @@ signals: void addMIMODevice(Workspace *inWorkspace, int deviceIndex); void addFeature(Workspace*, int); void featurePresetsDialogRequested(QPoint, Workspace*); + void configurationPresetsDialogRequested(); void startAllDevices(Workspace *inWorkspace); void stopAllDevices(Workspace *inWorkspace); };