mirror of
				https://github.com/f4exb/sdrangel.git
				synced 2025-10-29 20:10:22 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			797 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			797 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| ///////////////////////////////////////////////////////////////////////////////////
 | |
| // Copyright (C) 2022 F4EXB                                                      //
 | |
| // written by Edouard Griffiths                                                  //
 | |
| //                                                                               //
 | |
| // This program is free software; you can redistribute it and/or modify          //
 | |
| // it under the terms of the GNU General Public License as published by          //
 | |
| // the Free Software Foundation as version 3 of the License, or                  //
 | |
| // (at your option) any later version.                                           //
 | |
| //                                                                               //
 | |
| // This program is distributed in the hope that it will be useful,               //
 | |
| // but WITHOUT ANY WARRANTY; without even the implied warranty of                //
 | |
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the                  //
 | |
| // GNU General Public License V3 for more details.                               //
 | |
| //                                                                               //
 | |
| // You should have received a copy of the GNU General Public License             //
 | |
| // along with this program. If not, see <http://www.gnu.org/licenses/>.          //
 | |
| ///////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| #include <algorithm>
 | |
| 
 | |
| #include <QHBoxLayout>
 | |
| #include <QLabel>
 | |
| #include <QPushButton>
 | |
| #include <QStyle>
 | |
| #include <QMdiArea>
 | |
| #include <QMdiSubWindow>
 | |
| #include <QFrame>
 | |
| #include <QDebug>
 | |
| #include <QApplication>
 | |
| 
 | |
| #include "gui/samplingdevicedialog.h"
 | |
| #include "gui/rollupcontents.h"
 | |
| #include "gui/buttonswitch.h"
 | |
| #include "channel/channelgui.h"
 | |
| #include "feature/featuregui.h"
 | |
| #include "device/devicegui.h"
 | |
| #include "device/deviceset.h"
 | |
| #include "mainspectrum/mainspectrumgui.h"
 | |
| #include "workspace.h"
 | |
| #include "maincore.h"
 | |
| 
 | |
| Workspace::Workspace(int index, QWidget *parent, Qt::WindowFlags flags) :
 | |
|     QDockWidget(parent, flags),
 | |
|     m_index(index),
 | |
|     m_featureAddDialog(this),
 | |
|     m_stacking(false),
 | |
|     m_userChannelMinWidth(0)
 | |
