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 <DeviceBrowser.hpp>
#include <DeviceModel.hpp>
#include <DeviceModelDelegate.hpp>
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusMetaType>
@ -10,10 +15,6 @@
#include <QVector>
#include <Tree.hpp>
#include <DeviceModel.hpp>
#include <DeviceModelDelegate.hpp>
#include "MainWindow.hpp"
namespace TCDBus = TuxClocker::DBus;
using namespace TuxClocker;
@ -48,9 +49,12 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
auto root = flatTree.toTree(flatTree);
auto view = new QTreeView;
/*auto view = new QTreeView;
view->setItemDelegate(new DeviceModelDelegate);
auto model = new DeviceModel(root);
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
//#include "DeviceModel.hpp"
#include "DeviceModel.hpp"
#include <QObject>
#include <QStandardItem>
@ -12,11 +12,14 @@ class AssignableItem : public QObject, public QStandardItem {
public:
AssignableItem(QObject *parent = nullptr) : QObject(parent), QStandardItem() {
}
void setData(const QVariant &v, int role = Qt::UserRole + 1) {
//if (role == DeviceModel::AssignableRole) ;
}
bool committal() {return m_committed;}
// 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:
void assignableDataChanged(QVariant value);
private:
Q_OBJECT
bool m_committed = false;
};

View File

@ -12,6 +12,9 @@ public:
m_enabled = false;
}
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;}
QVariant value() {return m_targetValue;}
private:
@ -19,4 +22,3 @@ private:
TC::Device::AssignableInfo m_info;
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 "AssignableItem.hpp"
#include "AssignableProxy.hpp"
#include <QDebug>
#include <QVariantAnimation>
using namespace mpark::patterns;
Q_DECLARE_METATYPE(AssignableItemData)
Q_DECLARE_METATYPE(TCDBus::Enumeration)
Q_DECLARE_METATYPE(TCDBus::Range)
@ -16,10 +23,12 @@ DeviceModel::DeviceModel(TC::TreeNode<TCDBus::DeviceNode> root, QObject *parent)
setColumnCount(2);
std::function<void(TC::TreeNode<TCDBus::DeviceNode> node, QStandardItem*)> traverse;
traverse = [&traverse](auto node, auto item) {
std::function<void(TC::TreeNode<TCDBus::DeviceNode> node,
QStandardItem*)> traverse;
traverse = [&traverse, this](auto node, auto item) {
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();
QList<QStandardItem*> rowItems;
@ -40,14 +49,44 @@ DeviceModel::DeviceModel(TC::TreeNode<TCDBus::DeviceNode> root, QObject *parent)
auto d_arg = qvariant_cast<QDBusArgument>(a_info);
switch (d_arg.currentType()) {
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;
d_arg >> r;
QVariant v;
AssignableItemData data(r.toAssignableInfo());
v.setValue(data);
ifaceItem->setData(v, Role::AssignableRole);
ifaceItem->setText(r.min.variant().toString());
//ifaceItem->setText(r.min.variant().toString());
rowItems.append(ifaceItem);
break;
}

View File

@ -1,6 +1,6 @@
#pragma once
//#include "AssignableItem.hpp"
#include "AssignableItem.hpp"
#include "AssignableItemData.hpp"
#include <DBusTypes.hpp>
@ -10,6 +10,7 @@
#include <QDBusConnection>
#include <QDBusInterface>
#include <QStandardItemModel>
#include <QPalette>
namespace p = mpark::patterns;
namespace TC = TuxClocker;
@ -32,4 +33,17 @@ public:
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);
}
}
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>
// TODO: align checkbox to the right
class DeviceModelDelegate : public QStyledItemDelegate {
public:
DeviceModelDelegate(QObject *parent = nullptr);
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const;
//void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
// const QModelIndex &index) const;
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
const QModelIndex &index) const;
void setEditorData(QWidget *editor, const QModelIndex &index) const;
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const;

View File

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