qt: add applying for range-based assignables

This commit is contained in:
Jussi Kuokkanen 2020-06-01 16:48:50 +03:00
parent a2ab1b99b6
commit b3a8dddc0d
14 changed files with 279 additions and 21 deletions

View File

@ -1,4 +1,9 @@
#include "MainWindow.hpp"
#include <DBusTypes.hpp> #include <DBusTypes.hpp>
#include <DeviceBrowser.hpp>
#include <DeviceModel.hpp>
#include <DeviceModelDelegate.hpp>
#include <QDBusConnection> #include <QDBusConnection>
#include <QDBusInterface> #include <QDBusInterface>
#include <QDBusMetaType> #include <QDBusMetaType>
@ -10,10 +15,6 @@
#include <QVector> #include <QVector>
#include <Tree.hpp> #include <Tree.hpp>
#include <DeviceModel.hpp>
#include <DeviceModelDelegate.hpp>
#include "MainWindow.hpp"
namespace TCDBus = TuxClocker::DBus; namespace TCDBus = TuxClocker::DBus;
using namespace TuxClocker; using namespace TuxClocker;
@ -48,9 +49,12 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
auto root = flatTree.toTree(flatTree); auto root = flatTree.toTree(flatTree);
auto view = new QTreeView; /*auto view = new QTreeView;
view->setItemDelegate(new DeviceModelDelegate); view->setItemDelegate(new DeviceModelDelegate);
auto model = new DeviceModel(root);
view->setModel(model); view->setModel(model);
setCentralWidget(view); setCentralWidget(view);*/
auto model = new DeviceModel(root);
auto browser = new DeviceBrowser(*model);
setCentralWidget(browser);
} }

View File

@ -0,0 +1,16 @@
#include "AssignableItem.hpp"
Q_DECLARE_METATYPE(AssignableItemData)
void AssignableItem::setData(const QVariant &v, int role) {
if (role == DeviceModel::AssignableRole) {
auto data = v.value<AssignableItemData>().value();
// Value is empty
if (data.isValid()) {
QVariant vr;
vr.setValue(data);
emit assignableDataChanged(vr);
}
}
QStandardItem::setData(v, role);
}

View File

@ -1,6 +1,6 @@
#pragma once #pragma once
//#include "DeviceModel.hpp" #include "DeviceModel.hpp"
#include <QObject> #include <QObject>
#include <QStandardItem> #include <QStandardItem>
@ -12,11 +12,14 @@ class AssignableItem : public QObject, public QStandardItem {
public: public:
AssignableItem(QObject *parent = nullptr) : QObject(parent), QStandardItem() { AssignableItem(QObject *parent = nullptr) : QObject(parent), QStandardItem() {
} }
void setData(const QVariant &v, int role = Qt::UserRole + 1) { bool committal() {return m_committed;}
//if (role == DeviceModel::AssignableRole) ; // Whether or not the set value shall be applied. Doesn't reset or change it.
} void setCommittal(bool on) {m_committed = on;}
void setData(const QVariant &v, int role = Qt::UserRole + 1);
signals: signals:
void assignableDataChanged(QVariant value); void assignableDataChanged(QVariant value);
private: private:
Q_OBJECT Q_OBJECT
bool m_committed = false;
}; };

View File

@ -12,6 +12,9 @@ public:
m_enabled = false; m_enabled = false;
} }
TC::Device::AssignableInfo assignableInfo() {return m_info;} TC::Device::AssignableInfo assignableInfo() {return m_info;}
bool committal() {return m_enabled;}
// Whether or not the set value shall be applied. Doesn't reset or change it.
void setCommittal(bool on) {m_enabled = on;}
void setValue(QVariant v) {m_targetValue = v;} void setValue(QVariant v) {m_targetValue = v;}
QVariant value() {return m_targetValue;} QVariant value() {return m_targetValue;}
private: private:
@ -19,4 +22,3 @@ private:
TC::Device::AssignableInfo m_info; TC::Device::AssignableInfo m_info;
QVariant m_targetValue; QVariant m_targetValue;
}; };
Q_DECLARE_METATYPE(TC::Device::AssignableInfo)

View File

@ -0,0 +1,43 @@
#include "AssignableProxy.hpp"
#include <DBusTypes.hpp>
#include <QDBusReply>
#include <QDBusMessage>
namespace TCD = TuxClocker::DBus;
using namespace TuxClocker::Device;
Q_DECLARE_METATYPE(TCD::Result<int>)
AssignableProxy::AssignableProxy(QString path, QDBusConnection conn,
QObject *parent) : QObject(parent) {
qDBusRegisterMetaType<TCD::Result<int>>();
m_iface = new QDBusInterface("org.tuxclocker",
path, "org.tuxclocker.Assignable", conn, this);
}
void AssignableProxy::apply() {
// A value hasn't been set yet
if (m_value.isNull())
return;
// Use QDBusVariant since otherwise tries to call with the wrong signature
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);
// Indicate that there's no pending value to applied by making value invalid
m_value = QVariant();
}

View File

