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

360 lines
12 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"
#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>
#include <QtGlobal>
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)
2023-07-26 16:33:11 +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)
Q_DECLARE_METATYPE(TCDBus::Result<QString>)
2020-05-24 17:41:29 +03:00
2023-07-26 16:33:11 +03:00
DeviceModel::DeviceModel(TC::TreeNode<TCDBus::DeviceNode> root, QObject *parent)
: QStandardItemModel(parent) {
2020-05-24 17:41:29 +03:00
qDBusRegisterMetaType<TCDBus::Enumeration>();
qDBusRegisterMetaType<QVector<TCDBus::Enumeration>>();
qDBusRegisterMetaType<TCDBus::Range>();
qDBusRegisterMetaType<TCDBus::Result<QString>>();
2020-05-24 17:41:29 +03:00
/* Data storage:
- Interface column should store assignable info for editors
2023-07-26 16:33:11 +03:00
- Name colums should store the interface type for filtering
- Parametrization/connection data, where? */
2020-05-24 17:41:29 +03:00
setColumnCount(2);
2023-07-26 16:33:11 +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();
2023-07-26 16:33:11 +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();
2023-07-26 16:33:11 +03:00
QList<QStandardItem *> rowItems;
2020-05-24 17:41:29 +03:00
auto nameItem = new QStandardItem;
nameItem->setText(nodeName);
rowItems.append(nameItem);
2023-07-26 16:33:11 +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);
};
},
2023-07-26 16:33:11 +03:00
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();
2023-07-26 16:33:11 +03:00
nameItem->setData(icon, Qt::DecorationRole);
nameItem->setData(
DeviceModel::DynamicReadable, InterfaceTypeRole);
2020-06-03 23:24:07 +03:00
rowItems.append(item);
2023-07-26 16:33:11 +03:00
// qDebug() << item->data(DynamicReadableProxyRole);
2020-06-03 23:24:07 +03:00
};
},
2023-07-26 16:33:11 +03:00
pattern("org.tuxclocker.StaticReadable") =
[=, &rowItems] {
if_let(pattern(some(arg)) =
setupStaticReadable(node, conn)) = [&](auto item) {
2020-06-20 14:55:08 +03:00
auto icon = staticReadableIcon();
nameItem->setData(icon, Qt::DecorationRole);
2023-07-26 16:33:11 +03:00
nameItem->setData(
DeviceModel::StaticReadable, InterfaceTypeRole);
2020-06-20 14:55:08 +03:00
rowItems.append(item);
};
},
2023-07-26 16:33:11 +03:00
pattern(_) = [] {});
2020-05-24 17:41:29 +03:00
item->appendRow(rowItems);
2023-07-26 16:33:11 +03:00
2020-05-24 17:41:29 +03:00
for (auto c_node : node.children())
traverse(c_node, nameItem);
};
auto rootItem = invisibleRootItem();
2023-07-26 16:33:11 +03:00
2020-05-24 17:41:29 +03:00
for (auto &node : root.children())
traverse(node, rootItem);
}
2020-06-03 23:24:07 +03:00
EnumerationVec toEnumVec(QVector<TCDBus::Enumeration> enums) {
std::vector<TCDBus::Enumeration> stdEnumVec(enums.begin(), enums.end());
2023-07-26 16:33:11 +03:00
return transform(
[](auto e) {
return Enumeration{e.name.toStdString(), e.key};
},
stdEnumVec);
2020-06-03 23:24:07 +03:00
}
2023-07-26 16:33:11 +03:00
std::optional<const AssignableProxy *> DeviceModel::assignableProxyFromItem(QStandardItem *item) {
return (m_assignableProxyHash.contains(item))
? std::optional(m_assignableProxyHash.value(item))
: std::nullopt;
2020-07-21 17:50:01 +03:00
}
QString fromAssignmentArgument(AssignmentArgument a_arg) {
2023-07-26 16:33:11 +03:00
return p::match(a_arg)(
pattern(as<int>(arg)) = [](auto i) { return QString::number(i); },
pattern(as<uint>(arg)) = [](auto u) { return QString::number(u); },
pattern(as<double>(arg)) = [](auto d) { return QString::number(d); },
pattern(_) = [] { return QString(""); });
}
2023-07-26 16:33:11 +03:00
QStandardItem *DeviceModel::createAssignable(
TC::TreeNode<TCDBus::DeviceNode> node, QDBusConnection conn, AssignableItemData itemData) {
2020-06-04 02:03:42 +03:00
auto ifaceItem = new AssignableItem(this);
2020-06-03 23:24:07 +03:00
auto proxy = new AssignableProxy(node.value().path, conn, this);
2023-07-26 16:33:11 +03:00
connect(proxy, &AssignableProxy::connectionValueChanged, [=](auto result, auto text) {
p::match(result)(
pattern(as<QVariant>(arg)) =
[=](auto v) {
2020-07-21 17:50:01 +03:00
QVariant data;
data.setValue(connectionColor());
ifaceItem->setData(data, Qt::BackgroundRole);
ifaceItem->setText(text);
2023-07-26 16:33:11 +03:00
// qDebug() << text;
2020-07-21 17:50:01 +03:00
},
2023-07-26 16:33:11 +03:00
pattern(_) = [] {});
2020-07-21 17:50:01 +03:00
});
2023-07-26 16:33:11 +03:00
2020-07-21 17:50:01 +03:00
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);
// Set initial text to current value (one-time at startup)
QString text("No value set");
auto unit = itemData.unit();
auto currentValue = proxy->currentValue();
if (currentValue.has_value()) {
2023-07-26 16:33:11 +03:00
p::match(itemData.assignableInfo())(
pattern(as<EnumerationVec>(arg)) =
[&](auto e_vec) {
/* Find index from EnumVec (O(n) doesn't matter since we only search
once here) This should never be anything other than an uint but
in theory it could be whatever at this point */
try {
auto index = std::get<uint>(currentValue.value());
for (auto &e : e_vec) {
if (index == e.key) {
text = QString::fromStdString(e.name);
break;
}
}
2023-07-26 16:33:11 +03:00
} catch (std::bad_variant_access &e) {
}
2023-07-26 16:33:11 +03:00
// text =
// QString::fromStdString(e_vec[std::get<uint>(currentValue.value())].name);
},
2023-07-26 16:33:11 +03:00
pattern(as<RangeInfo>(_)) =
[&]() {
auto base = fromAssignmentArgument(currentValue.value());
if (unit.has_value())
text = QString("%1 %2").arg(base, unit.value());
else
text = base;
2023-07-26 16:33:11 +03:00
});
}
ifaceItem->setText(text);
2023-07-26 16:33:11 +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);
});
2023-07-26 16:33:11 +03:00
connect(ifaceItem, &AssignableItem::committalChanged, [=](bool on) {
QVariant colorData = (on) ? unappliedColor() : QVariant();
ifaceItem->setData(colorData, Qt::BackgroundRole);
});
2023-07-26 16:33:11 +03:00
2020-06-03 23:24:07 +03:00
connect(proxy, &AssignableProxy::applied, [=](auto err) {
// Fade out result color
2023-07-26 16:33:11 +03:00
auto startColor = (err.has_value()) ? errorColor() : successColor();
2020-06-03 23:24:07 +03:00
auto anim = new QVariantAnimation;
anim->setDuration(fadeOutTime());
anim->setStartValue(startColor);
anim->setEndValue(QPalette().color(QPalette::Base));
2023-07-26 16:33:11 +03:00
2020-06-03 23:24:07 +03:00
connect(anim, &QVariantAnimation::valueChanged, [=](QVariant v) {
QVariant iv;
iv.setValue(v.value<QColor>());
ifaceItem->setData(iv, Qt::BackgroundRole);
});
2023-07-26 16:33:11 +03:00
connect(anim, &QVariantAnimation::finished, [=] {
// Set invalid color to 'reset' the color
ifaceItem->setData(QVariant(), Qt::BackgroundRole);
});
2023-07-26 16:33:11 +03:00
2020-06-03 23:24:07 +03:00
anim->start(QAbstractAnimation::DeleteWhenStopped);
});
2023-07-26 16:33:11 +03:00
2020-06-03 23:24:07 +03:00
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
2023-07-26 16:33:11 +03:00
auto nameIndex = this->index(index.row(), DeviceModel::NameColumn, index.parent());
2020-07-21 17:50:01 +03:00
return nameIndex.data(Qt::DisplayRole);
}
if (index.column() != InterfaceColumn && role == DynamicReadableProxyRole) {
2023-07-26 16:33:11 +03:00
auto idx = this->index(index.row(), DeviceModel::InterfaceColumn, index.parent());
2020-07-21 17:50:01 +03:00
return idx.data(DynamicReadableProxyRole);
}
return QStandardItemModel::data(index, role);
}
std::optional<QString> fromDBusResult(TCDBus::Result<QString> res) {
if (res.error)
return std::nullopt;
return res.value;
}
2023-07-26 16:33:11 +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);
2023-07-26 16:33:11 +03:00
// Calling QObject::property used to work for getting (some) DBus properties but now we have
// to do this..
QDBusInterface propIface{
"org.tuxclocker", node.value().path, "org.freedesktop.DBus.Properties", conn};
QDBusReply<QDBusVariant> a_reply =
propIface.call("Get", "org.tuxclocker.Assignable", "assignableInfo");
QDBusReply<QDBusVariant> u_reply =
propIface.call("Get", "org.tuxclocker.Assignable", "unit");
if (!a_reply.isValid() || !u_reply.isValid()) {
// This code path shouldn't be reached
// The DBus path is contained in the error message
qWarning("Could not get assignableInfo or unit for Assignable "
2023-07-26 16:33:11 +03:00
"due to error(s) \"%s\" and \"%s\"",
qPrintable(a_reply.error().message()), qPrintable(u_reply.error().message()));
return std::nullopt;
}
// Calling QDBusReply::value() should ge safe here
// Need to serialize manually into the proper types
2023-07-26 16:33:11 +03:00
auto unit = fromDBusResult(
qdbus_cast<TCDBus::Result<QString>>(u_reply.value().variant().value<QDBusArgument>()));
// What a mess...
2023-07-26 16:33:11 +03:00
auto assInfoRaw =
a_reply.value().variant().value<QDBusVariant>().variant().value<QDBusArgument>();
// TODO: get initial value
2023-07-26 16:33:11 +03:00
// Use DBus signature to know the type of Assignable
if (assInfoRaw.currentSignature() == "(vv)") {
// RangeInfo
auto range = qdbus_cast<TCDBus::Range>(assInfoRaw);
AssignableItemData data{range.toAssignableInfo(), unit};
return createAssignable(node, conn, data);
} else if (assInfoRaw.currentSignature() == "a(us)") {
// EnumerationVec
auto enumVec = qdbus_cast<QVector<TCDBus::Enumeration>>(assInfoRaw);
AssignableItemData data{toEnumVec(enumVec), unit};
return createAssignable(node, conn, data);
2020-06-03 23:24:07 +03:00
}
return std::nullopt;
2020-06-03 23:24:07 +03:00
}
2020-06-05 14:28:39 +03:00
template <typename T>
2023-07-26 16:33:11 +03:00
void updateReadItemText(QStandardItem *item, T value, std::optional<QString> unit) {
2020-06-05 14:28:39 +03:00
// 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
2023-07-26 16:33:11 +03:00
auto text = (unit.has_value()) ? QString("%1 %2").arg(value).arg(unit.value())
: QString("%1").arg(value);
2020-06-05 14:28:39 +03:00
item->setText(text);
2023-07-26 16:33:11 +03:00
// qDebug() << item->data(DeviceModel::DynamicReadableProxyRole);
2020-06-05 14:28:39 +03:00
}
2023-07-26 16:33:11 +03:00
std::optional<QStandardItem *> DeviceModel::setupDynReadable(
TC::TreeNode<TCDBus::DeviceNode> node, QDBusConnection conn) {
2020-06-03 23:24:07 +03:00
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);
2023-07-26 16:33:11 +03:00
auto unit = proxy->unit();
2020-06-03 23:24:07 +03:00
connect(proxy, &DynamicReadableProxy::valueChanged, [=](ReadResult res) {
p::match(res)(
2023-07-26 16:33:11 +03:00
pattern(as<ReadableValue>(arg)) =
[=](auto rv) {
2020-06-03 23:24:07 +03:00
p::match(rv)(
2023-07-26 16:33:11 +03:00
pattern(as<double>(arg)) =
[=](auto d) { updateReadItemText(item, d, unit); },
pattern(as<int>(arg)) =
[=](auto i) { updateReadItemText(item, i, unit); },
pattern(as<uint>(arg)) =
[=](auto u) { updateReadItemText(item, u, unit); },
pattern(_) = [] {});
2020-06-03 23:24:07 +03:00
},
2023-07-26 16:33:11 +03:00
pattern(_) = [] {});
2020-06-03 23:24:07 +03:00
});
2023-07-26 16:33:11 +03:00
2020-06-03 23:24:07 +03:00
return item;
}
2020-06-20 14:55:08 +03:00
2023-07-26 16:33:11 +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();
2020-06-20 14:55:08 +03:00
// Workaround from DynamicReadableProxy for getting property with custom type
2023-07-26 16:33:11 +03:00
QDBusInterface propIface(
"org.tuxclocker", node.value().path, "org.freedesktop.DBus.Properties", conn);
2020-06-20 14:55:08 +03:00
QDBusReply<QDBusVariant> reply =
2023-07-26 16:33:11 +03:00
propIface.call("Get", "org.tuxclocker.StaticReadable", "unit");
2020-06-20 14:55:08 +03:00
if (!reply.isValid())
return std::nullopt;
auto arg = reply.value().variant().value<QDBusArgument>();
TCDBus::Result<QString> unit;
arg >> unit;
2023-07-26 16:33:11 +03:00
2020-06-20 14:55:08 +03:00
if (!unit.error)
value += " " + unit.value;
2023-07-26 16:33:11 +03:00
2020-06-20 14:55:08 +03:00
auto item = new QStandardItem;
item->setData(value, Qt::DisplayRole);
return item;
}