From afaaa48e9d22e4f60467e24d3dee4a9f8dc29cc0 Mon Sep 17 00:00:00 2001 From: Jussi Kuokkanen Date: Wed, 3 Jun 2020 23:24:07 +0300 Subject: [PATCH] qt: add enum editor --- src/tuxclocker-qt/data/DeviceModel.cpp | 231 ++++++++++++------ src/tuxclocker-qt/data/DeviceModel.hpp | 14 +- .../data/DeviceModelDelegate.cpp | 10 +- src/tuxclocker-qt/widgets/EnumEditor.cpp | 59 +++++ src/tuxclocker-qt/widgets/EnumEditor.hpp | 19 ++ 5 files changed, 258 insertions(+), 75 deletions(-) create mode 100644 src/tuxclocker-qt/widgets/EnumEditor.cpp create mode 100644 src/tuxclocker-qt/widgets/EnumEditor.hpp diff --git a/src/tuxclocker-qt/data/DeviceModel.cpp b/src/tuxclocker-qt/data/DeviceModel.cpp index b60839a..347cc5d 100644 --- a/src/tuxclocker-qt/data/DeviceModel.cpp +++ b/src/tuxclocker-qt/data/DeviceModel.cpp @@ -1,15 +1,21 @@ #include "DeviceModel.hpp" -#include "AssignableItem.hpp" #include "AssignableProxy.hpp" +#include "DynamicReadableProxy.hpp" +#include #include #include +// 'match' is a method in QAbstractItemModel :( +namespace p = mpark::patterns; +using namespace fplus; using namespace mpark::patterns; +using namespace TuxClocker::Device; Q_DECLARE_METATYPE(AssignableItemData) Q_DECLARE_METATYPE(TCDBus::Enumeration) Q_DECLARE_METATYPE(TCDBus::Range) +Q_DECLARE_METATYPE(EnumerationVec) DeviceModel::DeviceModel(TC::TreeNode root, QObject *parent) : QStandardItemModel(parent) { @@ -36,75 +42,21 @@ DeviceModel::DeviceModel(TC::TreeNode root, QObject *parent) nameItem->setText(nodeName); rowItems.append(nameItem); - // Add assignable data if this is one - if (node.value().interface == "org.tuxclocker.Assignable") { - QDBusInterface ifaceNode("org.tuxclocker", node.value().path, - "org.tuxclocker.Assignable", conn); - // Should never fail - auto a_info = - qvariant_cast(ifaceNode.property("assignableInfo")) - .variant(); - /* TODO: bad hack: this code can only differentiate between - arrays and structs */ - auto d_arg = qvariant_cast(a_info); - switch (d_arg.currentType()) { - case QDBusArgument::StructureType: { - auto ifaceItem = new AssignableItem; - ifaceItem->setEditable(true); - //ifaceItem->setCheckable(true); - auto proxy = new AssignableProxy(node.value().path, conn, this); - connect(ifaceItem, &AssignableItem::assignableDataChanged, - [=](QVariant v) { - proxy->setValue(v); - ifaceItem->setData(unappliedColor(), Qt::BackgroundRole); - }); - - connect(proxy, &AssignableProxy::applied, [=](auto err) { - // Fade out result color - auto startColor = (err.has_value()) ? errorColor() - : successColor(); - auto anim = new QVariantAnimation; - anim->setDuration(fadeOutTime()); - anim->setStartValue(startColor); - anim->setEndValue(QPalette().color(QPalette::Base)); - - connect(anim, &QVariantAnimation::valueChanged, [=](QVariant v) { - QVariant iv; - iv.setValue(v.value()); - ifaceItem->setData(iv, Qt::BackgroundRole); - }); - anim->start(QAbstractAnimation::DeleteWhenStopped); - }); - - connect(this, &DeviceModel::changesApplied, [=] { - proxy->apply(); - }); - - TCDBus::Range r; - d_arg >> r; - QVariant v; - AssignableItemData data(r.toAssignableInfo()); - v.setValue(data); - ifaceItem->setData(v, Role::AssignableRole); - //ifaceItem->setText(r.min.variant().toString()); - rowItems.append(ifaceItem); - break; - } - case QDBusArgument::ArrayType: { - auto ifaceItem = new QStandardItem; - QVector e; - d_arg >> e; - QVariant v; - v.setValue(e); - ifaceItem->setData(v, Role::AssignableRole); - ifaceItem->setText(e.first().name); - rowItems.append(ifaceItem); - break; - } - default: - break; - } - } + p::match(node.value().interface) ( + pattern("org.tuxclocker.Assignable") = [=, &rowItems]{ + if_let(pattern(some(arg)) = setupAssignable(node, conn)) + = [&](auto item) { + rowItems.append(item); + }; + }, + pattern("org.tuxclocker.DynamicReadable") = [=, &rowItems] { + if_let(pattern(some(arg)) = setupDynReadable(node, conn)) + = [&](auto item) { + rowItems.append(item); + }; + }, + pattern(_) = []{} + ); item->appendRow(rowItems); for (auto c_node : node.children()) @@ -115,3 +67,142 @@ DeviceModel::DeviceModel(TC::TreeNode root, QObject *parent) for (auto &node : root.children()) traverse(node, rootItem); } + +EnumerationVec toEnumVec(QVector enums) { + return transform([](auto e) { + return Enumeration{e.name.toStdString(), e.key}; + }, enums.toStdVector()); +} + +void DeviceModel::connectAssignable(TC::TreeNode node, + QDBusConnection conn, AssignableItem *ifaceItem) { + auto proxy = new AssignableProxy(node.value().path, conn, this); + connect(ifaceItem, &AssignableItem::assignableDataChanged, + [=](QVariant v) { + proxy->setValue(v); + ifaceItem->setData(unappliedColor(), Qt::BackgroundRole); + }); + + connect(proxy, &AssignableProxy::applied, [=](auto err) { + // Fade out result color + auto startColor = (err.has_value()) ? errorColor() + : successColor(); + auto anim = new QVariantAnimation; + anim->setDuration(fadeOutTime()); + anim->setStartValue(startColor); + anim->setEndValue(QPalette().color(QPalette::Base)); + + connect(anim, &QVariantAnimation::valueChanged, [=](QVariant v) { + QVariant iv; + iv.setValue(v.value()); + ifaceItem->setData(iv, Qt::BackgroundRole); + }); + anim->start(QAbstractAnimation::DeleteWhenStopped); + }); + + connect(this, &DeviceModel::changesApplied, [=] { + proxy->apply(); + }); +} + +std::optional DeviceModel::setupAssignable( + TC::TreeNode node, QDBusConnection conn) { + QDBusInterface ifaceNode("org.tuxclocker", node.value().path, + "org.tuxclocker.Assignable", conn); + // Should never fail + auto a_info = + qvariant_cast(ifaceNode.property("assignableInfo")) + .variant(); + /* TODO: bad hack: this code can only differentiate between + arrays and structs: make it based on signature instead */ + auto d_arg = qvariant_cast(a_info); + switch (d_arg.currentType()) { + case QDBusArgument::StructureType: { + auto ifaceItem = new AssignableItem; + ifaceItem->setEditable(true); + //ifaceItem->setCheckable(true); + /*auto proxy = new AssignableProxy(node.value().path, conn, this); + connect(ifaceItem, &AssignableItem::assignableDataChanged, + [=](QVariant v) { + proxy->setValue(v); + ifaceItem->setData(unappliedColor(), Qt::BackgroundRole); + }); + + connect(proxy, &AssignableProxy::applied, [=](auto err) { + // Fade out result color + auto startColor = (err.has_value()) ? errorColor() + : successColor(); + auto anim = new QVariantAnimation; + anim->setDuration(fadeOutTime()); + anim->setStartValue(startColor); + anim->setEndValue(QPalette().color(QPalette::Base)); + + connect(anim, &QVariantAnimation::valueChanged, [=](QVariant v) { + QVariant iv; + iv.setValue(v.value()); + ifaceItem->setData(iv, Qt::BackgroundRole); + }); + anim->start(QAbstractAnimation::DeleteWhenStopped); + }); + + connect(this, &DeviceModel::changesApplied, [=] { + proxy->apply(); + });*/ + + connectAssignable(node, conn, ifaceItem); + + TCDBus::Range r; + d_arg >> r; + QVariant v; + AssignableItemData data(r.toAssignableInfo()); + v.setValue(data); + ifaceItem->setData(v, Role::AssignableRole); + return ifaceItem; + } + case QDBusArgument::ArrayType: { + auto ifaceItem = new AssignableItem; + + connectAssignable(node, conn, ifaceItem); + + QVector e; + // TODO: convert to EnumerationVec + d_arg >> e; + QVariant v; + AssignableItemData data(toEnumVec(e)); + v.setValue(data); + ifaceItem->setData(v, Role::AssignableRole); + ifaceItem->setText(e.first().name); + return ifaceItem; + } + default: + return std::nullopt; + } +} + +std::optional DeviceModel::setupDynReadable( + TC::TreeNode node, QDBusConnection conn) { + auto item = new QStandardItem; + auto proxy = new DynamicReadableProxy(node.value().path, conn, this); + + connect(proxy, &DynamicReadableProxy::valueChanged, [=](ReadResult res) { + p::match(res)( + pattern(as(arg)) = [=](auto rv) { + p::match(rv)( + pattern(as(arg)) = [=](auto d) { + item->setText(QString::number(d)); + }, + pattern(as(arg)) = [=](auto i) { + item->setText(QString::number(i)); + }, + pattern(as(arg)) = [=](auto u) { + item->setText(QString::number(u)); + }, + pattern(_) = []{} + ); + }, + pattern(_) = []{} + ); + }); + + return item; +} diff --git a/src/tuxclocker-qt/data/DeviceModel.hpp b/src/tuxclocker-qt/data/DeviceModel.hpp index 0493b11..c476e35 100644 --- a/src/tuxclocker-qt/data/DeviceModel.hpp +++ b/src/tuxclocker-qt/data/DeviceModel.hpp @@ -12,10 +12,11 @@ #include #include -namespace p = mpark::patterns; namespace TC = TuxClocker; namespace TCDBus = TuxClocker::DBus; +// Why the fuck do I have to forward declare this? +class AssignableItem; class DeviceModel : public QStandardItemModel { public: @@ -40,8 +41,15 @@ signals: private: Q_OBJECT - constexpr int fadeOutTime() {return 5000;} - constexpr int transparency() {return 120;} + // Separate handling interfaces since otherwise we run out of columns + void connectAssignable(TC::TreeNode node, + QDBusConnection conn, AssignableItem *ifaceItem); + std::optional setupAssignable( + TC::TreeNode node, QDBusConnection conn); + std::optional setupDynReadable( + TC::TreeNode node, QDBusConnection conn); + constexpr int fadeOutTime() {return 5000;} // milliseconds + constexpr int transparency() {return 120;} // 0-255 // Colors for items QColor errorColor() {return QColor(255, 0, 0, transparency());} // red QColor unappliedColor() {return QColor(255, 255, 0, transparency());} // yellow diff --git a/src/tuxclocker-qt/data/DeviceModelDelegate.cpp b/src/tuxclocker-qt/data/DeviceModelDelegate.cpp index b3c6eed..90efcea 100644 --- a/src/tuxclocker-qt/data/DeviceModelDelegate.cpp +++ b/src/tuxclocker-qt/data/DeviceModelDelegate.cpp @@ -2,6 +2,7 @@ #include "DeviceModel.hpp" #include +#include #include #include @@ -26,8 +27,13 @@ QWidget *DeviceModelDelegate::createEditor(QWidget *parent, }, pattern(as>(arg)) = [&](auto dr) { editor = new DoubleRangeEditor(dr, parent); - }); - }); + } + ); + }, + pattern(as(arg)) = [&](auto ev) { + editor = new EnumEditor(ev, parent); + } + ); } return editor; } diff --git a/src/tuxclocker-qt/widgets/EnumEditor.cpp b/src/tuxclocker-qt/widgets/EnumEditor.cpp new file mode 100644 index 0000000..d935f9e --- /dev/null +++ b/src/tuxclocker-qt/widgets/EnumEditor.cpp @@ -0,0 +1,59 @@ +#include "EnumEditor.hpp" + +#include +#include +#include +#include +#include +#include + +using namespace fplus; + +constexpr int KeyRole = Qt::UserRole + 1; // Stores the associated key (uint) + +EnumEditor::EnumEditor(QWidget *parent) : AbstractAssignableEditor(parent) { + setAutoFillBackground(true); + auto layout = new QHBoxLayout(this); + layout->setMargin(0); + + m_comboBox = new QComboBox; + layout->addWidget(m_comboBox); +} + +EnumEditor::EnumEditor(TuxClocker::Device::EnumerationVec enums, QWidget *parent) + : EnumEditor(parent) { + auto qStrings = QVector::fromStdVector(transform([](auto e) { + return QString::fromStdString(e.name); + }, enums)); + + for (uint i = 0; i < qStrings.length(); i++) { + auto item = new QStandardItem; + item->setText(qStrings[i]); + item->setData(i, KeyRole); + m_model.appendRow(item); + } + + m_model.sort(0); + m_comboBox->setModel(&m_model); +} + +QVariant EnumEditor::assignableData() { + auto r = m_model.index(m_comboBox->currentIndex(), 0) + .data(KeyRole).toUInt(); + return r; +} +QString EnumEditor::displayData() { + auto r = m_model.index(m_comboBox->currentIndex(), 0) + .data(Qt::DisplayRole).toString(); + return r; +} +void EnumEditor::setAssignableData(QVariant data) { + // TODO: make worst case better than O(n) + auto u = data.toUInt(); + for (int i = 0; i < m_comboBox->model()->rowCount(); i++) { + if (m_comboBox->itemData(i, KeyRole).toUInt() == u) { + m_comboBox->setCurrentIndex(i); + break; + } + } +} diff --git a/src/tuxclocker-qt/widgets/EnumEditor.hpp b/src/tuxclocker-qt/widgets/EnumEditor.hpp new file mode 100644 index 0000000..66e894e --- /dev/null +++ b/src/tuxclocker-qt/widgets/EnumEditor.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "AbstractAssignableEditor.hpp" + +#include +#include +#include + +class EnumEditor : public AbstractAssignableEditor { +public: + EnumEditor(QWidget *parent = nullptr); + EnumEditor(TuxClocker::Device::EnumerationVec enums, QWidget *parent = nullptr); + virtual QVariant assignableData() override; + virtual QString displayData() override; + virtual void setAssignableData(QVariant data) override; +private: + QComboBox *m_comboBox; + QStandardItemModel m_model; +};