qt: add parametrization basics

This commit is contained in:
Jussi Kuokkanen 2020-07-21 17:50:01 +03:00
parent b29495629a
commit b0c39bfaeb
20 changed files with 616 additions and 112 deletions

View File

@ -0,0 +1,6 @@
#pragma once
class AbstractAssignableConnection {
public:
};

View File

@ -0,0 +1,32 @@
#pragma once
#include <boost/signals2.hpp>
#include <functional>
#include <QObject>
#include <QVariant>
// 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<void(QVariant, QString)> targetValueChanged;
boost::signals2::signal<void()> started;
boost::signals2::signal<void()> stopped;*/
signals:
void targetValueChanged(QVariant, QString);
void started();
void stopped();
private:
Q_OBJECT
};

View File

@ -1,5 +1,6 @@
#include "AssignableProxy.hpp"
#include <DBusTypes.hpp>
#include <QDBusReply>
#include <QDBusMessage>
@ -16,7 +17,20 @@ AssignableProxy::AssignableProxy(QString path, QDBusConnection conn,
m_iface = new QDBusInterface("org.tuxclocker",
path, "org.tuxclocker.Assignable", conn, this);
}
std::optional<AssignmentError> AssignableProxy::doApply(const QVariant &v) {
//qDebug() << v;
QDBusReply<TCD::Result<int>> reply = m_iface->call("assign", v);
if (reply.isValid()) {
TCD::Result<AssignmentError> ar{reply.value().error,
static_cast<AssignmentError>(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<TCD::Result<int>> reply = m_iface->call("assign", v);
if (reply.isValid()) {
qDebug() << reply.value().error << reply.value().value;
TCD::Result<AssignmentError> ar{reply.value().error,
static_cast<AssignmentError>(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<AssignableConnection> 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();
}

View File

@ -2,6 +2,7 @@
// Acts as a buffer for unapplied assignables and applies them asynchronously
#include <AssignableConnection.hpp>
#include <Device.hpp>
#include <memory>
#include <QDebug>
@ -17,12 +18,23 @@ public:
AssignableProxy(QString path, QDBusConnection conn,
QObject *parent = nullptr);
void apply();
void startConnection(std::shared_ptr<AssignableConnection> conn);
// Stop connection and clear current connection
void stopConnection();
void setValue(QVariant v) {m_value = v;}
signals:
void applied(std::optional<TC::Device::AssignmentError>);
void connectionValueChanged(std::variant<QVariant, TC::Device::AssignmentError>,
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<AssignableConnection> m_assignableConnection;
std::optional<TC::Device::AssignmentError> doApply(const QVariant &v);
};

View File

@ -2,6 +2,8 @@
#include "AssignableProxy.hpp"
#include "DynamicReadableProxy.hpp"
#include "qnamespace.h"
#include "qstandarditemmodel.h"
#include <fplus/fplus.hpp>
#include <QApplication>
#include <QDBusReply>
@ -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<TCDBus::DeviceNode> 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<TCDBus::DeviceNode> 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<TCDBus::Enumeration> enums) {
}, enums.toStdVector());
}
std::optional<const AssignableProxy*>
DeviceModel::assignableProxyFromItem(QStandardItem *item) {
return (m_assignableProxyHash.contains(item)) ?
std::optional(m_assignableProxyHash.value(item)) :
std::nullopt;
}
QStandardItem *DeviceModel::createAssignable(TC::TreeNode<TCDBus::DeviceNode> 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<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);
QVariant v;
v.setValue(itemData);
ifaceItem->setData(v, AssignableRole);
@ -159,6 +184,21 @@ QStandardItem *DeviceModel::createAssignable(TC::TreeNode<TCDBus::DeviceNode> 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<QStandardItem*> DeviceModel::setupAssignable(
TC::TreeNode<TCDBus::DeviceNode> 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<QStandardItem*> DeviceModel::setupDynReadable(
TC::TreeNode<TCDBus::DeviceNode> 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)(

View File

@ -2,23 +2,26 @@
#include "AssignableItem.hpp"
#include "AssignableItemData.hpp"
#include "DynamicReadableProxy.hpp"
#include <DBusTypes.hpp>
#include <Device.hpp>
#include <patterns.hpp>
#include <Tree.hpp>
#include <QDBusConnection>
#include <QDBusInterface>
#include <QFlags>
#include <QHash>
#include <QIcon>
#include <QStandardItemModel>
#include <QPalette>
#include <Tree.hpp>
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<InterfaceFlag> 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<const AssignableProxy*> 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<QStandardItem*, AssignableProxy*> m_assignableProxyHash;
// Separate handling interfaces since otherwise we run out of columns
QStandardItem *createAssignable(TC::TreeNode<TCDBus::DeviceNode> 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

View File

@ -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<DeviceModel::InterfaceFlag>();
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

View File

@ -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;

View File

@ -0,0 +1,106 @@
#pragma once
#include "AssignableConnection.hpp"
#include <DynamicReadableProxy.hpp>
#include <fplus/fplus.hpp>
#include <patterns.hpp>
#include <QDBusVariant>
#include <QDebug>
#include <QPointF>
#include <QRandomGenerator>
#include <QTimer>
#include <QVector>
#include <memory>
// delete these
using namespace fplus;
using namespace mpark::patterns;
using namespace TuxClocker::Device;
class DynamicReadableConnectionData {
public:
// y = f(x)
QVector<QPointF> 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 <typename OutType> // Result of linear interpolation
class DynamicReadableConnection : public AssignableConnection {
public:
DynamicReadableConnection(DynamicReadableProxy &proxy,
QVector<QPointF> 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<ReadableValue>(arg)) = [this](auto rv) {
match(rv) (
pattern(as<uint>(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<QPointF> m_points;
template <typename U>
void emitTargetValue(U reading) {
// Find two points from the vector so that:
// p[i].x < val < p[i + 1].x
std::optional<int> 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 <typename T>
T lerp(T a, T b, double t) {return a + (t * (b - a));}
};

View File

@ -4,7 +4,7 @@
int main(int argc, char **argv) {
QApplication app(argc, argv);
MainWindow mw;
mw.show();

View File

@ -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',

View File

@ -0,0 +1,89 @@
#include "DeviceBrowser.hpp"
#include "AssignableItemData.hpp"
#include "qnamespace.h"
#include <patterns.hpp>
#include <QVariant>
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<AssignableItemData>();
auto proxy = index.data(DeviceModel::AssignableProxyRole)
.value<AssignableProxy*>();
auto name = index.data(DeviceModel::NodeNameRole).toString();
match(a_info.assignableInfo()) (
pattern(as<RangeInfo>(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);
}

View File

@ -4,7 +4,7 @@
#include <DeviceModel.hpp>
#include <DeviceProxyModel.hpp>
#include <FlagEditor.hpp>
//#include <FunctionEditor.hpp>
#include <FunctionEditor.hpp>
#include <QDebug>
#include <QGridLayout>
#include <QLabel>
@ -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;

View File

@ -1,45 +1,50 @@
#include "DeviceTreeView.hpp"
#include "qcheckbox.h"
#include <DragChartView.hpp>
//#include <FunctionEditor.hpp>
#include <patterns.hpp>
#include <QCheckBox>
#include <QDebug>
#include <QHeaderView>
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<AssignableItemData>()) {
QCheckBox checkBox("Enable connection");
QAction editConn("Edit connection...");
auto enableConn = new QWidgetAction(&menu);
enableConn->setDefaultWidget(&checkBox);
menu.addActions({&editConn, enableConn});
if (data.canConvert<AssignableItemData>() &&
proxyData.canConvert<AssignableProxy*>()) {
functionEditorRequested(index);
/*auto a_data = data.value<AssignableItemData>();
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<AssignableProxy*>();
match(a_data.assignableInfo()) (
pattern(as<RangeInfo>(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);

View File

@ -1,7 +1,9 @@
#pragma once
#include <AssignableItemData.hpp>
#include <AssignableProxy.hpp>
#include <boost/signals2.hpp>
#include <Device.hpp>
#include <DeviceModel.hpp>
#include <DeviceModelDelegate.hpp>
#include <QMenu>
@ -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<void()> functionEditorRequested;
// TODO: make this more generalized
// Defers the complexity to DeviceBrowser
boost::signals2::signal<void(QModelIndex&)> functionEditorRequested;
protected:
/* Workaround for the retarded behavior of waiting for a double click,
you can't even disable it! */

View File

@ -176,6 +176,12 @@ void DragChartView::setVector(const QVector <QPointF> 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();
}

View File

@ -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 <QPointF> vector);
QVector <QPointF> 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*);

View File

@ -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();

View File

@ -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 <AssignableConnection.hpp>
#include <AssignableProxy.hpp>
#include <boost/signals2.hpp>
#include <DeviceProxyModel.hpp>
#include <DragChartView.hpp>
#include <DynamicReadableConnection.hpp>
#include <NodeSelector.hpp>
#include <patterns.hpp>
#include <QAbstractItemView>
#include <QComboBox>
#include <QDebug>
#include <QGridLayout>
#include <QLabel>
#include <QModelIndex>
#include <QPushButton>
#include <QTimer>
#include <QWidget>
// 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<TuxClocker::Device::Range<double>>(p::arg))
= [this](auto dr) {
m_dragView->setRange(0, 100, dr.min, dr.max);
},
p::pattern(p::as<TuxClocker::Device::Range<int>>(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<DynamicReadableProxy*>();
//qDebug() << proxy;
auto points = m_dragView->vector();
if (points.length() < 2)
return;
//qDebug() << points;
auto conn = std::make_shared<DynamicReadableConnection<int>>(
*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<void(std::shared_ptr<AssignableConnection>)>
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;
};

View File

@ -0,0 +1,65 @@
#pragma once
#include <boost/signals2.hpp>
#include <QApplication>
#include <QComboBox>
#include <QDebug>
#include <QEvent>
#include <QMouseEvent>
#include <QScrollBar>
// 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<QMouseEvent*>(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<void(QModelIndex&)> indexChanged;
private:
bool m_skipNextHide;
};