| {
 | |
|     m_mdi = new QMdiArea(this);
 | |
|     m_mdi->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
 | |
|     m_mdi->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
 | |
|     setWidget(m_mdi);
 | |
| 
 | |
|     setWindowTitle(tr("W%1").arg(m_index));
 | |
|     setObjectName(tr("W%1").arg(m_index));
 | |
| 
 | |
|     m_titleBar = new QWidget();
 | |
|     m_titleBarLayout = new QHBoxLayout();
 | |
|     m_titleBarLayout->setMargin(0);
 | |
|     m_titleBar->setLayout(m_titleBarLayout);
 | |
| 
 | |
|     m_titleLabel = new QLabel();
 | |
|     m_titleLabel->setFixedSize(32, 16);
 | |
|     m_titleLabel->setStyleSheet("QLabel { background-color: rgb(128, 128, 128); qproperty-alignment: AlignCenter; }");
 | |
|     m_titleLabel->setText(windowTitle());
 | |
| 
 | |
|     m_addRxDeviceButton = new QPushButton();
 | |
|     QIcon addRxIcon(":/rx.png");
 | |
|     m_addRxDeviceButton->setIcon(addRxIcon);
 | |
|     m_addRxDeviceButton->setToolTip("Add Rx device");
 | |
|     m_addRxDeviceButton->setFixedSize(20, 20);
 | |
| 
 | |
|     m_addTxDeviceButton = new QPushButton();
 | |
|     QIcon addTxIcon(":/tx.png");
 | |
|     m_addTxDeviceButton->setIcon(addTxIcon);
 | |
|     m_addTxDeviceButton->setToolTip("Add Tx device");
 | |
|     m_addTxDeviceButton->setFixedSize(20, 20);
 | |
| 
 | |
|     m_addMIMODeviceButton = new QPushButton();
 | |
|     QIcon addMIMOIcon(":/mimo.png");
 | |
|     m_addMIMODeviceButton->setIcon(addMIMOIcon);
 | |
|     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_addFeatureButton = new QPushButton();
 | |
|     QIcon addFeatureIcon(":/tool_add.png");
 | |
|     m_addFeatureButton->setIcon(addFeatureIcon);
 | |
|     m_addFeatureButton->setToolTip("Add features");
 | |
|     m_addFeatureButton->setFixedSize(20, 20);
 | |
| 
 | |
|     m_featurePresetsButton = new QPushButton();
 | |
|     QIcon presetsIcon(":/tool_star.png");
 | |
|     m_featurePresetsButton->setIcon(presetsIcon);
 | |
|     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_cascadeSubWindows = new QPushButton();
 | |
|     QIcon cascadeSubWindowsIcon(":/cascade.png");
 | |
|     m_cascadeSubWindows->setIcon(cascadeSubWindowsIcon);
 | |
|     m_cascadeSubWindows->setToolTip("Cascade sub windows");
 | |
|     m_cascadeSubWindows->setFixedSize(20, 20);
 | |
| 
 | |
|     m_tileSubWindows = new QPushButton();
 | |
|     QIcon tileSubWindowsIcon(":/tiles.png");
 | |
|     m_tileSubWindows->setIcon(tileSubWindowsIcon);
 | |
|     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 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_normalButton = new QPushButton();
 | |
|     QIcon normalIcon(":/dock.png");
 | |
|     m_normalButton->setIcon(normalIcon);
 | |
|     m_normalButton->setToolTip("Dock/undock");
 | |
|     m_normalButton->setFixedSize(20, 20);
 | |
| 
 | |
|     m_closeButton = new QPushButton();
 | |
|     QIcon closeIcon(":/hide.png");
 | |
|     m_closeButton->setIcon(closeIcon);
 | |
|     m_closeButton->setToolTip("Hide workspace");
 | |
|     m_closeButton->setFixedSize(20, 20);
 | |
| 
 | |
|     m_titleBarLayout->addWidget(m_titleLabel);
 | |
|     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_addFeatureButton);
 | |
|     m_titleBarLayout->addWidget(m_featurePresetsButton);
 | |
|     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);
 | |
|     setTitleBarWidget(m_titleBar);
 | |
| 
 | |
|     QObject::connect(
 | |
|         m_addRxDeviceButton,
 | |
|         &QPushButton::clicked,
 | |
|         this,
 | |
|         &Workspace::addRxDeviceClicked
 | |
|     );
 | |
| 
 | |
|     QObject::connect(
 | |
|         m_addTxDeviceButton,
 | |
|         &QPushButton::clicked,
 | |
|         this,
 | |
|         &Workspace::addTxDeviceClicked
 | |
|     );
 | |
| 
 | |
|     QObject::connect(
 | |
|         m_addMIMODeviceButton,
 | |
|         &QPushButton::clicked,
 | |
|         this,
 | |
|         &Workspace::addMIMODeviceClicked
 | |
|     );
 | |
| 
 | |
|     QObject::connect(
 | |
|         m_addFeatureButton,
 | |
|         &QPushButton::clicked,
 | |
|         this,
 | |
|         &Workspace::addFeatureDialog
 | |
|     );
 | |
| 
 | |
|     QObject::connect(
 | |
|         m_featurePresetsButton,
 | |
|         &QPushButton::clicked,
 | |
|         this,
 | |
|         &Workspace::featurePresetsDialog
 | |
|     );
 | |
| 
 | |
|     QObject::connect(
 | |
|         m_cascadeSubWindows,
 | |
|         &QPushButton::clicked,
 | |
|         this,
 | |
|         &Workspace::cascadeSubWindows
 | |
|     );
 | |
| 
 | |
|     QObject::connect(
 | |
|         m_tileSubWindows,
 | |
|         &QPushButton::clicked,
 | |
|         this,
 | |
|         &Workspace::tileSubWindows
 | |
|     );
 | |
| 
 | |
|     QObject::connect(
 | |
|         m_stackSubWindows,
 | |
|         &QPushButton::clicked,
 | |
|         this,
 | |
|         &Workspace::stackSubWindows
 | |
|     );
 | |
