2020-05-24 17:41:29 +03:00
|
|
|
#include "DeviceModel.hpp"
|
|
|
|
|
|
2020-06-01 16:48:50 +03:00
|
|
|
#include "AssignableProxy.hpp"
|
2020-06-03 23:24:07 +03:00
|
|
|
#include "DynamicReadableProxy.hpp"
|
|
|
|
|
#include <fplus/fplus.hpp>
|
2020-06-15 13:01:50 +03:00
|
|
|
#include <QApplication>
|
2020-06-20 14:55:08 +03:00
|
|
|
#include <QDBusReply>
|
2020-06-01 16:48:50 +03:00
|
|
|
#include <QDebug>
|
2020-06-15 13:01:50 +03:00
|
|
|
#include <QStyle>
|
2020-06-01 16:48:50 +03:00
|
|
|
#include <QVariantAnimation>
|
|
|
|
|
|
2020-06-03 23:24:07 +03:00
|
|
|
// 'match' is a method in QAbstractItemModel :(
|
|
|
|
|
namespace p = mpark::patterns;
|
|
|
|
|
using namespace fplus;
|
2020-06-01 16:48:50 +03:00
|
|
|
using namespace mpark::patterns;
|
2020-06-03 23:24:07 +03:00
|
|
|
using namespace TuxClocker::Device;
|
2020-06-01 16:48:50 +03:00
|
|
|
|
2020-05-24 17:41:29 +03:00
|
|
|
Q_DECLARE_METATYPE(AssignableItemData)
|
|
|
|
|
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);
|
|
|
|
|
|
2020-06-01 16:48:50 +03:00
|
|
|
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();
|
2020-06-01 16:48:50 +03:00
|
|
|
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 style = QApplication::style();
|
|
|
|
|
//auto icon = style->standardIcon(QStyle::SP_ComputerIcon);
|
|
|
|
|
auto icon = assignableIcon();
|
|
|
|
|
//QIcon icon("/home/jussi/Downloads/wrench.png");
|
|
|
|
|
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-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-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-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) {
|
2020-06-08 13:56:11 +03:00
|
|
|
// 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);
|
|
|
|
|
});
|
|
|
|
|
|
2020-06-08 13:56:11 +03:00
|
|
|
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);
|
|
|
|
|
});
|
2020-06-07 17:41:38 +03:00
|
|
|
|
|
|
|
|
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, [=] {
|
2020-06-08 13:56:11 +03:00
|
|
|
// 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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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-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-06-05 14:28:39 +03:00
|
|
|
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;
|
|
|
|
|
}
|