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); };