Make IO peripherals constructable through IOTab
MODIFY
src/io/iobase.cpp
src/io/iobase.cpp
#include "iobase.h"
-namespace Ripes {} // namespace Ripes
+namespace Ripes {
+
+std::map<std::type_index, int> IOBase::s_peripheralCount;
+
+} // namespace Ripes

MODIFY
src/io/iobase.h
src/io/iobase.h
#include <QVariant>
#include <QWidget>
+#include <typeindex>
+#include <typeinfo>
namespace Ripes {
@@ -25,10 +27,11 @@ class IOBase : public QWidget {
Q_OBJECT
public:
- IOBase(QWidget* parent, uint32_t startAddr) : QWidget(parent) {}
- virtual ~IOBase(){};
+ IOBase(QWidget* parent, uint32_t startAddr) : QWidget(parent) { registerPeripheral(this); }
+ virtual ~IOBase() { deregisterPeripheral(this); };
const std::map<unsigned, IOParam>& parameters() const { return m_parameters; }
+ virtual QString description() const = 0;
/**
* @brief setParameter
@@ -55,5 +58,15 @@ public:
protected:
::uint32_t m_startAddr;
std::map<unsigned, IOParam> m_parameters;
+
+ static std::map<std::type_index, int> s_peripheralCount;
+ static void registerPeripheral(const IOBase* p) {
+ s_peripheralCount[typeid(p)]++;
+ return;
+ }
+ static void deregisterPeripheral(const IOBase* p) {
+ Q_ASSERT(s_peripheralCount.at(typeid(p)) > 0);
+ s_peripheralCount[typeid(p)]--;
+ }
};
} // namespace Ripes

MODIFY
src/io/ioledmatrix.cpp
src/io/ioledmatrix.cpp
namespace Ripes {
-IOLedMatrix::IOLedMatrix(QWidget* parent, uint32_t startAddr, unsigned maxWidth)
- : IOBase(parent, startAddr), m_maxWidth(maxWidth) {
- constexpr unsigned defaultWidth = 10;
+IOLedMatrix::IOLedMatrix(QWidget* parent, uint32_t startAddr) : IOBase(parent, startAddr) {
+ constexpr unsigned defaultWidth = 25;
// Parameters
- m_parameters[WIDTH] = IOParam(WIDTH, "Matrix width", defaultWidth, true, 0, m_maxWidth);
- m_parameters[SIZE] = IOParam(SIZE, "LED size", 25, true, 1, 100);
+ m_parameters[HEIGHT] = IOParam(WIDTH, "Matrix height", defaultWidth, true, 0, m_maxSideWidth);
+ m_parameters[WIDTH] = IOParam(WIDTH, "Matrix width", defaultWidth + 10, true, 0, m_maxSideWidth);
+ m_parameters[SIZE] = IOParam(SIZE, "LED size", 8, true, 1, 100);
m_pen.setWidth(1);
m_pen.setColor(Qt::black);
@@ -19,6 +19,16 @@ IOLedMatrix::IOLedMatrix(QWidget* parent, uint32_t startAddr, unsigned maxWidth)
updateLEDRegs();
}
+QString IOLedMatrix::description() const {
+ QStringList desc;
+ desc << "A word-addressable LED matrix.";
+ desc
+ << "Each LED maps to a 24-bit register storing an RGB color value, with B stored in the least significant byte";
+ desc << "The byte offset of each LED is calculated as '(rowIdx + colIdx*width) * 4'.";
+
+ return desc.join('\n');
+}
+
const QVariant& IOLedMatrix::setParameter(unsigned ID, const QVariant& value) {
return QVariant();
}
@@ -46,27 +56,29 @@ inline QColor regToColor(uint32_t regVal) {
void IOLedMatrix::updateLEDRegs() {
const int width = m_parameters[WIDTH].value.toInt();
+ const int height = m_parameters[HEIGHT].value.toInt();
m_ledRegs.resize(width * width);
// DEBUG: test pattern
- for (int y = 0; y < width; y++) {
+ for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
const int r = (0xFF * x) / width;
- const int g = (0xFF * y) / width;
- const int b = (0xFF * (x + y)) / (width + width);
+ const int g = (0xFF * y) / height;
+ const int b = (0xFF * (x + y)) / (width + height);
const unsigned idx = y * width + x;
m_ledRegs.at(idx) = r << 16 | g << 8 | b;
- m_ledRegs.at(idx) = (0xFFFFFF / m_ledRegs.size()) * idx;
}
}
}
QSize IOLedMatrix::minimumSizeHint() const {
const int width = m_parameters.at(WIDTH).value.toInt();
+ const int height = m_parameters.at(HEIGHT).value.toInt();
const int size = m_parameters.at(SIZE).value.toInt();
- const int pixelWidth = width * (size + 2);
- return QSize(pixelWidth, pixelWidth);
+ const int pixelWidth = width * (size + m_pen.width());
+ const int pixelHeight = height * (size + m_pen.width());
+ return QSize(pixelWidth, pixelHeight);
}
void IOLedMatrix::paintEvent(QPaintEvent* event) {
@@ -75,14 +87,15 @@ void IOLedMatrix::paintEvent(QPaintEvent* event) {
painter.setPen(m_pen);
const int width = m_parameters[WIDTH].value.toInt();
+ const int height = m_parameters[HEIGHT].value.toInt();
const int size = m_parameters[SIZE].value.toInt();
- for (int y = 0; y < width; y++) {
+ for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
QBrush brush(regToColor(m_ledRegs.at(y * width + x)));
painter.setBrush(brush);
- const unsigned xpos = x * size + size / 2;
- const unsigned ypos = y * size + size / 2;
+ const unsigned xpos = x * (size + m_pen.width());
+ const unsigned ypos = y * (size + m_pen.width());
painter.drawEllipse(xpos, ypos, size, size);
}

MODIFY
src/io/ioledmatrix.h
src/io/ioledmatrix.h
@@ -11,15 +11,16 @@ namespace Ripes {
class IOLedMatrix : public IOBase {
Q_OBJECT
- enum Parameters { WIDTH, SIZE };
+ enum Parameters { HEIGHT, WIDTH, SIZE };
public:
- IOLedMatrix(QWidget* parent, uint32_t startAddr, unsigned maxWidth);
+ IOLedMatrix(QWidget* parent, uint32_t startAddr);
~IOLedMatrix(){};
uint32_t startAddr() const { return m_startAddr; }
- virtual uint32_t size() const override { return (m_maxWidth * m_maxWidth) * 4; }
+ 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 const QVariant& setParameter(unsigned ID, const QVariant& value) override;
@@ -44,7 +45,7 @@ private:
uint32_t regRead(uint32_t offset) const;
void updateLEDRegs();
- unsigned m_maxWidth;
+ unsigned m_maxSideWidth = 256;
std::vector<uint32_t> m_ledRegs;
QPen m_pen;

ADD
src/io/ioregistry.h
+#pragma once
+
+#include <QWidget>
+#include "iobase.h"
+
+#include "ioledmatrix.h"
+
+/** @brief IORegistry
+ *
+ * This is where all peripherals should be registerred to be made available in the UI.
+ * The peripheral must be registerred three times:
+ * - Add it to the IOType enum
+ * - Add it to the IOTypeTitles map (Associate a name with the peripheral)
+ * - Add it to the IOFactories map (Associate a constructor with the peripheral)
+ */
+
+namespace Ripes {
+
+enum class IOType { LED_MATRIX };
+
+template <typename T>
+IOBase* createIO(QWidget* parent, uint32_t startAddr) {
+ static_assert(std::is_base_of<IOBase, T>::value);
+ return new T(parent, startAddr);
+}
+
+using IOFactory = std::function<IOBase*(QWidget* parent, uint32_t)>;
+
+const static std::map<IOType, QString> IOTypeTitles = {{IOType::LED_MATRIX, "LED Matrix"}};
+const static std::map<IOType, IOFactory> IOFactories = {{IOType::LED_MATRIX, createIO<IOLedMatrix>}};
+
+} // namespace Ripes
+
+Q_DECLARE_METATYPE(Ripes::IOType);

MODIFY
src/iotab.cpp
src/iotab.cpp
#include <QDockWidget>
#include <QGraphicsItem>
+#include <QMdiSubWindow>
#include <QToolBar>
#include "processorhandler.h"
-// IO components
-#include "io/ioledmatrix.h"
-
namespace Ripes {
IOTab::IOTab(QToolBar* toolbar, QWidget* parent) : RipesTab(toolbar, parent), m_ui(new Ui::IOTab) {
@@ -18,26 +16,78 @@ IOTab::IOTab(QToolBar* toolbar, QWidget* parent) : RipesTab(toolbar, parent), m_
m_ui->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
m_ui->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
- for (int i = 0; i < 10; i++) {
+ // Toolbar actions
+ m_tileAction = new QAction(this);
+ m_tileAction->setIcon(QIcon(":/icons/tile.svg"));
+ m_tileAction->setText("Tile I/O windows");
+ connect(m_tileAction, &QAction::triggered, this, &IOTab::tile);
+ m_toolbar->addAction(m_tileAction);
+
+ // Setup peripheral selection window
+ m_ui->peripheralsTable->setRowCount(IOTypeTitles.size());
+ m_ui->peripheralsTable->setColumnCount(1);
+ m_ui->peripheralsTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
+ m_ui->peripheralsTable->setHorizontalHeaderLabels({"Peripherals"});
+ m_ui->peripheralsTable->verticalHeader()->hide();
+ int row = 0;
+ for (const auto& it : IOTypeTitles) {
+ QTableWidgetItem* periphItem = new QTableWidgetItem(it.second);
+ periphItem->setData(Qt::UserRole, QVariant::fromValue(it.first));
+ periphItem->setToolTip("Double-click to create");
+ periphItem->setFlags(periphItem->flags() & ~Qt::ItemIsEditable);
+ m_ui->peripheralsTable->setItem(row++, 0, periphItem);
+ }
+
+ connect(m_ui->peripheralsTable, &QTableWidget::itemDoubleClicked,
+ [this](QTableWidgetItem* item) { this->createPeripheral(item->data(Qt::UserRole).value<IOType>()); });
+
+ // Setup MDI area
+ connect(m_ui->mdiArea, &QMdiArea::subWindowActivated, [this](QMdiSubWindow* w) {
+ if (w == nullptr) {
+ setPeripheralActive(nullptr);
+ } else {
+ // MDI window -> QMainwindow -> QDockWidget -> IOBase widget... Whew!
+ auto* w1 = w->widget();
+ auto* w2 = w1->findChildren<QDockWidget*>().at(0);
+ auto* w3 = w2->widget();
+ auto* peripheral = dynamic_cast<IOBase*>(w3);
+ Q_ASSERT(peripheral != nullptr);
+ this->setPeripheralActive(peripheral);
+ }
+ });
+}
+
+/**
+ * Dummy function, should be moved to a separate memory map manager
+ */
+uint32_t nextAddress() {
+ return 0x10000;
+}
+
+void IOTab::createPeripheral(IOType type) {
// 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);
- auto* dw = new QDockWidget("LED Matrix");
- dw->setWidget(new IOLedMatrix(this, 0x10000, 25));
+ 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->setAllowedAreas(Qt::AllDockWidgetAreas);
mw->addDockWidget(Qt::TopDockWidgetArea, dw);
- m_ui->mdiArea->addSubWindow(mw);
+ auto* sw = m_ui->mdiArea->addSubWindow(mw);
+
+ connect(sw, &QObject::destroyed, this, &IOTab::removePeripheral);
}
- // Toolbar actions
- m_tileAction = new QAction(this);
- m_tileAction->setIcon(QIcon(":/icons/tile.svg"));
- m_tileAction->setText("Tile I/O windows");
- connect(m_tileAction, &QAction::triggered, this, &IOTab::tile);
- m_toolbar->addAction(m_tileAction);
+void IOTab::setPeripheralActive(IOBase* peripheral) {
+ if (peripheral == nullptr) {
+ // No more widgets left, figure out what to do with the tab...
+ } else {
+ // ...
+ }
}
+void IOTab::removePeripheral(QObject* peripheral) {}
+
void IOTab::tile() {
Q_ASSERT(false);
}

MODIFY
src/iotab.h
src/iotab.h
#include "processorhandler.h"
#include "ripestab.h"
+#include "io/ioregistry.h"
+
namespace Ripes {
namespace Ui {
@@ -24,11 +26,15 @@ public:
IOTab(QToolBar* toolbar, QWidget* parent = nullptr);
~IOTab() override;
+private slots:
+ void removePeripheral(QObject* peripheral);
+ void setPeripheralActive(IOBase* peripheral);
+
private:
- void tile();
+ void createPeripheral(IOType type);
+ void tile();
Ui::IOTab* m_ui = nullptr;
-
QAction* m_tileAction = nullptr;
};
} // namespace Ripes

MODIFY
src/iotab.ui
src/iotab.ui
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="1">
- <layout class="QVBoxLayout" name="verticalLayout">
+ <layout class="QVBoxLayout" name="verticalLayout" stretch="0,1">
<item>
- <widget class="QTreeView" name="treeView"/>
+ <widget class="QTableWidget" name="peripheralsTable"/>
</item>
<item>
- <widget class="QTabWidget" name="tabWidget_2">
+ <widget class="QTabWidget" name="peripheralsTab">
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Tab 1</string>

Make IO peripherals constructable through IOTab