Synchronize MDI area and peripheral tab selection
MODIFY
src/io/iobase.h
src/io/iobase.h
@@ -32,6 +32,7 @@ public:
const std::map<unsigned, IOParam>& parameters() const { return m_parameters; }
virtual QString description() const = 0;
+ virtual QString name() const = 0;
/**
* @brief setParameter

MODIFY
src/io/ioledmatrix.cpp
src/io/ioledmatrix.cpp
@@ -29,6 +29,10 @@ QString IOLedMatrix::description() const {
return desc.join('\n');
}
+QString IOLedMatrix::name() const {
+ return "LED Matrix"; // Todo: generate unique name
+}
+
const QVariant& IOLedMatrix::setParameter(unsigned ID, const QVariant& value) {
return QVariant();
}

MODIFY
src/io/ioledmatrix.h
src/io/ioledmatrix.h
@@ -21,6 +21,7 @@ public:
virtual uint32_t size() const override { return (m_maxSideWidth * m_maxSideWidth) * 4; }
uint32_t endAddr() const { return startAddr() + size(); }
virtual QString description() const override;
+ virtual QString name() const override;
virtual const QVariant& setParameter(unsigned ID, const QVariant& value) override;

MODIFY
src/iotab.cpp
src/iotab.cpp
@@ -16,6 +16,8 @@ IOTab::IOTab(QToolBar* toolbar, QWidget* parent) : RipesTab(toolbar, parent), m_
m_ui->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
m_ui->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+ m_ui->peripheralsTab->clear();
+
// Toolbar actions
m_tileAction = new QAction(this);
m_tileAction->setIcon(QIcon(":/icons/tile.svg"));
@@ -44,7 +46,7 @@ IOTab::IOTab(QToolBar* toolbar, QWidget* parent) : RipesTab(toolbar, parent), m_
// Setup MDI area
connect(m_ui->mdiArea, &QMdiArea::subWindowActivated, [this](QMdiSubWindow* w) {
if (w == nullptr) {
- setPeripheralActive(nullptr);
+ setPeripheralTabActive(nullptr);
} else {
// MDI window -> QMainwindow -> QDockWidget -> IOBase widget... Whew!
auto* w1 = w->widget();
@@ -52,9 +54,11 @@ IOTab::IOTab(QToolBar* toolbar, QWidget* parent) : RipesTab(toolbar, parent), m_
auto* w3 = w2->widget();
auto* peripheral = dynamic_cast<IOBase*>(w3);
Q_ASSERT(peripheral != nullptr);
- this->setPeripheralActive(peripheral);
+ this->setPeripheralTabActive(peripheral);
}
});
+
+ connect(m_ui->peripheralsTab, &QTabWidget::currentChanged, this, &IOTab::setPeripheralMDIWindowActive);
}
/**
@@ -64,29 +68,68 @@ uint32_t nextAddress() {
return 0x10000;
}
+class PeripheralTab : public QWidget {
+public:
+ PeripheralTab(QWidget* parent, IOBase* peripheral) : QWidget(parent), m_peripheral(peripheral) {}
+
+private:
+ IOBase* m_peripheral;
+};
+
void IOTab::createPeripheral(IOType type) {
+ auto* peripheral = IOFactories.at(type)(this, nextAddress());
+ // Create tab for peripheral
+ auto* peripheralTab = new PeripheralTab(this, peripheral);
+ m_ui->peripheralsTab->addTab(peripheralTab, peripheral->name());
+ m_ioTabs[peripheral] = peripheralTab;
+
// It seems excessive to create a QMainWindow for each peripheral but it seems like the only way to mix MDI
// behaviour + dockable widgets that are able to pop out to a separate window
auto* mw = new QMainWindow(this);
m_ui->dockArea->addWidget(mw); // Shouldn't be needed, but MDI windows aren't created without this?
auto* dw = new QDockWidget(IOTypeTitles.at(type));
- dw->setWidget(IOFactories.at(type)(this, nextAddress()));
+ dw->setWidget(peripheral);
dw->setAllowedAreas(Qt::AllDockWidgetAreas);
mw->addDockWidget(Qt::TopDockWidgetArea, dw);
- auto* sw = m_ui->mdiArea->addSubWindow(mw);
+ auto* mdiw = m_ui->mdiArea->addSubWindow(mw);
+
+ m_subWindows[peripheralTab] = mdiw;
+
+ // Signals
- connect(sw, &QObject::destroyed, this, &IOTab::removePeripheral);
+ // Connect to the destruction of the peripheral to trigger required changes on peripheral deletion. The peripheral
+ // is implicitly destructed when the MDIWindow is closed.
+ connect(peripheral, &IOBase::destroyed, this, &IOTab::removePeripheral);
}
-void IOTab::setPeripheralActive(IOBase* peripheral) {
+void IOTab::setPeripheralTabActive(IOBase* peripheral) {
if (peripheral == nullptr) {
- // No more widgets left, figure out what to do with the tab...
- } else {
- // ...
+ return;
+ }
+ m_ui->peripheralsTab->setCurrentWidget(m_ioTabs.at(peripheral));
+}
+
+void IOTab::setPeripheralMDIWindowActive(int tabIndex) {
+ if (tabIndex == -1) {
+ // No more peripherals
+ return;
}
+ auto it = m_subWindows.find(m_ui->peripheralsTab->widget(tabIndex));
+ if (it == m_subWindows.end()) {
+ // We are currently creating the peripheral
+ return;
}
-void IOTab::removePeripheral(QObject* peripheral) {}
+ m_ui->mdiArea->setActiveSubWindow(it->second);
+}
+
+void IOTab::removePeripheral(QObject* peripheral) {
+ auto* tab = m_ioTabs.at(peripheral);
+ Q_ASSERT(m_subWindows.count(tab) != 0);
+ m_subWindows.erase(tab);
+ m_ioTabs.at(peripheral)->deleteLater();
+ m_ioTabs.erase(peripheral);
+}
void IOTab::tile() {
Q_ASSERT(false);

MODIFY
src/iotab.h
src/iotab.h
#include "defines.h"
#include <QAction>
+#include <QMdiSubWindow>
#include <QWidget>
#include <unordered_map>
@@ -28,7 +29,10 @@ public:
private slots:
void removePeripheral(QObject* peripheral);
- void setPeripheralActive(IOBase* peripheral);
+
+ /** The following to slots ensures synchronization between the current MDI window and peripheral tab */
+ void setPeripheralTabActive(IOBase* peripheral);
+ void setPeripheralMDIWindowActive(int tabIndex);
private:
void createPeripheral(IOType type);
@@ -36,5 +40,18 @@ private:
void tile();
Ui::IOTab* m_ui = nullptr;
QAction* m_tileAction = nullptr;
+
+ /**
+ * @brief m_ioTabs
+ * Pointer from the (QObject*) peripheral to its corresponding IO tab. We maintain a QObject* here rather than the
+ * IOBase* given that the association is used during emission of the QObject::destroyed signal. In this case, the
+ * IOBase* subclass has already been destroyed.
+ */
+ std::unordered_map<QObject*, QWidget*> m_ioTabs;
+ /**
+ * @brief m_ioTabs
+ * Pointer from the tab associated with a peripheral to its corresponding MDI subwindow.
+ */
+ std::unordered_map<QWidget*, QMdiSubWindow*> m_subWindows;
};
} // namespace Ripes

Synchronize MDI area and peripheral tab selection