qt: add basic assignable editing

This commit is contained in:
Jussi Kuokkanen 2020-05-24 17:41:29 +03:00
parent 138dc7d67f
commit 8b80c9c9a6
13 changed files with 398 additions and 15 deletions

View File

@ -4,13 +4,20 @@
#include <QDBusMetaType>
#include <QDBusReply>
#include <QDebug>
#include <QStandardItemModel>
#include <QString>
#include <QTreeView>
#include <QVector>
#include <Tree.hpp>
#include <DeviceModel.hpp>
#include <DeviceModelDelegate.hpp>
#include "MainWindow.hpp"
namespace TCDBus = TuxClocker::DBus;
using namespace TuxClocker;
Q_DECLARE_METATYPE(TCDBus::DeviceNode)
Q_DECLARE_METATYPE(TCDBus::FlatTreeNode<TCDBus::DeviceNode>)
@ -24,10 +31,26 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
QDBusReply<QVector<TCDBus::FlatTreeNode<TCDBus::DeviceNode>>> reply =
tuxclockerd.call("flatDeviceTree");
// Convert to non-dbus flat tree
FlatTree<TCDBus::DeviceNode> flatTree;
if (reply.isValid()) {
auto flatTree = reply.value();
for (auto &f_node : flatTree)
qDebug() << f_node.childIndices << f_node.value.interface;
auto dbusFlatTree = reply.value();
for (auto &f_node : dbusFlatTree) {
//qDebug() << f_node.value.interface << f_node.value.path;
FlatTreeNode<TCDBus::DeviceNode> node{
f_node.value,
f_node.childIndices.toStdVector()
};
flatTree.nodes.push_back(node);
}
}
auto root = flatTree.toTree(flatTree);
auto view = new QTreeView;
view->setItemDelegate(new DeviceModelDelegate);
auto model = new DeviceModel(root);
view->setModel(model);
setCentralWidget(view);
}

View File

@ -0,0 +1,22 @@
#pragma once
//#include "DeviceModel.hpp"
#include <QObject>
#include <QStandardItem>
// Class for forwarding changes in DeviceModel's assignables
// Forgive me for the sin of multiple inheritance
class AssignableItem : public QObject, public QStandardItem {
public:
AssignableItem(QObject *parent = nullptr) : QObject(parent), QStandardItem() {
}
void setData(const QVariant &v, int role = Qt::UserRole + 1) {
//if (role == DeviceModel::AssignableRole) ;
}
signals:
void assignableDataChanged(QVariant value);
private:
Q_OBJECT
};

View File

@ -0,0 +1,22 @@
#pragma once
#include <Device.hpp>
#include <QVariant>
namespace TC = TuxClocker;
class AssignableItemData {
public:
AssignableItemData() {m_enabled = false;}
AssignableItemData(TC::Device::AssignableInfo info) : m_info(info) {
m_enabled = false;
}
TC::Device::AssignableInfo assignableInfo() {return m_info;}
void setValue(QVariant v) {m_targetValue = v;}
QVariant value() {return m_targetValue;}
private:
bool m_enabled;
TC::Device::AssignableInfo m_info;
QVariant m_targetValue;
};
Q_DECLARE_METATYPE(TC::Device::AssignableInfo)

View File

@ -0,0 +1,78 @@
#include "DeviceModel.hpp"
Q_DECLARE_METATYPE(AssignableItemData)
Q_DECLARE_METATYPE(TCDBus::Enumeration)
Q_DECLARE_METATYPE(TCDBus::Range)
DeviceModel::DeviceModel(TC::TreeNode<TCDBus::DeviceNode> root, QObject *parent) :
QStandardItemModel(parent) {
qDBusRegisterMetaType<TCDBus::Enumeration>();
qDBusRegisterMetaType<QVector<TCDBus::Enumeration>>();
qDBusRegisterMetaType<TCDBus::Range>();
/* Data storage:
- Interface column should store assignable info for editors
- Name colums should store the interface type for filtering
- Parametrization/connection data, where? */
setColumnCount(2);
std::function<void(TC::TreeNode<TCDBus::DeviceNode> node, QStandardItem*)> traverse;
traverse = [&traverse](auto node, auto item) {
auto conn = QDBusConnection::systemBus();
QDBusInterface nodeIface("org.tuxclocker", node.value().path, "org.tuxclocker.Node", conn);
auto nodeName = nodeIface.property("name").toString();
QList<QStandardItem*> rowItems;
auto nameItem = new QStandardItem;
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<QDBusVariant>(ifaceNode.property("assignableInfo"))
.variant();
/* TODO: bad hack: this code can only differentiate between
arrays and structs */
auto d_arg = qvariant_cast<QDBusArgument>(a_info);
switch (d_arg.currentType()) {
case QDBusArgument::StructureType: {
auto ifaceItem = new QStandardItem;
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<TCDBus::Enumeration> 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;
}
}
item->appendRow(rowItems);
for (auto c_node : node.children())
traverse(c_node, nameItem);
};
auto rootItem = invisibleRootItem();
for (auto &node : root.children())
traverse(node, rootItem);
}

