diff --git a/sdrgui/gui/workspace.cpp b/sdrgui/gui/workspace.cpp
index a9345417e..5c28578ea 100644
--- a/sdrgui/gui/workspace.cpp
+++ b/sdrgui/gui/workspace.cpp
@@ -16,6 +16,8 @@
// along with this program. If not, see . //
///////////////////////////////////////////////////////////////////////////////////
+#include
+
#include
#include
#include
@@ -23,14 +25,23 @@
#include
#include
#include
+#include
+#include
#include "gui/samplingdevicedialog.h"
+#include "gui/rollupcontents.h"
+#include "channel/channelgui.h"
+#include "feature/featuregui.h"
+#include "device/devicegui.h"
+#include "mainspectrum/mainspectrumgui.h"
#include "workspace.h"
Workspace::Workspace(int index, QWidget *parent, Qt::WindowFlags flags) :
QDockWidget(parent, flags),
m_index(index),
- m_featureAddDialog(this)
+ m_featureAddDialog(this),
+ m_stacking(false),
+ m_userChannelMinWidth(0)
{
m_mdi = new QMdiArea(this);
m_mdi->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
@@ -100,6 +111,19 @@ 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_autoStackSubWindows = new QPushButton("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_normalButton = new QPushButton();
QIcon normalIcon(":/dock.png");
m_normalButton->setIcon(normalIcon);
@@ -122,6 +146,8 @@ Workspace::Workspace(int index, QWidget *parent, Qt::WindowFlags flags) :
m_titleBarLayout->addWidget(m_vline2);
m_titleBarLayout->addWidget(m_cascadeSubWindows);
m_titleBarLayout->addWidget(m_tileSubWindows);
+ m_titleBarLayout->addWidget(m_stackSubWindows);
+ m_titleBarLayout->addWidget(m_autoStackSubWindows);
m_titleBarLayout->addStretch(1);
m_titleBarLayout->addWidget(m_normalButton);
m_titleBarLayout->addWidget(m_closeButton);
@@ -176,6 +202,20 @@ Workspace::Workspace(int index, QWidget *parent, Qt::WindowFlags flags) :
&Workspace::tileSubWindows
);
+ QObject::connect(
+ m_stackSubWindows,
+ &QPushButton::clicked,
+ this,
+ &Workspace::stackSubWindows
+ );
+
+ QObject::connect(
+ m_autoStackSubWindows,
+ &QPushButton::clicked,
+ this,
+ &Workspace::autoStackSubWindows
+ );
+
QObject::connect(
m_normalButton,
&QPushButton::clicked,
@@ -191,6 +231,7 @@ Workspace::Workspace(int index, QWidget *parent, Qt::WindowFlags flags) :
this,
&Workspace::addFeatureEmitted
);
+
}
Workspace::~Workspace()
@@ -198,6 +239,8 @@ Workspace::~Workspace()
qDebug("Workspace::~Workspace");
delete m_closeButton;
delete m_normalButton;
+ delete m_autoStackSubWindows;
+ delete m_stackSubWindows;
delete m_tileSubWindows;
delete m_cascadeSubWindows;
delete m_vline2;
@@ -288,15 +331,344 @@ void Workspace::tileSubWindows()
m_mdi->tileSubWindows();
}
+void Workspace::orderByIndex(QList &list)
+{
+ std::sort(list.begin(), list.end(),
+ [](const ChannelGUI *a, const ChannelGUI *b) -> bool
+ {
+ if (a->getDeviceSetIndex() == b->getDeviceSetIndex()) {
+ return a->getIndex() < b->getIndex();
+ } else {
+ return a->getDeviceSetIndex() < b->getDeviceSetIndex();
+ }
+ });
+}
+
+void Workspace::orderByIndex(QList &list)
+{
+ std::sort(list.begin(), list.end(),
+ [](const FeatureGUI *a, const FeatureGUI *b) -> bool
+ {
+ return a->getIndex() < b->getIndex();
+ });
+}
+
+void Workspace::orderByIndex(QList &list)
+{
+ std::sort(list.begin(), list.end(),
+ [](const DeviceGUI *a, const DeviceGUI *b) -> bool
+ {
+ return a->getIndex() < b->getIndex();
+ });
+}
+
+void Workspace::orderByIndex(QList &list)
+{
+ std::sort(list.begin(), list.end(),
+ [](const MainSpectrumGUI *a, const MainSpectrumGUI *b) -> bool
+ {
+ return a->getIndex() < b->getIndex();
+ });
+}
+
+// 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()
+{
+ // Set a flag so event handler knows if it's this code or the user that
+ // resizes a window
+ m_stacking = true;
+
+ // Categorise windows according to type
+ QList windows = m_mdi->subWindowList(QMdiArea::CreationOrder);
+ QList devices;
+ QList spectrums;
+ QList channels;
+ QList fixedFeatures;
+ QList features;
+
+ for (auto window : windows)
+ {
+ if (window->isVisible())
+ {
+ 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")) {
+ if (window->sizePolicy().verticalPolicy() == QSizePolicy::Fixed) { // Test vertical, as horizontal can be adjusted a little bit
+ fixedFeatures.append(qobject_cast(window));
+ } else {
+ features.append(qobject_cast(window));
+ }
+ }
+ }
+ }
+
+ // Order windows by device/feature/channel index
+ orderByIndex(devices);
+ orderByIndex(spectrums);
+ orderByIndex(channels);
+ orderByIndex(fixedFeatures);
+ orderByIndex(features);
+
+ // Spacing between windows
+ const int spacing = 2;
+
+ // Calculate width and height needed for devices
+ int deviceMinWidth = 0;
+ int deviceTotalMinHeight = 0;
+ for (auto window : devices)
+ {
+ int winMinWidth = std::max(window->minimumSizeHint().width(), window->minimumWidth());
+ deviceMinWidth = std::max(deviceMinWidth, winMinWidth);
+ deviceTotalMinHeight += window->minimumSizeHint().height() + spacing;
+ }
+
+ // Calculate width & height needed for spectrums
+ int spectrumMinWidth = 0;
+ int spectrumTotalMinHeight = 0;
+ int expandingSpectrums = 0;
+ for (auto window : spectrums)
+ {
+ int winMinWidth = std::max(window->minimumSizeHint().width(), window->minimumWidth());
+ spectrumMinWidth = std::max(spectrumMinWidth, winMinWidth);
+ spectrumTotalMinHeight += window->minimumSizeHint().height() + spacing;
+ expandingSpectrums++;
+ }
+
+ // Calculate width & height needed for channels
+ int channelMinWidth = m_userChannelMinWidth;
+ int channelTotalMinHeight = 0;
+ int expandingChannels = 0;
+ for (auto window : channels)
+ {
+ int winMinWidth = std::max(window->minimumSizeHint().width(), window->minimumWidth());
+ channelMinWidth = std::max(channelMinWidth, winMinWidth);
+ channelTotalMinHeight += window->minimumSizeHint().height() + spacing;
+ if (window->sizePolicy().verticalPolicy() == QSizePolicy::Expanding) {
+ expandingChannels++;
+ }
+ }
+
+ // Calculate width & height needed for features
+ // These are spilt in to two groups - fixed size and expandable
+ int fixedFeaturesWidth = 0;
+ int fixedFeaturesTotalMinHeight = 0;
+ int featuresMinWidth = 0;
+ int featuresTotalMinHeight = 0;
+ int expandingFeatures = 0;
+ for (auto window : fixedFeatures)
+ {
+ int winMinWidth = std::max(window->minimumSizeHint().width(), window->minimumWidth());
+ fixedFeaturesWidth = std::max(fixedFeaturesWidth, winMinWidth);
+ fixedFeaturesTotalMinHeight += window->minimumSizeHint().height() + spacing;
+ }
+ for (auto window : features)
+ {
+ int winMinWidth = std::max(window->minimumSizeHint().width(), window->minimumWidth());
+ featuresMinWidth = std::max(featuresMinWidth, winMinWidth);
+ featuresTotalMinHeight += window->minimumSizeHint().height() + spacing;
+ expandingFeatures++;
+ }
+
+ // Calculate width for left hand column
+ int devicesFeaturesWidth = std::max(deviceMinWidth, fixedFeaturesWidth);
+ // Calculate min width for centre column
+ int spectrumFeaturesMinWidth = std::max(spectrumMinWidth, featuresMinWidth);
+
+ // Calculate spacing between columns
+ int spacing1 = devicesFeaturesWidth > 0 ? spacing : 0;
+ int spacing2 = spectrumFeaturesMinWidth > 0 ? spacing : 0;
+
+ // Will we need scroll bars?
+ QSize mdiSize = m_mdi->size();
+ int minWidth = devicesFeaturesWidth + spacing1 + spectrumFeaturesMinWidth + spacing2 + channelMinWidth;
+ int minHeight = std::max(std::max(deviceTotalMinHeight + fixedFeaturesTotalMinHeight, channelTotalMinHeight), channelTotalMinHeight + featuresTotalMinHeight);
+ 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);
+ }
+
+ // Now position the windows
+ int x = 0;
+ int y = 0;
+
+ // Put devices down left hand side
+ for (auto window : devices)
+ {
+ window->move(x, y);
+ y += window->size().height() + spacing;
+ }
+
+ // Put fixed height features underneath devices
+ // Resize them to be same width
+ for (auto window : fixedFeatures)
+ {
+ window->move(x, y);
+ window->resize(devicesFeaturesWidth, window->size().height());
+ y += window->size().height() + spacing;
+ }
+
+ // Calculate width needed for spectrum and features in the centre - use all available space
+ int spectrumFeaturesWidth = std::max(mdiSize.width() - channelMinWidth - devicesFeaturesWidth - spacing1 - spacing2, spectrumFeaturesMinWidth);
+
+ // Put channels on right hand side
+ // Try to resize them horizontally so they are the same width
+ // Share any available vertical space between expanding channels
+
+ x = devicesFeaturesWidth + spacing1 + spectrumFeaturesWidth + spacing2;
+ y = 0;
+ int extraSpacePerWindow;
+ int extraSpaceFirstWindow;
+ if ((channelTotalMinHeight < mdiSize.height()) && (expandingChannels > 0))
+ {
+ extraSpacePerWindow = (mdiSize.height() - channelTotalMinHeight) / expandingChannels;
+ extraSpaceFirstWindow = (mdiSize.height() - channelTotalMinHeight) % expandingChannels;
+ }
+ else
+ {
+ extraSpacePerWindow = 0;
+ extraSpaceFirstWindow = 0;
+ }
+
+ for (auto window : channels)
+ {
+ window->move(x, y);
+ int channelHeight = window->minimumSizeHint().height();
+ if (window->sizePolicy().verticalPolicy() == QSizePolicy::Expanding)
+ {
+ channelHeight += extraSpacePerWindow + extraSpaceFirstWindow;
+ extraSpaceFirstWindow = 0;
+ }
+ window->resize(channelMinWidth, channelHeight);
+ y += window->size().height() + spacing;
+ }
+
+ // Split remaining space in the middle between spectrums and expandable features, with spectrums stacked on top
+ x = devicesFeaturesWidth + spacing1;
+ y = 0;
+ if ((spectrumTotalMinHeight + featuresTotalMinHeight < mdiSize.height()) && (expandingSpectrums + expandingFeatures > 0))
+ {
+ int h = mdiSize.height() - spectrumTotalMinHeight - featuresTotalMinHeight;
+ int f = expandingSpectrums + expandingFeatures;
+ extraSpacePerWindow = h / f;
+ extraSpaceFirstWindow = h % f;
+ }
+ else
+ {
+ extraSpacePerWindow = 0;
+ extraSpaceFirstWindow = 0;
+ }
+
+ for (auto window : spectrums)
+ {
+ window->move(x, y);
+ int w = spectrumFeaturesWidth;
+ int h = window->minimumSizeHint().height() + extraSpacePerWindow + extraSpaceFirstWindow;
+ window->resize(w, h);
+ extraSpaceFirstWindow = 0;
+ y += window->size().height() + spacing;
+ }
+ for (auto window : features)
+ {
+ window->move(x, y);
+ int w = spectrumFeaturesWidth;
+ int h = window->minimumSizeHint().height() + extraSpacePerWindow + extraSpaceFirstWindow;
+ window->resize(w, h);
+ extraSpaceFirstWindow = 0;
+ y += window->size().height() + spacing;
+ }
+
+ m_stacking = false;
+}
+
+void Workspace::autoStackSubWindows()
+{
+ // FIXME: Need to save whether this is checked as a preference
+ if (m_autoStackSubWindows->isChecked()) {
+ stackSubWindows();
+ }
+}
+
+void Workspace::resizeEvent(QResizeEvent *event)
+{
+ QDockWidget::resizeEvent(event);
+ autoStackSubWindows();
+}
+
void Workspace::addToMdiArea(QMdiSubWindow *sub)
{
+ // Add event handler to auto-stack when sub window shown or hidden
+ 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);
m_mdi->addSubWindow(sub);
sub->show();
+ // 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);
+ }
+ FeatureGUI *feature = qobject_cast(sub);
+ if (feature) {
+ connect(feature->getRollupContents(), &RollupContents::widgetRolled, this, &Workspace::autoStackSubWindows);
+ }
}
void Workspace::removeFromMdiArea(QMdiSubWindow *sub)
{
m_mdi->removeSubWindow(sub);
+ sub->removeEventFilter(this);
+ disconnect(sub, &QObject::destroyed, this, &Workspace::autoStackSubWindows);
+ ChannelGUI *channel = qobject_cast(sub);
+ if (channel) {
+ disconnect(channel->getRollupContents(), &RollupContents::widgetRolled, this, &Workspace::autoStackSubWindows);
+ }
+ FeatureGUI *feature = qobject_cast(sub);
+ if (feature) {
+ disconnect(feature->getRollupContents(), &RollupContents::widgetRolled, this, &Workspace::autoStackSubWindows);
+ }
+}
+
+bool Workspace::eventFilter(QObject *obj, QEvent *event)
+{
+ if (event->type() == QEvent::Show)
+ {
+ autoStackSubWindows();
+ }
+ else if (event->type() == QEvent::Hide)
+ {
+ autoStackSubWindows();
+ }
+ else if (event->type() == QEvent::Resize)
+ {
+ if (!m_stacking && m_autoStackSubWindows->isChecked())
+ {
+ ChannelGUI *channel = qobject_cast(obj);
+ if (channel)
+ {
+ // Allow width of channels column to be set by user when they
+ // resize a channel window
+ QResizeEvent *resizeEvent = static_cast(event);
+ m_userChannelMinWidth = resizeEvent->size().width();
+ stackSubWindows();
+ }
+ }
+ }
+ return QDockWidget::eventFilter(obj, event);
}
int Workspace::getNumberOfSubWindows() const
diff --git a/sdrgui/gui/workspace.h b/sdrgui/gui/workspace.h
index fff92eaa7..339c83252 100644
--- a/sdrgui/gui/workspace.h
+++ b/sdrgui/gui/workspace.h
@@ -31,6 +31,10 @@ class QStringList;
class QMdiArea;
class QMdiSubWindow;
class QFrame;
+class ChannelGUI;
+class FeatureGUI;
+class DeviceGUI;
+class MainSpectrumGUI;
class SDRGUI_API Workspace : public QDockWidget
{
@@ -49,6 +53,10 @@ public:
QByteArray saveMdiGeometry();
void restoreMdiGeometry(const QByteArray& blob);
QList getSubWindowList() const;
+ void orderByIndex(QList &list);
+ void orderByIndex(QList &list);
+ void orderByIndex(QList &list);
+ void orderByIndex(QList &list);
private:
int m_index;
@@ -61,6 +69,8 @@ private:
QFrame *m_vline2;
QPushButton *m_cascadeSubWindows;
QPushButton *m_tileSubWindows;
+ QPushButton *m_stackSubWindows;
+ QPushButton *m_autoStackSubWindows;
QWidget *m_titleBar;
QHBoxLayout *m_titleBarLayout;
QLabel *m_titleLabel;
@@ -68,6 +78,12 @@ private:
QPushButton *m_closeButton;
FeatureAddDialog m_featureAddDialog;
QMdiArea *m_mdi;
+ bool m_stacking; // Set when stackSubWindows() is running
+ int m_userChannelMinWidth; // Minimum width of channels column for stackSubWindows(), set by user resizing a channel window
+
+protected:
+ void resizeEvent(QResizeEvent *event) override;
+ bool eventFilter(QObject *obj, QEvent *event) override;
private slots:
void addRxDeviceClicked();
@@ -77,6 +93,8 @@ private slots:
void featurePresetsDialog();
void cascadeSubWindows();
void tileSubWindows();
+ void stackSubWindows();
+ void autoStackSubWindows();
void addFeatureEmitted(int featureIndex);
void toggleFloating();