| 
 | |
|     QObject::connect(
 | |
|         m_startStopButton,
 | |
|         &ButtonSwitch::clicked,
 | |
|         this,
 | |
|         &Workspace::startStopClicked
 | |
|     );
 | |
| 
 | |
|     QObject::connect(
 | |
|         m_autoStackSubWindows,
 | |
|         &QPushButton::clicked,
 | |
|         this,
 | |
|         &Workspace::autoStackSubWindows
 | |
|     );
 | |
| 
 | |
|     QObject::connect(
 | |
|         m_normalButton,
 | |
|         &QPushButton::clicked,
 | |
|         this,
 | |
|         &Workspace::toggleFloating
 | |
|     );
 | |
| 
 | |
|     connect(m_closeButton, SIGNAL(clicked()), this, SLOT(hide()));
 | |
| 
 | |
|     QObject::connect(
 | |
|         &m_featureAddDialog,
 | |
|         &FeatureAddDialog::addFeature,
 | |
|         this,
 | |
|         &Workspace::addFeatureEmitted
 | |
|     );
 | |
| 
 | |
|     QObject::connect(
 | |
|         MainCore::instance(),
 | |
|         &MainCore::deviceStateChanged,
 | |
|         this,
 | |
|         &Workspace::deviceStateChanged
 | |
|     );
 | |
| 
 | |
| }
 | |
| 
 | |
| 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;
 | |
|     delete m_vline1;
 | |
|     delete m_startStopButton;
 | |
|     delete m_addRxDeviceButton;
 | |
|     delete m_addTxDeviceButton;
 | |
|     delete m_addMIMODeviceButton;
 | |
|     delete m_addFeatureButton;
 | |
|     delete m_featurePresetsButton;
 | |
|     delete m_titleLabel;
 | |
|     delete m_titleBarLayout;
 | |
|     delete m_titleBar;
 | |
|     qDebug("Workspace::~Workspace: about to delete MDI");
 | |
|     delete m_mdi;
 | |
|     qDebug("Workspace::~Workspace: end");
 | |
| }
 | |
| 
 | |
| void Workspace::setIndex(int index)
 | |
| {
 | |
|     m_index = index;
 | |
|     setWindowTitle(tr("W%1").arg(m_index));
 | |
|     setObjectName(tr("W%1").arg(m_index));
 | |
|     m_titleLabel->setText(windowTitle());
 | |
| }
 | |
| 
 | |
| QList<QMdiSubWindow *> Workspace::getSubWindowList() const
 | |
| {
 | |
|     return m_mdi->subWindowList();
 | |
| }
 | |
| 
 | |
| void Workspace::toggleFloating()
 | |
| {
 | |
|     setFloating(!isFloating());
 | |
| }
 | |
| 
 | |
| void Workspace::addRxDeviceClicked()
 | |
| {
 | |
|     SamplingDeviceDialog dialog(0, this);
 | |
| 
 | |
|     if (dialog.exec() == QDialog::Accepted) {
 | |
|         emit addRxDevice(this, dialog.getSelectedDeviceIndex());
 | |
|     }
 | |
| }
 | |
| 
 | |
| void Workspace::addTxDeviceClicked()
 | |
| {
 | |
|     SamplingDeviceDialog dialog(1, this);
 | |
| 
 | |
|     if (dialog.exec() == QDialog::Accepted) {
 | |
|         emit addTxDevice(this, dialog.getSelectedDeviceIndex());
 | |
|     }
 | |
| }
 | |
| 
 | |
| void Workspace::addMIMODeviceClicked()
 | |
| {
 | |
|     SamplingDeviceDialog dialog(2, this);
 | |
| 
 | |
|     if (dialog.exec() == QDialog::Accepted) {
 | |
|         emit addMIMODevice(this, dialog.getSelectedDeviceIndex());
 | |
|     }
 | |
| }
 | |
| 
 | |
| void Workspace::addFeatureDialog()
 | |
| {
 | |
|     m_featureAddDialog.exec();
 | |
| }
 | |
| 
 | |
| void Workspace::addFeatureEmitted(int featureIndex)
 | |