View File

@ -0,0 +1,35 @@
#pragma once
//#include "AssignableItem.hpp"
#include "AssignableItemData.hpp"
#include <DBusTypes.hpp>
#include <Device.hpp>
#include <patterns.hpp>
#include <Tree.hpp>
#include <QDBusConnection>
#include <QDBusInterface>
#include <QStandardItemModel>
namespace p = mpark::patterns;
namespace TC = TuxClocker;
namespace TCDBus = TuxClocker::DBus;
class DeviceModel : public QStandardItemModel {
public:
DeviceModel(TC::TreeNode<TCDBus::DeviceNode> root, QObject *parent = nullptr);
enum ColumnType {
Name = 0, // Node name
Interface = 1 // Column for presenting interfaces
};
enum Role {
AssignableRole = Qt::UserRole, // Holds the data about the assignable
ConnectionRole // Data about the connection
};
enum class FilterFlag {
};
};

View File

@ -0,0 +1,58 @@
#include "DeviceModelDelegate.hpp"
#include "DeviceModel.hpp"
#include <DoubleRangeEditor.hpp>
#include <IntRangeEditor.hpp>
#include <patterns.hpp>
using namespace TuxClocker::Device;
using namespace mpark::patterns;
Q_DECLARE_METATYPE(AssignableItemData)
DeviceModelDelegate::DeviceModelDelegate(QObject* parent) :
QStyledItemDelegate(parent) {}
QWidget *DeviceModelDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem&, const QModelIndex &index) const {
auto v = index.data(DeviceModel::AssignableRole);
QWidget *editor = nullptr;
if (v.canConvert<AssignableItemData>()) {
match(v.value<AssignableItemData>().assignableInfo())
(pattern(as<RangeInfo>(arg)) = [&](auto r_info) {
match(r_info)
(pattern(as<Range<int>>(arg)) = [&](auto ir) {
editor = new IntRangeEditor(ir, parent);
},
pattern(as<Range<double>>(arg)) = [&](auto dr) {
editor = new DoubleRangeEditor(dr, parent);
});
});
}
return editor;
}
void DeviceModelDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const {
auto v = index.data(DeviceModel::AssignableRole);
if (v.canConvert<AssignableItemData>()) {
auto data = v.value<AssignableItemData>().value();
auto a_editor = static_cast<AbstractAssignableEditor*>(editor);
a_editor->setAssignableData(data);
}
}
void DeviceModelDelegate::setModelData(QWidget *editor,
QAbstractItemModel *model, const QModelIndex &index) const {
auto v = index.data(DeviceModel::AssignableRole);
if (v.canConvert<AssignableItemData>()) {
auto a_editor = static_cast<AbstractAssignableEditor*>(editor);
auto data = index.data(DeviceModel::AssignableRole)
.value<AssignableItemData>();
data.setValue(a_editor->assignableData());
QVariant v;
v.setValue(data);
model->setData(index, a_editor->displayData(), Qt::DisplayRole);
model->setData(index, v, DeviceModel::AssignableRole);
}
}

View File

@ -0,0 +1,15 @@
#pragma once
#include <QStyledItemDelegate>
class DeviceModelDelegate : public QStyledItemDelegate {
public:
DeviceModelDelegate(QObject *parent = nullptr);
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const;
//void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
// const QModelIndex &index) const;
void setEditorData(QWidget *editor, const QModelIndex &index) const;
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const;
};

View File

@ -2,20 +2,11 @@
#include "MainWindow.hpp"
#include <QDBusConnection>
int main(int argc, char **argv) {
QApplication app(argc, argv);
MainWindow mw;
mw.show();
QObject o;
auto conn = QDBusConnection::sessionBus();
conn.registerObject("/", &o);
conn.registerService("org.tuxclockerqt");
return app.exec();
}

View File