@ -0,0 +1,28 @@
#pragma once
// Acts as a buffer for unapplied assignables and applies them asynchronously
#include <Device.hpp>
#include <memory>
#include <QDebug>
#include <QDBusConnection>
#include <QDBusInterface>
#include <QObject>
#include <variant>
namespace TC = TuxClocker;
class AssignableProxy : public QObject {
public:
AssignableProxy(QString path, QDBusConnection conn,
QObject *parent = nullptr);
void apply();
void setValue(QVariant v) {m_value = v;}
signals:
void applied(std::optional<TC::Device::AssignmentError>);
private:
Q_OBJECT
QVariant m_value;
QDBusInterface *m_iface;
};

View File

@ -1,5 +1,12 @@
#include "DeviceModel.hpp" #include "DeviceModel.hpp"
#include "AssignableItem.hpp"
#include "AssignableProxy.hpp"
#include <QDebug>
#include <QVariantAnimation>
using namespace mpark::patterns;
Q_DECLARE_METATYPE(AssignableItemData) Q_DECLARE_METATYPE(AssignableItemData)
Q_DECLARE_METATYPE(TCDBus::Enumeration) Q_DECLARE_METATYPE(TCDBus::Enumeration)
Q_DECLARE_METATYPE(TCDBus::Range) Q_DECLARE_METATYPE(TCDBus::Range)
@ -16,10 +23,12 @@ DeviceModel::DeviceModel(TC::TreeNode<TCDBus::DeviceNode> root, QObject *parent)
setColumnCount(2); setColumnCount(2);
std::function<void(TC::TreeNode<TCDBus::DeviceNode> node, QStandardItem*)> traverse; std::function<void(TC::TreeNode<TCDBus::DeviceNode> node,
traverse = [&traverse](auto node, auto item) { QStandardItem*)> traverse;
traverse = [&traverse, this](auto node, auto item) {
auto conn = QDBusConnection::systemBus(); auto conn = QDBusConnection::systemBus();
QDBusInterface nodeIface("org.tuxclocker", node.value().path, "org.tuxclocker.Node", conn); QDBusInterface nodeIface("org.tuxclocker",
node.value().path,"org.tuxclocker.Node", conn);
auto nodeName = nodeIface.property("name").toString(); auto nodeName = nodeIface.property("name").toString();
QList<QStandardItem*> rowItems; QList<QStandardItem*> rowItems;
@ -40,14 +49,44 @@ DeviceModel::DeviceModel(TC::TreeNode<TCDBus::DeviceNode> root, QObject *parent)
auto d_arg = qvariant_cast<QDBusArgument>(a_info); auto d_arg = qvariant_cast<QDBusArgument>(a_info);
switch (d_arg.currentType()) { switch (d_arg.currentType()) {
case QDBusArgument::StructureType: { case QDBusArgument::StructureType: {
auto ifaceItem = new QStandardItem; auto ifaceItem = new AssignableItem;
ifaceItem->setEditable(true);
//ifaceItem->setCheckable(true);
auto proxy = new AssignableProxy(node.value().path, conn, this);
connect(ifaceItem, &AssignableItem::assignableDataChanged,
[=](QVariant v) {
proxy->setValue(v);
ifaceItem->setData(unappliedColor(), Qt::BackgroundRole);
});
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);
});
anim->start(QAbstractAnimation::DeleteWhenStopped);
});
connect(this, &DeviceModel::changesApplied, [=] {
proxy->apply();
});
TCDBus::Range r; TCDBus::Range r;
d_arg >> r; d_arg >> r;
QVariant v; QVariant v;
AssignableItemData data(r.toAssignableInfo()); AssignableItemData data(r.toAssignableInfo());
v.setValue(data); v.setValue(data);
ifaceItem->setData(v, Role::AssignableRole); ifaceItem->setData(v, Role::AssignableRole);
ifaceItem->setText(r.min.variant().toString()); //ifaceItem->setText(r.min.variant().toString());
rowItems.append(ifaceItem); rowItems.append(ifaceItem);
break; break;
} }

View File

@ -1,6 +1,6 @@
#pragma once #pragma once
//#include "AssignableItem.hpp" #include "AssignableItem.hpp"
#include "AssignableItemData.hpp" #include "AssignableItemData.hpp"
#include <DBusTypes.hpp> #include <DBusTypes.hpp>
@ -10,6 +10,7 @@
#include <QDBusConnection> #include <QDBusConnection>
#include <QDBusInterface> #include <QDBusInterface>
#include <QStandardItemModel> #include <QStandardItemModel>
#include <QPalette>
namespace p = mpark::patterns; namespace p = mpark::patterns;
namespace TC = TuxClocker; namespace TC = TuxClocker;
@ -32,4 +33,17 @@ public:
enum class FilterFlag { enum class FilterFlag {
}; };
// For decoupling AssignableItems created in the model
void applyChanges() {emit changesApplied();}
signals:
void changesApplied();
private:
Q_OBJECT
constexpr int fadeOutTime() {return 5000;}
constexpr int transparency() {return 120;}
// Colors for items
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