| {
 | |
|     if (featureIndex >= 0) {
 | |
|         emit addFeature(this, featureIndex);
 | |
|     }
 | |
| }
 | |
| 
 | |
| void Workspace::featurePresetsDialog()
 | |
| {
 | |
|     QPoint p = mapFromGlobal(QCursor::pos());
 | |
|     emit featurePresetsDialogRequested(p, this);
 | |
| }
 | |
| 
 | |
| void Workspace::cascadeSubWindows()
 | |
| {
 | |
|     m_mdi->cascadeSubWindows();
 | |
| }
 | |
| 
 | |
| void Workspace::tileSubWindows()
 | |
| {
 | |
|     m_mdi->tileSubWindows();
 | |
| }
 | |
| 
 | |
| void Workspace::orderByIndex(QList<ChannelGUI *> &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<FeatureGUI *> &list)
 | |
| {
 | |
|     std::sort(list.begin(), list.end(),
 | |
|         [](const FeatureGUI *a, const FeatureGUI *b) -> bool
 | |
|         {
 | |
|             return a->getIndex() < b->getIndex();
 | |
|         });
 | |
| }
 | |
| 
 | |
| void Workspace::orderByIndex(QList<DeviceGUI *> &list)
 | |
| {
 | |
|     std::sort(list.begin(), list.end(),
 | |
|         [](const DeviceGUI *a, const DeviceGUI *b) -> bool
 | |
|         {
 | |
|             return a->getIndex() < b->getIndex();
 | |
|         });
 | |
| }
 | |
| 
 | |