@ -1,15 +1,22 @@
qt5 = import('qt5')
qt5_dep = dependency('qt5', modules: ['DBus', 'Charts', 'Widgets'])
moc_files = qt5.preprocess(moc_headers: ['MainWindow.hpp'],
moc_files = qt5.preprocess(moc_headers: ['MainWindow.hpp',
'data/AssignableItem.hpp'],
dependencies: qt5_dep)
sources = ['main.cpp',
'data/DeviceModel.cpp',
'data/DeviceModelDelegate.cpp',
'MainWindow.cpp']
local_incdir = include_directories(['data',
'widgets',
'../include/deps/patterns/include/mpark'])
executable('tuxclocker-qt',
moc_files,
sources,
dependencies: qt5_dep,
include_directories: [incdir],
include_directories: [incdir, local_incdir],
install: true)

View File

@ -0,0 +1,12 @@
#pragma once
#include <QWidget>
class AbstractAssignableEditor : public QWidget {
public:
AbstractAssignableEditor(QWidget *parent = nullptr) : QWidget(parent) {}
virtual ~AbstractAssignableEditor() {}
virtual QVariant assignableData() = 0;
virtual QString displayData() = 0;
virtual void setAssignableData(QVariant data) = 0;
};

View File

@ -0,0 +1,65 @@
#pragma once
#include "AbstractAssignableEditor.hpp"
#include <Device.hpp>
#include <tgmath.h>
#include <QDoubleSpinBox>
#include <QHBoxLayout>
#include <QSlider>
namespace TC = TuxClocker;
class DoubleRangeEditor : public AbstractAssignableEditor {
public:
DoubleRangeEditor(QWidget *parent = nullptr)
: AbstractAssignableEditor(parent) {
setAutoFillBackground(true);
m_layout = new QHBoxLayout(this);
m_spinBox = new QDoubleSpinBox;
m_slider = new QSlider(Qt::Horizontal);
connect(m_slider, &QSlider::rangeChanged, [this](int min, int max) {
m_spinBox->setRange(toDouble(min), toDouble(max));
});
connect(m_spinBox, QOverload<double>::of(&QDoubleSpinBox::valueChanged),
[this](double val) {
m_slider->setValue(toInt(val));
});
connect(m_slider, &QSlider::valueChanged, [this](int val) {
m_spinBox->setValue(toDouble(val));
});
m_layout->setMargin(0);
m_layout->addWidget(m_slider);
m_layout->addWidget(m_spinBox);
setLayout(m_layout);
}
DoubleRangeEditor(TC::Device::Range<double> range, QWidget *parent = nullptr)
: DoubleRangeEditor(parent) {
setRange(range);
}
virtual QVariant assignableData() override {return m_spinBox->value();}
virtual QString displayData() override {
return QString::number(m_spinBox->value());
}
virtual void setAssignableData(QVariant data) override {
m_spinBox->setValue(data.toDouble());
}
void setRange(TC::Device::Range<double> range) {
m_slider->setRange(toInt(range.min), toInt(range.max));
}
private:
// How many digits are displayed after the dot
constexpr int doubleDecimals() {return 2;}
double toDouble(int i) {
return static_cast<double>(i) / pow(10, doubleDecimals());
}
int toInt(double d) {
return static_cast<int>(d * pow(10, doubleDecimals()));
}
QDoubleSpinBox *m_spinBox;
QHBoxLayout *m_layout;
QSlider *m_slider;
};

View File

@ -0,0 +1,50 @@
#pragma once
#include "AbstractAssignableEditor.hpp"
#include <Device.hpp>
#include <QSlider>
#include <QSpinBox>
#include <QHBoxLayout>
#include <QWidget>
namespace TC = TuxClocker;
class IntRangeEditor : public AbstractAssignableEditor {
public:
IntRangeEditor(QWidget *parent = nullptr) : AbstractAssignableEditor(parent) {
setAutoFillBackground(true);
m_layout = new QHBoxLayout(this);
m_layout->setMargin(0);
m_slider = new QSlider(Qt::Horizontal);
m_spinBox = new QSpinBox;
m_layout->addWidget(m_slider);
m_layout->addWidget(m_spinBox);
setLayout(m_layout);
connect(m_slider, &QSlider::rangeChanged, m_spinBox, &QSpinBox::setRange);
connect(m_slider, &QSlider::valueChanged, m_spinBox, &QSpinBox::setValue);
connect(m_spinBox, QOverload<int>::of(&QSpinBox::valueChanged), m_slider, &QSlider::setValue);
}
IntRangeEditor(TC::Device::Range<int> range, QWidget *parent = nullptr)
: IntRangeEditor(parent) {
setRange(range);
}
virtual QVariant assignableData() override {return m_slider->value();}
virtual QString displayData() override {
return QString::number(m_slider->value());
}
virtual void setAssignableData(QVariant data) override {m_slider->setValue(data.toInt());}
void setRange(TC::Device::Range<int> range) {
m_slider->setRange(range.min, range.max);
}
int value() {return m_slider->value();}
private:
QHBoxLayout *m_layout;
QSlider *m_slider;
QSpinBox *m_spinBox;
};

View File

@ -0,0 +1,5 @@
#pragma once
// Primary widget for viewing the tuxclocker device tree.