@ -56,3 +56,15 @@ void DeviceModelDelegate::setModelData(QWidget *editor,
model->setData(index, v, DeviceModel::AssignableRole); model->setData(index, v, DeviceModel::AssignableRole);
} }
} }
void DeviceModelDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option, const QModelIndex&) const {
// Why do I need to override this to perform such a basic task?
editor->setGeometry(option.rect);
}
/*void DeviceModelDelegate::drawCheck(QPainter *painter,
const QStyleOptionViewItem &option, const QRect &rect,
Qt::CheckState state) const {
QStyledItemDelegate::drawCheck(painter, option, rect, state)
}*/

View File

@ -2,13 +2,14 @@
#include <QStyledItemDelegate> #include <QStyledItemDelegate>
// TODO: align checkbox to the right
class DeviceModelDelegate : public QStyledItemDelegate { class DeviceModelDelegate : public QStyledItemDelegate {
public: public:
DeviceModelDelegate(QObject *parent = nullptr); DeviceModelDelegate(QObject *parent = nullptr);
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const; const QModelIndex &index) const;
//void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
// const QModelIndex &index) const; const QModelIndex &index) const;
void setEditorData(QWidget *editor, const QModelIndex &index) const; void setEditorData(QWidget *editor, const QModelIndex &index) const;
void setModelData(QWidget *editor, QAbstractItemModel *model, void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const; const QModelIndex &index) const;

View File

@ -2,12 +2,17 @@ qt5 = import('qt5')
qt5_dep = dependency('qt5', modules: ['DBus', 'Charts', 'Widgets']) qt5_dep = dependency('qt5', modules: ['DBus', 'Charts', 'Widgets'])
moc_files = qt5.preprocess(moc_headers: ['MainWindow.hpp', moc_files = qt5.preprocess(moc_headers: ['MainWindow.hpp',
'data/AssignableItem.hpp'], 'data/AssignableItem.hpp',
'data/AssignableProxy.hpp',
'data/DeviceModel.hpp'],
dependencies: qt5_dep) dependencies: qt5_dep)
sources = ['main.cpp', sources = ['main.cpp',
'data/AssignableItem.cpp',
'data/AssignableProxy.cpp',
'data/DeviceModel.cpp', 'data/DeviceModel.cpp',
'data/DeviceModelDelegate.cpp', 'data/DeviceModelDelegate.cpp',
'widgets/DeviceTreeView.cpp',
'MainWindow.cpp'] 'MainWindow.cpp']
local_incdir = include_directories(['data', local_incdir = include_directories(['data',
@ -17,6 +22,7 @@ local_incdir = include_directories(['data',
executable('tuxclocker-qt', executable('tuxclocker-qt',
moc_files, moc_files,
sources, sources,
override_options : ['cpp_std=c++17'],
dependencies: qt5_dep, dependencies: qt5_dep,
include_directories: [incdir, local_incdir], include_directories: [incdir, local_incdir],
install: true) install: true)

View File

@ -0,0 +1,32 @@
#pragma once
#include <DeviceTreeView.hpp>
#include <DeviceModel.hpp>
#include <QGridLayout>
#include <QPushButton>
#include <QWidget>
// 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_treeView = new DeviceTreeView(model);
m_apply = new QPushButton("Apply changes");
m_apply->setEnabled(true);
connect(m_apply, &QPushButton::pressed, &m_deviceModel, &DeviceModel::applyChanges);
m_layout->addWidget(m_treeView, 0, 0);
m_layout->addWidget(m_apply, 1, 0);
setLayout(m_layout);
}
private:
DeviceModel &m_deviceModel;
DeviceTreeView *m_treeView;
QPushButton *m_apply;
QGridLayout *m_layout;
};

View File

@ -0,0 +1,39 @@
#include "DeviceTreeView.hpp"
#include <QCheckBox>
#include <QDebug>
Q_DECLARE_METATYPE(AssignableItemData)
DeviceTreeView::DeviceTreeView(DeviceModel &model, QWidget *parent)
: QTreeView(parent), m_deviceModel(model) {
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &QTreeView::customContextMenuRequested, [this](QPoint point) {
auto index = indexAt(point);
auto data = index.data(DeviceModel::AssignableRole);
QMenu menu;
if (data.canConvert<AssignableItemData>()) {
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, [&] {
qDebug() << a_data.committal();
QVariant v;
v.setValue(a_data);
m_deviceModel.setData(index, v, DeviceModel::AssignableRole);
});
menu.addAction(commitAct);
menu.exec(QCursor::pos());
}
});
m_delegate = new DeviceModelDelegate(this);
setItemDelegate(m_delegate);
setModel(&m_deviceModel);
}

View File

@ -0,0 +1,19 @@
#pragma once
#include <AssignableItemData.hpp>
#include <DeviceModel.hpp>
#include <DeviceModelDelegate.hpp>
#include <QMenu>
#include <QTreeView>
#include <QWidgetAction>
// Class for handling menus on DeviceModel
class DeviceTreeView : public QTreeView {
public:
DeviceTreeView(DeviceModel &model, QWidget *parent = nullptr);
// Accessor method for connecting everything in the browser
const DeviceModel &deviceModel() {return m_deviceModel;}
private:
DeviceModel &m_deviceModel;
DeviceModelDelegate *m_delegate;
};