| void Workspace::orderByIndex(QList<MainSpectrumGUI *> &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<QMdiSubWindow *> windows = m_mdi->subWindowList(QMdiArea::CreationOrder);
 | |
|     QList<DeviceGUI *> devices;
 | |
|     QList<MainSpectrumGUI *> spectrums;
 | |
|     QList<ChannelGUI *> channels;
 | |
|     QList<FeatureGUI *> fixedFeatures;
 | |
|     QList<FeatureGUI *> features;
 | |
| 
 | |
|     for (auto window : windows)
 | |
|     {
 | |
|         if (window->isVisible())
 | |
|         {
 | |
|             if (window->inherits("DeviceGUI")) {
 | |
|                 devices.append(qobject_cast<DeviceGUI *>(window));
 | |
|             } else if (window->inherits("MainSpectrumGUI")) {
 | |
|                 spectrums.append(qobject_cast<MainSpectrumGUI *>(window));
 | |
|             } else if (window->inherits("ChannelGUI")) {
 | |
|                 channels.append(qobject_cast<ChannelGUI *>(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<FeatureGUI *>(window));
 | |
|                 } else {
 | |
|                     features.append(qobject_cast<FeatureGUI *>(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 = channels.size() > 0 ? m_userChannelMinWidth : 0;
 | |
|     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()
 | |
| {
 | |
|     if (m_autoStackSubWindows->isChecked()) {
 | |
|         stackSubWindows();
 | |
|     }
 | |
| }
 | |
| 
 | |
| // Start/stop all devices in workspace
 | |
| void Workspace::startStopClicked(bool checked)
 | |
| {
 | |
|     if (!checked) {
 | |
|         emit stopAllDevices(this);
 | |
|     } else {
 | |
|         emit startAllDevices(this);
 | |
|     }
 | |
|     updateStartStopButton(checked);
 | |
| }
 | |
| 
 | |
| void Workspace::updateStartStopButton(bool checked)
 | |
| {
 | |
|     if (!checked)
 | |
|     {
 | |
|         QIcon startIcon(":/play.png");
 | |
|         m_startStopButton->setIcon(startIcon);
 | |
|         m_startStopButton->setStyleSheet("QToolButton { background-color : blue; }");
 | |
|         m_startStopButton->setToolTip("Start all devices in workspace");
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         QIcon stopIcon(":/stop.png");
 | |
|         m_startStopButton->setIcon(stopIcon);
 | |
|         m_startStopButton->setStyleSheet("QToolButton { background-color : green; }");
 | |
|         m_startStopButton->setToolTip("Stop all devices in workspace");
 | |
|     }
 | |
| }
 | |
| 
 | |
| void Workspace::deviceStateChanged(int index, DeviceAPI *deviceAPI)
 | |
| {
 | |
|     if (deviceAPI->getWorkspaceIndex() == m_index)
 | |
|     {
 | |
|         // Check state of all devices in workspace, to see if any are running or have errors
 | |
|         bool running = false;
 | |
|         bool error = false;
 | |
|         std::vector<DeviceSet*> deviceSets = MainCore::instance()->getDeviceSets();
 | |
|         for (auto deviceSet : deviceSets)
 | |
|         {
 | |
|             DeviceAPI::EngineState state = deviceSet->m_deviceAPI->state();
 | |
|             if (state == DeviceAPI::StRunning) {
 | |
|                 running = true;
 | |
|             } else if (state == DeviceAPI::StError) {
 | |
|                 error = true;
 | |
|             }
 | |
|         }
 | |
|         // Update start/stop button to reflect current state of devices
 | |
|         updateStartStopButton(running);
 | |
|         m_startStopButton->setChecked(running);
 | |
|         if (error) {
 | |
|             m_startStopButton->setStyleSheet("QToolButton { background-color : red; }");
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| 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<ChannelGUI *>(sub);
 | |
|     if (channel) {
 | |
|         connect(channel->getRollupContents(), &RollupContents::widgetRolled, this, &Workspace::autoStackSubWindows);
 | |
|     }
 | |
|     FeatureGUI *feature = qobject_cast<FeatureGUI *>(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<ChannelGUI *>(sub);
 | |
|     if (channel) {
 | |
|         disconnect(channel->getRollupContents(), &RollupContents::widgetRolled, this, &Workspace::autoStackSubWindows);
 | |
|     }
 | |
|     FeatureGUI *feature = qobject_cast<FeatureGUI *>(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<ChannelGUI *>(obj);
 | |
|             if (channel)
 | |
|             {
 | |
|                 // Allow width of channels column to be set by user when they
 | |
|                 // resize a channel window
 | |
|                 QResizeEvent *resizeEvent = static_cast<QResizeEvent *>(event);
 | |
|                 m_userChannelMinWidth = resizeEvent->size().width();
 | |
|                 stackSubWindows();
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     return QDockWidget::eventFilter(obj, event);
 | |
| }
 | |
| 
 | |
| int Workspace::getNumberOfSubWindows() const
 | |
| {
 | |
|     return m_mdi->subWindowList().size();
 | |
| }
 | |
| 
 | |
| QByteArray Workspace::saveMdiGeometry()
 | |
| {
 | |
|     return qCompress(m_mdi->saveGeometry());
 | |
| }
 | |
| 
 | |
| void Workspace::restoreMdiGeometry(const QByteArray& blob)
 | |
| {
 | |
|     m_mdi->restoreGeometry(qUncompress(blob));
 | |
| }
 | |
| 
 | |
| bool Workspace::getAutoStackOption() const
 | |
| {
 | |
|     return m_autoStackSubWindows->isChecked();
 | |
| }
 | |
| 
 | |
| void Workspace::setAutoStackOption(bool autoStack)
 | |
| {
 | |
|     m_autoStackSubWindows->doToggle(autoStack);
 | |
| }
 | |
| 
 | |
| void Workspace::adjustSubWindowsAfterRestore()
 | |
| {
 | |
|     QList<QMdiSubWindow *> subWindowList = m_mdi->subWindowList();
 | |
| 
 | |
|     for (auto& subWindow : subWindowList)
 | |
|     {
 | |
|         if ((subWindow->y() >= 20) && (subWindow->y() < 40)) {
 | |
|             subWindow->move(subWindow->x(), subWindow->y() - 20);
 | |
|         }
 | |
| 
 | |
|         if (qobject_cast<ChannelGUI*>(subWindow)) {
 | |
|             subWindow->resize(subWindow->width(), subWindow->height() - 22);
 | |
|         }
 | |
| 
 | |
|         if (qobject_cast<FeatureGUI*>(subWindow)) {
 | |
|             subWindow->resize(subWindow->width(), subWindow->height() - 8);
 | |
|         }
 | |
|     }
 | |
| }
 |