diff --git a/src/tuxclocker-qt/data/AbstractAssignableConnection.hpp b/src/tuxclocker-qt/data/AbstractAssignableConnection.hpp new file mode 100644 index 0000000..ddbbbf1 --- /dev/null +++ b/src/tuxclocker-qt/data/AbstractAssignableConnection.hpp @@ -0,0 +1,6 @@ +#pragma once + +class AbstractAssignableConnection { +public: + +}; diff --git a/src/tuxclocker-qt/data/AssignableConnection.hpp b/src/tuxclocker-qt/data/AssignableConnection.hpp new file mode 100644 index 0000000..8a80fa7 --- /dev/null +++ b/src/tuxclocker-qt/data/AssignableConnection.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include + +// Interface for the function for an assignable +class AssignableConnection : public QObject { +public: + AssignableConnection(QObject *parent = nullptr) : QObject(parent) {} + /*AssignableConnection(const AssignableConnection &other) { + targetValueChanged.connect(other.targetValueChanged); + }*/ + virtual ~AssignableConnection() {} + // Returns the data as QVariant for saving to settings + virtual QVariant connectionData() = 0; + virtual void start() = 0; + virtual void stop() = 0; + /* Value and text representation of desired value, + since converting QDBusVariant to QString isn't trivial + for complex types. */ + /*boost::signals2::signal targetValueChanged; + boost::signals2::signal started; + boost::signals2::signal stopped;*/ +signals: + void targetValueChanged(QVariant, QString); + void started(); + void stopped(); +private: + Q_OBJECT +}; diff --git a/src/tuxclocker-qt/data/AssignableProxy.cpp b/src/tuxclocker-qt/data/AssignableProxy.cpp index 901a21b..603d51c 100644 --- a/src/tuxclocker-qt/data/AssignableProxy.cpp +++ b/src/tuxclocker-qt/data/AssignableProxy.cpp @@ -1,5 +1,6 @@ #include "AssignableProxy.hpp" + #include #include #include @@ -16,7 +17,20 @@ AssignableProxy::AssignableProxy(QString path, QDBusConnection conn, m_iface = new QDBusInterface("org.tuxclocker", path, "org.tuxclocker.Assignable", conn, this); } - + +std::optional AssignableProxy::doApply(const QVariant &v) { + //qDebug() << v; + QDBusReply> reply = m_iface->call("assign", v); + if (reply.isValid()) { + TCD::Result ar{reply.value().error, + static_cast(reply.value().value)}; + //qDebug("Success!"); + return ar.toOptional(); + } + // TODO: indicate dbus error + return AssignmentError::UnknownError; +} + void AssignableProxy::apply() { // A value hasn't been set yet if (m_value.isNull()) @@ -26,18 +40,21 @@ void AssignableProxy::apply() { QDBusVariant dv(m_value); QVariant v; v.setValue(dv); - QDBusReply> reply = m_iface->call("assign", v); - if (reply.isValid()) { - qDebug() << reply.value().error << reply.value().value; - - TCD::Result ar{reply.value().error, - static_cast(reply.value().value)}; - qDebug("Success!"); - emit applied(ar.toOptional()); - } - // TODO: Maybe indicate that calling dbus errored out - else - emit applied(AssignmentError::UnknownError); + emit applied(doApply(v)); // Indicate that there's no pending value to applied by making value invalid m_value = QVariant(); } + +void AssignableProxy::startConnection(std::shared_ptr conn) { + m_assignableConnection = conn; + connect(conn.get(), &AssignableConnection::targetValueChanged, [this](auto targetValue, auto text) { + auto err = doApply(targetValue); + if (err.has_value()) + emit connectionValueChanged(err.value(), text); + else + emit connectionValueChanged(targetValue, text); + }); + // Emit started signal in case a connection emits a new value right away + emit connectionStarted(); + m_assignableConnection->start(); +} diff --git a/src/tuxclocker-qt/data/AssignableProxy.hpp b/src/tuxclocker-qt/data/AssignableProxy.hpp index ce82f46..3e54827 100644 --- a/src/tuxclocker-qt/data/AssignableProxy.hpp +++ b/src/tuxclocker-qt/data/AssignableProxy.hpp @@ -2,6 +2,7 @@ // Acts as a buffer for unapplied assignables and applies them asynchronously +#include #include #include #include @@ -17,12 +18,23 @@ public: AssignableProxy(QString path, QDBusConnection conn, QObject *parent = nullptr); void apply(); + void startConnection(std::shared_ptr conn); + // Stop connection and clear current connection + void stopConnection(); void setValue(QVariant v) {m_value = v;} signals: void applied(std::optional); + void connectionValueChanged(std::variant, + QString text); + void connectionStarted(); + void connectionStopped(); private: Q_OBJECT QVariant m_value; QDBusInterface *m_iface; + // This is a bit of a peril but not sure if we can store interfaces any better... + std::shared_ptr m_assignableConnection; + + std::optional doApply(const QVariant &v); }; diff --git a/src/tuxclocker-qt/data/DeviceModel.cpp b/src/tuxclocker-qt/data/DeviceModel.cpp index d2c6aab..1087c8d 100644 --- a/src/tuxclocker-qt/data/DeviceModel.cpp +++ b/src/tuxclocker-qt/data/DeviceModel.cpp @@ -2,6 +2,8 @@ #include "AssignableProxy.hpp" #include "DynamicReadableProxy.hpp" +#include "qnamespace.h" +#include "qstandarditemmodel.h" #include #include #include @@ -16,6 +18,8 @@ using namespace mpark::patterns; using namespace TuxClocker::Device; Q_DECLARE_METATYPE(AssignableItemData) +Q_DECLARE_METATYPE(AssignableProxy*) +Q_DECLARE_METATYPE(DynamicReadableProxy*) Q_DECLARE_METATYPE(TCDBus::Enumeration) Q_DECLARE_METATYPE(TCDBus::Range) Q_DECLARE_METATYPE(EnumerationVec) @@ -50,13 +54,8 @@ DeviceModel::DeviceModel(TC::TreeNode root, QObject *parent) if_let(pattern(some(arg)) = setupAssignable(node, conn)) = [&](auto item) { 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); - rowItems.append(item); }; }, @@ -69,6 +68,7 @@ DeviceModel::DeviceModel(TC::TreeNode root, QObject *parent) nameItem->setData(DeviceModel::DynamicReadable, InterfaceTypeRole); rowItems.append(item); + //qDebug() << item->data(DynamicReadableProxyRole); }; }, pattern("org.tuxclocker.StaticReadable") = [=, &rowItems] { @@ -100,10 +100,35 @@ EnumerationVec toEnumVec(QVector enums) { }, enums.toStdVector()); } +std::optional + DeviceModel::assignableProxyFromItem(QStandardItem *item) { + return (m_assignableProxyHash.contains(item)) ? + std::optional(m_assignableProxyHash.value(item)) : + std::nullopt; +} + QStandardItem *DeviceModel::createAssignable(TC::TreeNode node, QDBusConnection conn, AssignableItemData itemData) { auto ifaceItem = new AssignableItem(this); auto proxy = new AssignableProxy(node.value().path, conn, this); + + connect(proxy, &AssignableProxy::connectionValueChanged, [=] (auto result, auto text) { + p::match(result) ( + pattern(as(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); QVariant v; v.setValue(itemData); ifaceItem->setData(v, AssignableRole); @@ -159,6 +184,21 @@ QStandardItem *DeviceModel::createAssignable(TC::TreeNode no return ifaceItem; } +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); +} + std::optional DeviceModel::setupAssignable( TC::TreeNode node, QDBusConnection conn) { QDBusInterface ifaceNode("org.tuxclocker", node.value().path, @@ -197,13 +237,17 @@ void updateReadItemText(QStandardItem *item, T value, QString("%1 %2").arg(value).arg(unit.value()) : QString("%1").arg(value); item->setText(text); + //qDebug() << item->data(DeviceModel::DynamicReadableProxyRole); } std::optional DeviceModel::setupDynReadable( TC::TreeNode node, QDBusConnection conn) { auto item = new QStandardItem; auto proxy = new DynamicReadableProxy(node.value().path, conn, this); - auto unit = proxy->unit(); + QVariant v; + v.setValue(proxy); + item->setData(v, DynamicReadableProxyRole); + auto unit = proxy->unit(); connect(proxy, &DynamicReadableProxy::valueChanged, [=](ReadResult res) { p::match(res)( diff --git a/src/tuxclocker-qt/data/DeviceModel.hpp b/src/tuxclocker-qt/data/DeviceModel.hpp index df38a9a..883ef7e 100644 --- a/src/tuxclocker-qt/data/DeviceModel.hpp +++ b/src/tuxclocker-qt/data/DeviceModel.hpp @@ -2,23 +2,26 @@ #include "AssignableItem.hpp" #include "AssignableItemData.hpp" +#include "DynamicReadableProxy.hpp" #include #include #include -#include #include #include #include +#include #include #include #include +#include namespace TC = TuxClocker; namespace TCDBus = TuxClocker::DBus; // Why the fuck do I have to forward declare this? class AssignableItem; +class AssignableProxy; class DeviceModel : public QStandardItemModel { public: @@ -30,8 +33,11 @@ public: enum Role { AssignableRole = Qt::UserRole, // Holds the data about the assignable + AssignableProxyRole, ConnectionRole, // Data about the connection - InterfaceTypeRole // InterfaceType + DynamicReadableProxyRole, + InterfaceTypeRole, // InterfaceType + NodeNameRole // }; enum InterfaceFlag { @@ -41,14 +47,15 @@ public: AllInterfaces = (Assignable | DynamicReadable | StaticReadable) }; typedef QFlags InterfaceFlags; - - enum class FilterFlag { - - }; + // For decoupling AssignableItems created in the model void applyChanges() {emit changesApplied();} - + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; static QIcon assignableIcon() {return QIcon::fromTheme("edit-entry");} + // Get AssignableProxy of an item + std::optional assignableProxyFromItem(QStandardItem *item); + // AssignableProxies are associated with both row items + //QVariant data(QModelIndex&); static QIcon dynamicReadableIcon() {return QIcon(":/ruler.svg");} static QIcon staticReadableIcon() {return QIcon::fromTheme("help-about");} signals: @@ -56,6 +63,8 @@ signals: private: Q_OBJECT + QHash m_assignableProxyHash; + // Separate handling interfaces since otherwise we run out of columns QStandardItem *createAssignable(TC::TreeNode node, QDBusConnection conn, AssignableItemData data); @@ -68,6 +77,7 @@ private: constexpr int fadeOutTime() {return 5000;} // milliseconds constexpr int transparency() {return 120;} // 0-255 // Colors for items + QColor connectionColor() {return QColor(0, 0, 255, transparency());} // blue QColor errorColor() {return QColor(255, 0, 0, transparency());} // red QColor unappliedColor() {return QColor(255, 255, 0, transparency());} // yellow QColor successColor() {return QColor(0, 255, 0, transparency());} // green diff --git a/src/tuxclocker-qt/data/DeviceProxyModel.cpp b/src/tuxclocker-qt/data/DeviceProxyModel.cpp index cc21475..117a72d 100644 --- a/src/tuxclocker-qt/data/DeviceProxyModel.cpp +++ b/src/tuxclocker-qt/data/DeviceProxyModel.cpp @@ -5,7 +5,8 @@ Q_DECLARE_METATYPE(DeviceModel::InterfaceFlag) DeviceProxyModel::DeviceProxyModel(DeviceModel &model, QObject *parent) : - QSortFilterProxyModel(parent), m_showIcons(true), m_showValueColumn(true) { + QSortFilterProxyModel(parent), m_disableFiltered(false), + m_showIcons(true), m_showValueColumn(true) { setSourceModel(&model); m_flags = DeviceModel::AllInterfaces; } @@ -37,6 +38,22 @@ bool DeviceProxyModel::filterAcceptsRow(int sourceRow, return !shouldHide; } +Qt::ItemFlags DeviceProxyModel::flags(const QModelIndex &index) const { + if (!m_disableFiltered) + return QSortFilterProxyModel::flags(index); + + auto iface = + QSortFilterProxyModel::data(index, DeviceModel::InterfaceTypeRole); + auto removedFlags = Qt::ItemIsSelectable; + auto itemFlags = QSortFilterProxyModel::flags(index); + if (!iface.isValid()) + // Remove selectable flag + return itemFlags &(~removedFlags); + auto flags = iface.value(); + return (flags & m_flags) ? itemFlags : + itemFlags &(~removedFlags); +} + bool DeviceProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { // TODO: doesn't work as expected if sorted from interface column diff --git a/src/tuxclocker-qt/data/DeviceProxyModel.hpp b/src/tuxclocker-qt/data/DeviceProxyModel.hpp index 7a558e8..a69f336 100644 --- a/src/tuxclocker-qt/data/DeviceProxyModel.hpp +++ b/src/tuxclocker-qt/data/DeviceProxyModel.hpp @@ -9,6 +9,8 @@ class DeviceProxyModel : public QSortFilterProxyModel { public: DeviceProxyModel(DeviceModel &model, QObject *parent = nullptr); DeviceModel::InterfaceFlags filterFlags() {return m_flags;} + // Disable items that don't contain the interface in m_flags + void setDisableFiltered(bool on) {m_disableFiltered = on;} void setFlags(DeviceModel::InterfaceFlags flags) { m_flags = flags; invalidateFilter(); @@ -37,9 +39,12 @@ protected: const override { return !(!m_showValueColumn && sourceColumn == DeviceModel::InterfaceColumn); } + // Optionally disable selection of items that don't have the wanted interface + Qt::ItemFlags flags(const QModelIndex &index) const override; // Overridden for showing items with children last/first bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; private: + bool m_disableFiltered; bool m_showIcons; bool m_showValueColumn; DeviceModel::InterfaceFlags m_flags; diff --git a/src/tuxclocker-qt/data/DynamicReadableConnection.hpp b/src/tuxclocker-qt/data/DynamicReadableConnection.hpp new file mode 100644 index 0000000..a0649c4 --- /dev/null +++ b/src/tuxclocker-qt/data/DynamicReadableConnection.hpp @@ -0,0 +1,106 @@ +#pragma once + +#include "AssignableConnection.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// delete these +using namespace fplus; +using namespace mpark::patterns; +using namespace TuxClocker::Device; + +class DynamicReadableConnectionData { +public: + // y = f(x) + QVector points; + // DBus path + QString dynamicReadablePath; +}; + +// TODO: make DynamicReadableProxy type parametrized so we can be sure to only get numeric values from it +// Connection of an Assignable with a DynamicReadable +template // Result of linear interpolation +class DynamicReadableConnection : public AssignableConnection { +public: + DynamicReadableConnection(DynamicReadableProxy &proxy, + QVector points, QObject *parent = nullptr) + : AssignableConnection(parent), m_proxy(proxy), m_points(points) { + auto sorted = sort_by([](auto point_l, auto point_r) { + return point_l.x() < point_r.x(); + }, m_points); + m_points = sorted; + } + virtual QVariant connectionData() override {return QVariant();} + virtual void start() override { + /*QObject::connect(&m_timer, &QTimer::timeout, [this] { + int rand = QRandomGenerator::global()->bounded(0, 10); + + QDBusVariant arg{QVariant{rand}}; + QVariant v; + v.setValue(arg); + emit targetValueChanged(v, QString::number(rand)); + }); + m_timer.start(1000);*/ + + connect(&m_proxy, &DynamicReadableProxy::valueChanged, [this](auto val) { + match(val) ( + pattern(as(arg)) = [this](auto rv) { + match(rv) ( + pattern(as(arg)) = [this](auto u) { + emitTargetValue(u); + } + ); + } + ); + }); + + /*QDBusVariant arg{QVariant{1}}; + QVariant v; + v.setValue(arg); + emit targetValueChanged(v, "1");*/ + + } + virtual void stop() override { + } +private: + DynamicReadableProxy &m_proxy; + QTimer m_timer; + QVector m_points; + + template + void emitTargetValue(U reading) { + // Find two points from the vector so that: + // p[i].x < val < p[i + 1].x + std::optional leftIndex = std::nullopt; + for (int i = 0; i < m_points.length() - 1; i++) { + if (m_points[i].x() < reading && reading < m_points[i + 1].x()) { + leftIndex = i; + break; + } + } + if (leftIndex == std::nullopt) + return; + int li = leftIndex.value(); + // What percentage the value is from dx of left and right interp points + double dx = m_points[li + 1].x() - m_points[li].x(); + double dvx = reading - m_points[li].x(); + double p = dvx / dx; + OutType interp_y = lerp(m_points[li].y(), + m_points[li + 1].y(), p); + QDBusVariant arg{QVariant{interp_y}}; + QVariant v; + v.setValue(arg); + emit targetValueChanged(v, QString::number(interp_y)); + } + + template + T lerp(T a, T b, double t) {return a + (t * (b - a));} +}; diff --git a/src/tuxclocker-qt/main.cpp b/src/tuxclocker-qt/main.cpp index ae70f18..0dea939 100644 --- a/src/tuxclocker-qt/main.cpp +++ b/src/tuxclocker-qt/main.cpp @@ -4,7 +4,7 @@ int main(int argc, char **argv) { QApplication app(argc, argv); - + MainWindow mw; mw.show(); diff --git a/src/tuxclocker-qt/meson.build b/src/tuxclocker-qt/meson.build index 0a7643c..08313ca 100644 --- a/src/tuxclocker-qt/meson.build +++ b/src/tuxclocker-qt/meson.build @@ -5,9 +5,11 @@ qt5_dep = dependency('qt5', modules: ['DBus', 'Charts', 'Widgets']) boost_dep = dependency('boost') moc_files = qt5.preprocess(moc_headers: ['MainWindow.hpp', + 'data/AssignableConnection.hpp', 'data/AssignableItem.hpp', 'data/AssignableProxy.hpp', 'data/DeviceModel.hpp', + 'data/DynamicReadableConnection.hpp', 'data/DynamicReadableProxy.hpp', 'widgets/DragChartView.hpp'], qresources : ['resources/resources.qrc'], @@ -20,6 +22,7 @@ sources = ['main.cpp', 'data/DeviceModelDelegate.cpp', 'data/DeviceProxyModel.cpp', 'data/DynamicReadableProxy.cpp', + 'widgets/DeviceBrowser.cpp', 'widgets/DeviceTreeView.cpp', 'widgets/DragChartView.cpp', 'widgets/EnumEditor.cpp', diff --git a/src/tuxclocker-qt/widgets/DeviceBrowser.cpp b/src/tuxclocker-qt/widgets/DeviceBrowser.cpp new file mode 100644 index 0000000..9a50853 --- /dev/null +++ b/src/tuxclocker-qt/widgets/DeviceBrowser.cpp @@ -0,0 +1,89 @@ +#include "DeviceBrowser.hpp" +#include "AssignableItemData.hpp" +#include "qnamespace.h" + +#include +#include + +using namespace mpark::patterns; +using namespace TuxClocker::Device; + +Q_DECLARE_METATYPE(AssignableItemData) +Q_DECLARE_METATYPE(AssignableProxy*) + +DeviceBrowser::DeviceBrowser(DeviceModel &model, QWidget *parent) + : QWidget(parent), m_deviceModel(model) { + m_layout = new QGridLayout(this); + m_proxyModel = new DeviceProxyModel(model, this); + m_treeView = new DeviceTreeView; + m_treeView->setModel(m_proxyModel); + m_flagLabel = new QLabel("Showing:"); + m_apply = new QPushButton("Apply changes"); + m_apply->setEnabled(true); + + m_flagEditor = new FlagEditor( + QVector({ + std::tuple( + QString("Assignables"), + DeviceModel::assignableIcon(), + DeviceModel::Assignable + ), + std::tuple( + QString("Dynamic Values"), + DeviceModel::dynamicReadableIcon(), + DeviceModel::DynamicReadable + ), + std::tuple( + QString("Static Values"), + DeviceModel::staticReadableIcon(), + DeviceModel::StaticReadable + ) + }), this); + + connect(m_apply, &QPushButton::pressed, &m_deviceModel, + &DeviceModel::applyChanges); + + m_treeView->functionEditorRequested.connect([this](QModelIndex &index) { + auto a_data = index.data(DeviceModel::AssignableRole); + if (!a_data.isValid()) + return; + + auto a_info = a_data.value(); + auto proxy = index.data(DeviceModel::AssignableProxyRole) + .value(); + auto name = index.data(DeviceModel::NodeNameRole).toString(); + match(a_info.assignableInfo()) ( + pattern(as(arg)) = [=](auto ri) { + auto f_editor = new FunctionEditor{m_deviceModel, + ri, *proxy, name}; + f_editor->show(); + f_editor->assignableConnectionChanged + .connect([=](auto conn) { + proxy->startConnection(conn); + }); + }, + pattern(_) = [] {} + ); + + /*auto f_editor = new FunctionEditor(m_deviceModel, rangeInfo, proxy); + f_editor->show(); + + f_editor->assignableConnectionChanged.connect( + [&proxy] (auto assignableConnection) { + proxy.startConnection(assignableConnection); + });*/ + }); + + m_flagEditor->setFlags(DeviceModel::AllInterfaces); + + m_flagEditor->flagsChanged.connect([=](auto flags) { + m_proxyModel->setFlags(flags); + }); + + m_layout->addWidget(m_flagLabel, 0, 0); + m_layout->addWidget(m_flagEditor, 0, 1); + m_layout->addWidget(m_treeView, 1, 0, 1, 2); + m_layout->addWidget(m_apply, 2, 0, 1, 2); + + setLayout(m_layout); +} diff --git a/src/tuxclocker-qt/widgets/DeviceBrowser.hpp b/src/tuxclocker-qt/widgets/DeviceBrowser.hpp index 8d51376..a71896e 100644 --- a/src/tuxclocker-qt/widgets/DeviceBrowser.hpp +++ b/src/tuxclocker-qt/widgets/DeviceBrowser.hpp @@ -4,7 +4,7 @@ #include #include #include -//#include +#include #include #include #include @@ -14,57 +14,7 @@ // Class for viewing/editing the main tuxclocker tree class DeviceBrowser : public QWidget { public: - DeviceBrowser(DeviceModel &model, QWidget *parent = nullptr) - : QWidget(parent), m_deviceModel(model) { - m_layout = new QGridLayout(this); - m_proxyModel = new DeviceProxyModel(model, this); - m_treeView = new DeviceTreeView; - m_treeView->setModel(m_proxyModel); - m_flagLabel = new QLabel("Showing:"); - m_apply = new QPushButton("Apply changes"); - m_apply->setEnabled(true); - - m_flagEditor = new FlagEditor( - QVector({ - std::tuple( - QString("Assignables"), - DeviceModel::assignableIcon(), - DeviceModel::Assignable - ), - std::tuple( - QString("Dynamic Values"), - DeviceModel::dynamicReadableIcon(), - DeviceModel::DynamicReadable - ), - std::tuple( - QString("Static Values"), - DeviceModel::staticReadableIcon(), - DeviceModel::StaticReadable - ) - }), this); - - connect(m_apply, &QPushButton::pressed, &m_deviceModel, - &DeviceModel::applyChanges); - - m_treeView->functionEditorRequested.connect([this] { - //auto f_editor = new FunctionEditor(m_deviceModel); - //f_editor->show(); - }); - - m_flagEditor->setFlags(DeviceModel::AllInterfaces); - - m_flagEditor->flagsChanged.connect([=](auto flags) { - m_proxyModel->setFlags(flags); - }); - - m_layout->addWidget(m_flagLabel, 0, 0); - m_layout->addWidget(m_flagEditor, 0, 1); - m_layout->addWidget(m_treeView, 1, 0, 1, 2); - m_layout->addWidget(m_apply, 2, 0, 1, 2); - - setLayout(m_layout); - } - + DeviceBrowser(DeviceModel &model, QWidget *parent = nullptr); private: DeviceModel &m_deviceModel; DeviceProxyModel *m_proxyModel; diff --git a/src/tuxclocker-qt/widgets/DeviceTreeView.cpp b/src/tuxclocker-qt/widgets/DeviceTreeView.cpp index 16e2784..8f09e02 100644 --- a/src/tuxclocker-qt/widgets/DeviceTreeView.cpp +++ b/src/tuxclocker-qt/widgets/DeviceTreeView.cpp @@ -1,45 +1,50 @@ #include "DeviceTreeView.hpp" +#include "qcheckbox.h" #include -//#include +#include #include #include +#include + +using namespace mpark::patterns; +using namespace TuxClocker::Device; Q_DECLARE_METATYPE(AssignableItemData) +Q_DECLARE_METATYPE(AssignableProxy*) DeviceTreeView::DeviceTreeView(QWidget *parent) : QTreeView(parent) { + header()->setSectionResizeMode(QHeaderView::ResizeToContents); setSortingEnabled(true); - auto triggers = editTriggers() ^= DoubleClicked; - triggers |= SelectedClicked; setEditTriggers(SelectedClicked | EditKeyPressed); setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &QTreeView::customContextMenuRequested, [this](QPoint point) { auto index = indexAt(point); auto data = index.data(DeviceModel::AssignableRole); + auto proxyData = index.data(DeviceModel::AssignableProxyRole); QMenu menu; - if (data.canConvert()) { + QCheckBox checkBox("Enable connection"); + QAction editConn("Edit connection..."); + auto enableConn = new QWidgetAction(&menu); + enableConn->setDefaultWidget(&checkBox); + menu.addActions({&editConn, enableConn}); + if (data.canConvert() && + proxyData.canConvert()) { + functionEditorRequested(index); /*auto a_data = data.value(); - QCheckBox commitCb("Commit"); - auto commitAct = new QWidgetAction(&menu); - commitAct->setDefaultWidget(&commitCb); - commitCb.setChecked(a_data.committal()); - connect(&commitCb, &QCheckBox::toggled, [&](bool toggled) { - a_data.setCommittal(toggled); - }); - // Write the committal value to the model only on menu close - connect(&menu, &QMenu::aboutToHide, [&] { - QVariant v; - v.setValue(a_data); - m_deviceModel.setData(index, v, DeviceModel::AssignableRole); - }); - menu.addAction(commitAct); - menu.exec(QCursor::pos());*/ + auto proxy = proxyData.value(); + match(a_data.assignableInfo()) ( + pattern(as(arg)) = [this, &menu, proxy, &editConn](auto ri) { + //functionEditorRequested(*proxy, ri); + connect(&editConn, &QAction::triggered, [this, proxy, ri] { + functionEditorRequested(*proxy, ri); + }); + menu.exec(QCursor::pos()); + }, + pattern(_) = []{} + );*/ - //auto dragView = new FunctionEditor; - //dragView->show(); - - functionEditorRequested(); } }); m_delegate = new DeviceModelDelegate(this); diff --git a/src/tuxclocker-qt/widgets/DeviceTreeView.hpp b/src/tuxclocker-qt/widgets/DeviceTreeView.hpp index faaf684..bf407a6 100644 --- a/src/tuxclocker-qt/widgets/DeviceTreeView.hpp +++ b/src/tuxclocker-qt/widgets/DeviceTreeView.hpp @@ -1,7 +1,9 @@ #pragma once #include +#include #include +#include #include #include #include @@ -14,7 +16,9 @@ public: DeviceTreeView(QWidget *parent = nullptr); // Accessor method for connecting everything in the browser //const DeviceModel &deviceModel() {return m_deviceModel;} - boost::signals2::signal functionEditorRequested; + // TODO: make this more generalized + // Defers the complexity to DeviceBrowser + boost::signals2::signal functionEditorRequested; protected: /* Workaround for the retarded behavior of waiting for a double click, you can't even disable it! */ diff --git a/src/tuxclocker-qt/widgets/DragChartView.cpp b/src/tuxclocker-qt/widgets/DragChartView.cpp index af449f5..4e6cfe7 100644 --- a/src/tuxclocker-qt/widgets/DragChartView.cpp +++ b/src/tuxclocker-qt/widgets/DragChartView.cpp @@ -176,6 +176,12 @@ void DragChartView::setVector(const QVector vector) { m_series.replace(vector); } +void DragChartView::setRange(qreal xmin, qreal xmax, qreal ymin, qreal ymax) { + m_series.clear(); + m_xAxis.setRange(xmin, xmax); + m_yAxis.setRange(ymin, ymax); +} + bool DragChartView::event(QEvent *event) { //qDebug() << event->type(); @@ -227,7 +233,10 @@ void DragChartView::mouseMoveEvent(QMouseEvent *event) { m_toolTipLabel->show(); } - m_toolTipLabel->setText(QString("%1, %2").arg(QString::number(m_latestScatterPoint.x()), QString::number(m_latestScatterPoint.y()))); + m_toolTipLabel->setText(QString("%1, %2") + .arg(QString::number(m_latestScatterPoint.x()), + QString::number(m_latestScatterPoint.y()))); + // FIXME : doesn't work properly when screen is switched(?) m_toolTipLabel->move(event->screenPos().toPoint() + toolTipOffset(this, event->windowPos().toPoint())); @@ -324,14 +333,17 @@ void DragChartView::drawFillerLines(QScatterSeries *series) { for (int i = 0; i < sorted.length() - 1; i++) { m_lineFillerItems[i]->setLine(QLineF(chart()->mapToPosition(sorted[i]), - chart()->mapToPosition(sorted[i + 1]))); + chart()->mapToPosition(sorted[i + 1]))); } - m_leftLineFillerItem->setLine(QLineF(chart()->mapToPosition(QPointF(m_xAxis.min(), sorted[0].y())), - chart()->mapToPosition(sorted[0]))); + m_leftLineFillerItem->setLine( + QLineF(chart()->mapToPosition(QPointF( + m_xAxis.min(), sorted[0].y())), + chart()->mapToPosition(sorted[0]))); - m_rightLineFillerItem->setLine(QLineF(chart()->mapToPosition(sorted.last()), - chart()->mapToPosition(QPointF(m_xAxis.max(), sorted.last().y())))); + m_rightLineFillerItem->setLine( + QLineF(chart()->mapToPosition(sorted.last()), + chart()->mapToPosition(QPointF(m_xAxis.max(), sorted.last().y())))); chart()->update(); } diff --git a/src/tuxclocker-qt/widgets/DragChartView.hpp b/src/tuxclocker-qt/widgets/DragChartView.hpp index 8749ade..29ca538 100644 --- a/src/tuxclocker-qt/widgets/DragChartView.hpp +++ b/src/tuxclocker-qt/widgets/DragChartView.hpp @@ -10,11 +10,13 @@ using namespace QtCharts; class DragChartView : public QChartView { public: DragChartView(QWidget *parent = nullptr); - QValueAxis *xAxis() {return &m_xAxis;} - QValueAxis *yAxis() {return &m_yAxis;} + QValueAxis &xAxis() {return m_xAxis;} + QValueAxis &yAxis() {return m_yAxis;} void setVector(const QVector vector); QVector vector() {return m_series.pointsVector();} + // Clear the points and set new range + void setRange(qreal xmin, qreal xmax, qreal ymin, qreal ymax); protected: bool event(QEvent*); void mousePressEvent(QMouseEvent*); diff --git a/src/tuxclocker-qt/widgets/FlagEditor.hpp b/src/tuxclocker-qt/widgets/FlagEditor.hpp index a305a1b..2779818 100644 --- a/src/tuxclocker-qt/widgets/FlagEditor.hpp +++ b/src/tuxclocker-qt/widgets/FlagEditor.hpp @@ -94,6 +94,9 @@ protected: QStylePainter painter(this); QStyleOptionComboBox opt; initStyleOption(&opt); + QPalette rp; + rp.setColor(QPalette::Highlight, Qt::red); + opt.palette = rp; // Show which flags are selected auto items = m_flagHash.values(); diff --git a/src/tuxclocker-qt/widgets/FunctionEditor.hpp b/src/tuxclocker-qt/widgets/FunctionEditor.hpp new file mode 100644 index 0000000..fa8ea4d --- /dev/null +++ b/src/tuxclocker-qt/widgets/FunctionEditor.hpp @@ -0,0 +1,122 @@ +#pragma once + +/* Widget for editing the function for an assignable. + Possible options, where y = value of assignable, t = time, + x = value of readable, n = static value set in model editor: + - y = n + - y = f(x) + - y = f(t) +*/ + +#include "DynamicReadableProxy.hpp" +#include "qnamespace.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Delet this +namespace p = mpark::patterns; + +Q_DECLARE_METATYPE(DynamicReadableProxy*) + +// TODO: make constructor of the type data Editor a = Maybe (Range a) +class FunctionEditor : public QWidget { +public: + FunctionEditor(DeviceModel &model, TuxClocker::Device::RangeInfo rangeInfo, + AssignableProxy &proxy, QString nodeName, + QWidget *parent = nullptr) + : QWidget(parent), m_assignableProxy(proxy), + m_model(model), m_proxyModel(model), + m_rangeInfo(rangeInfo) { + m_proxyModel.setDisableFiltered(true); + m_proxyModel.setFlags(DeviceModel::DynamicReadable); + m_proxyModel.setShowIcons(false); + //m_proxyModel.setShowValueColumn(false); + m_layout = new QGridLayout(this); + m_functionComboBox = new QComboBox; + m_functionComboBox->addItem("Function of time"); + m_dependableReadableComboBox = new NodeSelector; + auto treeView = new QTreeView; + m_dependableReadableComboBox->setModel(&m_proxyModel); + m_dependableReadableComboBox->setView(treeView); + treeView->expandAll(); + m_dependableLabel = new QLabel("Connecting with:"); + m_layout->addWidget(m_dependableReadableComboBox, 0, 0); + m_layout->addWidget(m_functionComboBox, 0, 1); + m_dragView = new DragChartView; + + p::match(rangeInfo) ( + p::pattern(p::as>(p::arg)) + = [this](auto dr) { + m_dragView->setRange(0, 100, dr.min, dr.max); + }, + p::pattern(p::as>(p::arg)) + = [this](auto ir) { + m_dragView->setRange(0, 100, ir.min, ir.max); + } + ); + //m_dragView->setRange(0, 100, 0, 100); + m_layout->addWidget(m_dragView, 1, 0, 1, 2); + m_applyButton = new QPushButton("Apply"); + // No connection to apply at first + m_applyButton->setEnabled(false); + m_cancelButton = new QPushButton("Cancel"); + m_layout->addWidget(m_cancelButton, 2, 0, 1, 1); + m_layout->addWidget(m_applyButton, 2, 1, 1, 1); + + connect(m_applyButton, &QPushButton::clicked, [this] { + auto proxy = m_latestNodeIndex + .data(DeviceModel::DynamicReadableProxyRole) + .value(); + //qDebug() << proxy; + auto points = m_dragView->vector(); + if (points.length() < 2) + return; + //qDebug() << points; + auto conn = std::make_shared>( + *proxy, points); + assignableConnectionChanged(conn); + }); + + m_dragView->yAxis().setTitleText(nodeName); + + m_dependableReadableComboBox->indexChanged + .connect([this](auto &index) { + m_latestNodeIndex = index; + m_applyButton->setEnabled(true); + auto nodeName = index.data(Qt::DisplayRole).toString(); + m_dragView->xAxis().setTitleText(nodeName); + }); + + setLayout(m_layout); + } + boost::signals2::signal)> + assignableConnectionChanged; +private: + AssignableProxy &m_assignableProxy; + DeviceModel &m_model; + DeviceProxyModel m_proxyModel; + DragChartView *m_dragView; + //NodeSelector *m_nodeSelector; + QComboBox *m_functionComboBox; + NodeSelector *m_dependableReadableComboBox; + QGridLayout *m_layout; + QLabel *m_dependableLabel; + QModelIndex m_latestNodeIndex; + QPushButton *m_applyButton, *m_cancelButton; + TuxClocker::Device::RangeInfo m_rangeInfo; +}; diff --git a/src/tuxclocker-qt/widgets/NodeSelector.hpp b/src/tuxclocker-qt/widgets/NodeSelector.hpp new file mode 100644 index 0000000..a90d650 --- /dev/null +++ b/src/tuxclocker-qt/widgets/NodeSelector.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// TODO: If a disabled item is clicked, the item that is highlighted get selected instead +class NodeSelector : public QComboBox { +public: + NodeSelector(QWidget *parent = nullptr) : QComboBox(parent), + m_skipNextHide(false) { + view()->viewport()->installEventFilter(this); + } + bool eventFilter(QObject *obj, QEvent *ev) { + if (ev->type() == QEvent::MouseButtonPress && obj == view()->viewport()) { + auto mouse_ev = static_cast(ev); + auto index = view()->indexAt(mouse_ev->pos()); + // Hide popup when an enabled item is clicked + if (!view()->visualRect(index).contains(mouse_ev->pos()) || + !(model()->flags(index) & Qt::ItemIsEnabled)) + m_skipNextHide = true; + else if (model()->flags(index) & Qt::ItemIsSelectable) { + qDebug() << index.data(DeviceModel::DynamicReadableProxyRole); + indexChanged(index); + m_skipNextHide = false; + } + } + return QComboBox::eventFilter(obj, ev); + } + virtual void hidePopup() override { + if (m_skipNextHide) + m_skipNextHide = false; + else + QComboBox::hidePopup(); + } + /*void mouseReleaseEvent(QMouseEvent *event) override { + auto index = view()->indexAt(event->pos()); + if (!view()->visualRect(index).contains(event->pos()) || + !(model()->flags(index) & Qt::ItemIsEnabled)) + qDebug() << index.data(); + + QComboBox::mouseReleaseEvent(event); + }*/ + void setView(QAbstractItemView *view) { + // Why no signal, Qt? + view->viewport()->installEventFilter(this); + + QComboBox::setView(view); + } + virtual void showPopup() override { + // TODO: don't account for scrollbar width when it isn't visible + // This is quite a weird way to do it but it works + auto vBarWidth = + QApplication::style()->pixelMetric(QStyle::PM_ScrollBarExtent); + view()->setMinimumWidth(view()->sizeHintForColumn(0) + vBarWidth); + QComboBox::showPopup(); + } + boost::signals2::signal indexChanged; +private: + bool m_skipNextHide; +};