Files
tuxclocker/src/tuxclocker-qt/data/DeviceModel.cpp

299 lines
9.3 KiB
C++
Raw Normal View History

2020-05-24 17:41:29 +03:00
#include "DeviceModel.hpp"
#include "AssignableProxy.hpp"
2020-06-03 23:24:07 +03:00
#include "DynamicReadableProxy.hpp"
2020-07-21 17:50:01 +03:00
#include "qnamespace.h"
#include "qstandarditemmodel.h"
2020-06-03 23:24:07 +03:00
#include <fplus/fplus.hpp>
2020-06-15 13:01:50 +03:00
#include <QApplication>
2020-06-20 14:55:08 +03:00
#include <QDBusReply>
#include <QDebug>
2020-06-15 13:01:50 +03:00
#include <QStyle>
#include <QVariantAnimation>
2020-06-03 23:24:07 +03:00
// 'match' is a method in QAbstractItemModel :(
namespace p = mpark::patterns;
using namespace fplus;
using namespace mpark::patterns;
2020-06-03 23:24:07 +03:00
using namespace TuxClocker::Device;
2020-05-24 17:41:29 +03:00
Q_DECLARE_METATYPE(AssignableItemData)
2020-07-21 17:50:01 +03:00
Q_DECLARE_METATYPE(AssignableProxy*)
Q_DECLARE_METATYPE(DynamicReadableProxy*)
2020-05-24 17:41:29 +03:00
Q_DECLARE_METATYPE(TCDBus::Enumeration)
Q_DECLARE_METATYPE(TCDBus::Range)
2020-06-03 23:24:07 +03:00
Q_DECLARE_METATYPE(EnumerationVec)
2020-05-24 17:41:29 +03:00
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, this](auto node, auto item) {
2020-05-24 17:41:29 +03:00
auto conn = QDBusConnection::systemBus();
QDBusInterface nodeIface("org.tuxclocker",
node.value().path,"org.tuxclocker.Node", conn);
2020-05-24 17:41:29 +03:00
auto nodeName = nodeIface.property("name").toString();
QList<QStandardItem*> rowItems;
auto nameItem = new QStandardItem;
nameItem->setText(nodeName);
rowItems.append(nameItem);
2020-06-03 23:24:07 +03:00
p::match(node.value().interface) (
pattern("org.tuxclocker.Assignable") = [=, &rowItems]{
if_let(pattern(some(arg)) = setupAssignable(node, conn))
= [&](auto item) {
2020-06-15 13:01:50 +03:00
nameItem->setData(Assignable, InterfaceTypeRole);
auto icon = assignableIcon();
nameItem->setData(icon, Qt::DecorationRole);
2020-06-03 23:24:07 +03:00
rowItems.append(item);
};
},
pattern("org.tuxclocker.DynamicReadable") = [=, &rowItems] {
if_let(pattern(some(arg)) = setupDynReadable(node, conn))
= [&](auto item) {
2020-06-15 13:01:50 +03:00
auto icon = dynamicReadableIcon();
nameItem->setData(icon, Qt::DecorationRole);
nameItem->setData(DeviceModel::DynamicReadable,
InterfaceTypeRole);
2020-06-03 23:24:07 +03:00
rowItems.append(item);
2020-07-21 17:50:01 +03:00
//qDebug() << item->data(DynamicReadableProxyRole);
2020-06-03 23:24:07 +03:00
};
},
2020-06-20 14:55:08 +03:00
pattern("org.tuxclocker.StaticReadable") = [=, &rowItems] {
if_let(pattern(some(arg)) = setupStaticReadable(node, conn))
= [&](auto item) {
auto icon = staticReadableIcon();
nameItem->setData(icon, Qt::DecorationRole);
nameItem->setData(DeviceModel::StaticReadable,
InterfaceTypeRole);
rowItems.append(item);
};
},
2020-06-03 23:24:07 +03:00
pattern(_) = []{}
);
2020-05-24 17:41:29 +03:00
item->appendRow(rowItems);
for (auto c_node : node.children())
traverse(c_node, nameItem);
};
auto rootItem = invisibleRootItem();
for (auto &node : root.children())
traverse(node, rootItem);
}
2020-06-03 23:24:07 +03:00
EnumerationVec toEnumVec(QVector<TCDBus::Enumeration> enums) {
return transform([](auto e) {
return Enumeration{e.name.toStdString(), e.key};
}, enums.toStdVector());
}
2020-07-21 17:50:01 +03:00
std::optional<const AssignableProxy*>
DeviceModel::assignableProxyFromItem(QStandardItem *item) {
return (m_assignableProxyHash.contains(item)) ?
std::optional(m_assignableProxyHash.value(item)) :
std::nullopt;
}
2020-06-04 02:03:42 +03:00
QStandardItem *DeviceModel::createAssignable(TC::TreeNode<TCDBus::DeviceNode> node,
QDBusConnection conn, AssignableItemData itemData) {
auto ifaceItem = new AssignableItem(this);
2020-06-03 23:24:07 +03:00
auto proxy = new AssignableProxy(node.value().path, conn, this);
2020-07-21 17:50:01 +03:00
connect(proxy, &AssignableProxy::connectionValueChanged, [=] (auto result, auto text) {
p::match(result) (
pattern(as<QVariant>(arg)) = [=](auto v) {
QVariant data;
data.setValue(connectionColor());
ifaceItem->setData(data, Qt::BackgroundRole);
ifaceItem->setText(text);
//qDebug() << text;
},
pattern(_) = []{}
);
});
QVariant pv;
pv.setValue(proxy);
ifaceItem->setData(pv, AssignableProxyRole);
2020-06-04 02:03:42 +03:00
QVariant v;
v.setValue(itemData);
ifaceItem->setData(v, AssignableRole);
2020-06-15 13:01:50 +03:00
ifaceItem->setText("No value set");
2020-06-04 02:03:42 +03:00
2020-06-03 23:24:07 +03:00
connect(ifaceItem, &AssignableItem::assignableDataChanged,
[=](QVariant v) {
// Only show checkbox when value has been changed
ifaceItem->setCheckable(true);
ifaceItem->setCheckState(Qt::Checked);
2020-06-03 23:24:07 +03:00
proxy->setValue(v);
ifaceItem->setData(unappliedColor(), Qt::BackgroundRole);
});
connect(ifaceItem, &AssignableItem::committalChanged, [=](bool on) {
QVariant colorData = (on) ? unappliedColor() : QVariant();
ifaceItem->setData(colorData, Qt::BackgroundRole);
});
2020-06-03 23:24:07 +03:00
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<QColor>());
ifaceItem->setData(iv, Qt::BackgroundRole);
});
connect(anim, &QVariantAnimation::finished, [=] {
// Set invalid color to 'reset' the color
ifaceItem->setData(QVariant(), Qt::BackgroundRole);
});
2020-06-03 23:24:07 +03:00
anim->start(QAbstractAnimation::DeleteWhenStopped);
});
connect(this, &DeviceModel::changesApplied, [=] {
// Don't apply if unchecked
if (ifaceItem->checkState() == Qt::Checked) {
ifaceItem->setCheckState(Qt::Unchecked);
ifaceItem->setCheckable(false);
// What the fuck do I need to this for?
ifaceItem->setData(QVariant(), Qt::CheckStateRole);
proxy->apply();
}
2020-06-03 23:24:07 +03:00
});
2020-06-04 02:03:42 +03:00
return ifaceItem;
2020-06-03 23:24:07 +03:00
}
2020-07-21 17:50:01 +03:00
QVariant DeviceModel::data(const QModelIndex &index, int role) const {
if (index.row() != DeviceModel::NameColumn && role == DeviceModel::NodeNameRole) {
// Get name from adjacent column
auto nameIndex =
this->index(index.row(), DeviceModel::NameColumn, index.parent());
return nameIndex.data(Qt::DisplayRole);
}
if (index.column() != InterfaceColumn && role == DynamicReadableProxyRole) {
auto idx =
this->index(index.row(), DeviceModel::InterfaceColumn, index.parent());
return idx.data(DynamicReadableProxyRole);
}
return QStandardItemModel::data(index, role);
}
2020-06-03 23:24:07 +03:00
std::optional<QStandardItem*> DeviceModel::setupAssignable(
TC::TreeNode<TCDBus::DeviceNode> node, QDBusConnection conn) {
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: make it based on signature instead */
auto d_arg = qvariant_cast<QDBusArgument>(a_info);
switch (d_arg.currentType()) {
case QDBusArgument::StructureType: {
TCDBus::Range r;
d_arg >> r;
AssignableItemData data(r.toAssignableInfo());
2020-06-04 02:03:42 +03:00
return createAssignable(node, conn, data);
2020-06-03 23:24:07 +03:00
}
case QDBusArgument::ArrayType: {
QVector<TCDBus::Enumeration> e;
d_arg >> e;
AssignableItemData data(toEnumVec(e));
2020-06-04 02:03:42 +03:00
return createAssignable(node, conn, data);
2020-06-03 23:24:07 +03:00
}
default:
return std::nullopt;
}
}
2020-06-05 14:28:39 +03:00
template <typename T>
void updateReadItemText(QStandardItem *item, T value,
std::optional<QString> unit) {
// TODO: this can be made a lot (around 3x) faster by using direct copying
// Form a string of the form "1000 MHz" if has unit
auto text = (unit.has_value()) ?
QString("%1 %2").arg(value).arg(unit.value()) :
QString("%1").arg(value);
item->setText(text);
2020-07-21 17:50:01 +03:00
//qDebug() << item->data(DeviceModel::DynamicReadableProxyRole);
2020-06-05 14:28:39 +03:00
}
2020-06-03 23:24:07 +03:00
std::optional<QStandardItem*> DeviceModel::setupDynReadable(
TC::TreeNode<TCDBus::DeviceNode> node, QDBusConnection conn) {
auto item = new QStandardItem;
auto proxy = new DynamicReadableProxy(node.value().path, conn, this);
2020-07-21 17:50:01 +03:00
QVariant v;
v.setValue(proxy);
item->setData(v, DynamicReadableProxyRole);
auto unit = proxy->unit();
2020-06-03 23:24:07 +03:00
connect(proxy, &DynamicReadableProxy::valueChanged, [=](ReadResult res) {
p::match(res)(
pattern(as<ReadableValue>(arg)) = [=](auto rv) {
p::match(rv)(
pattern(as<double>(arg)) = [=](auto d) {
2020-06-05 14:28:39 +03:00
updateReadItemText(item, d, unit);
2020-06-03 23:24:07 +03:00
},
pattern(as<int>(arg)) = [=](auto i) {
2020-06-05 14:28:39 +03:00
updateReadItemText(item, i, unit);
2020-06-03 23:24:07 +03:00
},
pattern(as<uint>(arg)) = [=](auto u) {
2020-06-05 14:28:39 +03:00
updateReadItemText(item, u, unit);
2020-06-03 23:24:07 +03:00
},
pattern(_) = []{}
);
},
pattern(_) = []{}
);
});
return item;
}
2020-06-20 14:55:08 +03:00
std::optional<QStandardItem*> DeviceModel::setupStaticReadable(
TC::TreeNode<TCDBus::DeviceNode> node, QDBusConnection conn) {
QDBusInterface staticIface("org.tuxclocker", node.value().path,
"org.tuxclocker.StaticReadable", conn);
auto value = staticIface.property("value")
.value<QDBusVariant>().variant().toString();
// Workaround from DynamicReadableProxy for getting property with custom type
QDBusInterface propIface("org.tuxclocker", node.value().path,
"org.freedesktop.DBus.Properties", conn);
QDBusReply<QDBusVariant> reply =
propIface.call("Get", "org.tuxclocker.StaticReadable", "unit");
if (!reply.isValid())
return std::nullopt;
auto arg = reply.value().variant().value<QDBusArgument>();
TCDBus::Result<QString> unit;
arg >> unit;
if (!unit.error)
value += " " + unit.value;
auto item = new QStandardItem;
item->setData(value, Qt::DisplayRole);
return item;
}