mirror of
https://github.com/Lurkki14/tuxclocker.git
synced 2025-02-25 18:55:24 -06:00
qt: add filtering based on node type
This commit is contained in:
parent
b09f5e3848
commit
ad81d8d13c
@ -5,12 +5,16 @@ 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
|
||||
// Value is not empty
|
||||
if (data.isValid()) {
|
||||
QVariant vr;
|
||||
vr.setValue(data);
|
||||
emit assignableDataChanged(vr);
|
||||
}
|
||||
}
|
||||
if (role == Qt::CheckStateRole) {
|
||||
bool state = v.toBool();
|
||||
emit committalChanged(state);
|
||||
}
|
||||
QStandardItem::setData(v, role);
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ public:
|
||||
void setData(const QVariant &v, int role = Qt::UserRole + 1);
|
||||
signals:
|
||||
void assignableDataChanged(QVariant value);
|
||||
void committalChanged(bool on);
|
||||
private:
|
||||
Q_OBJECT
|
||||
|
||||
|
@ -3,7 +3,9 @@
|
||||
#include "AssignableProxy.hpp"
|
||||
#include "DynamicReadableProxy.hpp"
|
||||
#include <fplus/fplus.hpp>
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QStyle>
|
||||
#include <QVariantAnimation>
|
||||
|
||||
// 'match' is a method in QAbstractItemModel :(
|
||||
@ -46,12 +48,25 @@ DeviceModel::DeviceModel(TC::TreeNode<TCDBus::DeviceNode> root, QObject *parent)
|
||||
pattern("org.tuxclocker.Assignable") = [=, &rowItems]{
|
||||
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);
|
||||
};
|
||||
},
|
||||
pattern("org.tuxclocker.DynamicReadable") = [=, &rowItems] {
|
||||
if_let(pattern(some(arg)) = setupDynReadable(node, conn))
|
||||
= [&](auto item) {
|
||||
auto icon = dynamicReadableIcon();
|
||||
nameItem->setData(icon, Qt::DecorationRole);
|
||||
|
||||
nameItem->setData(DeviceModel::DynamicReadable,
|
||||
InterfaceTypeRole);
|
||||
rowItems.append(item);
|
||||
};
|
||||
},
|
||||
@ -81,6 +96,7 @@ QStandardItem *DeviceModel::createAssignable(TC::TreeNode<TCDBus::DeviceNode> no
|
||||
QVariant v;
|
||||
v.setValue(itemData);
|
||||
ifaceItem->setData(v, AssignableRole);
|
||||
ifaceItem->setText("No value set");
|
||||
|
||||
connect(ifaceItem, &AssignableItem::assignableDataChanged,
|
||||
[=](QVariant v) {
|
||||
|
@ -9,6 +9,8 @@
|
||||
#include <Tree.hpp>
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusInterface>
|
||||
#include <QFlags>
|
||||
#include <QIcon>
|
||||
#include <QStandardItemModel>
|
||||
#include <QPalette>
|
||||
|
||||
@ -22,20 +24,33 @@ class DeviceModel : public QStandardItemModel {
|
||||
public:
|
||||
DeviceModel(TC::TreeNode<TCDBus::DeviceNode> root, QObject *parent = nullptr);
|
||||
enum ColumnType {
|
||||
Name = 0, // Node name
|
||||
Interface = 1 // Column for presenting interfaces
|
||||
NameColumn = 0, // Node name
|
||||
InterfaceColumn = 1 // Column for presenting interfaces
|
||||
};
|
||||
|
||||
enum Role {
|
||||
AssignableRole = Qt::UserRole, // Holds the data about the assignable
|
||||
ConnectionRole // Data about the connection
|
||||
ConnectionRole, // Data about the connection
|
||||
InterfaceTypeRole // InterfaceType
|
||||
};
|
||||
|
||||
enum InterfaceFlag {
|
||||
Assignable = 1,
|
||||
DynamicReadable = 2,
|
||||
StaticReadable = 4,
|
||||
AllInterfaces = (Assignable | DynamicReadable | StaticReadable)
|
||||
};
|
||||
typedef QFlags<InterfaceFlag> InterfaceFlags;
|
||||
|
||||
enum class FilterFlag {
|
||||
|
||||
};
|
||||
// For decoupling AssignableItems created in the model
|
||||
void applyChanges() {emit changesApplied();}
|
||||
|
||||
static QIcon assignableIcon() {return QIcon::fromTheme("edit-entry");}
|
||||
static QIcon dynamicReadableIcon() {return QIcon(":/ruler.svg");}
|
||||
|
||||
signals:
|
||||
void changesApplied();
|
||||
private:
|
||||
|
38
src/tuxclocker-qt/data/DeviceProxyModel.cpp
Normal file
38
src/tuxclocker-qt/data/DeviceProxyModel.cpp
Normal file
@ -0,0 +1,38 @@
|
||||
#include "DeviceProxyModel.hpp"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
Q_DECLARE_METATYPE(DeviceModel::InterfaceFlag)
|
||||
|
||||
DeviceProxyModel::DeviceProxyModel(DeviceModel &model, QObject *parent) :
|
||||
QSortFilterProxyModel(parent) {
|
||||
setSourceModel(&model);
|
||||
m_flags = DeviceModel::AllInterfaces;
|
||||
}
|
||||
|
||||
bool DeviceProxyModel::filterAcceptsRow(int sourceRow,
|
||||
const QModelIndex &sourceParent) const {
|
||||
auto model = sourceModel();
|
||||
// Interface type is stored in the item with the name
|
||||
auto thisItem = model->index(sourceRow, DeviceModel::NameColumn,
|
||||
sourceParent);
|
||||
|
||||
// Check recursively if a child item has the flag set that we want to show
|
||||
bool shouldHide = true;
|
||||
std::function<void(QModelIndex)> traverse;
|
||||
traverse = [&](QModelIndex index) {
|
||||
auto data = index.data(DeviceModel::InterfaceTypeRole);
|
||||
auto ifaceType = data.value<DeviceModel::InterfaceFlag>();
|
||||
if (data.isValid() && (m_flags & ifaceType)) {
|
||||
// Item has the flag we want to show
|
||||
shouldHide = false;
|
||||
return;
|
||||
}
|
||||
auto rowCount = model->rowCount(index);
|
||||
for (int i = 0; i < rowCount; i++)
|
||||
traverse(model->index(i, DeviceModel::NameColumn, index));
|
||||
};
|
||||
traverse(thisItem);
|
||||
|
||||
return !shouldHide;
|
||||
}
|
20
src/tuxclocker-qt/data/DeviceProxyModel.hpp
Normal file
20
src/tuxclocker-qt/data/DeviceProxyModel.hpp
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "DeviceModel.hpp"
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
// Filters items from the device model based on the node interface type
|
||||
class DeviceProxyModel : public QSortFilterProxyModel {
|
||||
public:
|
||||
DeviceProxyModel(DeviceModel &model, QObject *parent = nullptr);
|
||||
DeviceModel::InterfaceFlags filterFlags() {return m_flags;}
|
||||
void setFlags(DeviceModel::InterfaceFlags flags) {
|
||||
m_flags = flags;
|
||||
invalidateFilter();
|
||||
}
|
||||
protected:
|
||||
bool filterAcceptsRow(int sourceRow,
|
||||
const QModelIndex &sourceParent) const override;
|
||||
private:
|
||||
DeviceModel::InterfaceFlags m_flags;
|
||||
};
|
@ -1,11 +1,16 @@
|
||||
qt5 = import('qt5')
|
||||
qt5_dep = dependency('qt5', modules: ['DBus', 'Charts', 'Widgets'])
|
||||
|
||||
# signals2
|
||||
boost_dep = dependency('boost')
|
||||
|
||||
moc_files = qt5.preprocess(moc_headers: ['MainWindow.hpp',
|
||||
'data/AssignableItem.hpp',
|
||||
'data/AssignableProxy.hpp',
|
||||
'data/DeviceModel.hpp',
|
||||
'data/DynamicReadableProxy.hpp'],
|
||||
'data/DynamicReadableProxy.hpp',
|
||||
'widgets/DragChartView.hpp'],
|
||||
qresources : ['resources/resources.qrc'],
|
||||
dependencies: qt5_dep)
|
||||
|
||||
sources = ['main.cpp',
|
||||
@ -13,9 +18,12 @@ sources = ['main.cpp',
|
||||
'data/AssignableProxy.cpp',
|
||||
'data/DeviceModel.cpp',
|
||||
'data/DeviceModelDelegate.cpp',
|
||||
'data/DeviceProxyModel.cpp',
|
||||
'data/DynamicReadableProxy.cpp',
|
||||
'widgets/DeviceTreeView.cpp',
|
||||
'widgets/DragChartView.cpp',
|
||||
'widgets/EnumEditor.cpp',
|
||||
'widgets/FlagEditor.cpp',
|
||||
'MainWindow.cpp']
|
||||
|
||||
local_incdir = include_directories(['data',
|
||||
|
2
src/tuxclocker-qt/resources/attributions
Normal file
2
src/tuxclocker-qt/resources/attributions
Normal file
@ -0,0 +1,2 @@
|
||||
flaticon.com:
|
||||
Kiranshastry
|
5
src/tuxclocker-qt/resources/resources.qrc
Normal file
5
src/tuxclocker-qt/resources/resources.qrc
Normal file
@ -0,0 +1,5 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>ruler.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
53
src/tuxclocker-qt/resources/ruler.svg
Normal file
53
src/tuxclocker-qt/resources/ruler.svg
Normal file
@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 466.85 466.85" style="enable-background:new 0 0 466.85 466.85;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M463.925,122.425l-119.5-119.5c-3.9-3.9-10.2-3.9-14.1,0l-327.4,327.4c-3.9,3.9-3.9,10.2,0,14.1l119.5,119.5
|
||||
c3.9,3.9,10.2,3.9,14.1,0l327.4-327.4C467.825,132.625,467.825,126.325,463.925,122.425z M129.425,442.725l-105.3-105.3l79.1-79.1
|
||||
l35.9,35.9c3.8,4,10.2,4.1,14.1,0.2c4-3.8,4.1-10.2,0.2-14.1c-0.1-0.1-0.1-0.1-0.2-0.2l-35.9-35.8l26.1-26.1l56,56
|
||||
c3.9,3.9,10.3,3.9,14.1-0.1c3.9-3.9,3.9-10.2,0-14.1l-56-56l26.1-26.1l35.9,35.8c3.9,3.9,10.2,3.9,14.1,0c3.9-3.9,3.9-10.2,0-14.1
|
||||
l-35.9-35.8l26.1-26.1l56,56c3.9,3.9,10.2,3.9,14.1,0c3.9-3.9,3.9-10.2,0-14.1l-56-56l26.1-26.1l35.9,35.9
|
||||
c3.9,3.9,10.2,4,14.1,0.1c3.9-3.9,4-10.2,0.1-14.1c0,0,0,0-0.1-0.1l-35.6-36.2l26.1-26.1l56,56c3.9,3.9,10.2,3.9,14.1,0
|
||||
c3.9-3.9,3.9-10.2,0-14.1l-56-56l18.8-18.8l105.3,105.3L129.425,442.725z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M137.325,331.325c-12.6-12.5-32.9-12.5-45.4,0c-12.5,12.6-12.5,32.9,0,45.4s32.9,12.5,45.4,0
|
||||
S149.825,343.925,137.325,331.325z M124.225,362.325c-0.2,0.2-0.5,0.5-1.1,0.4c-4.7,4.7-12.4,4.7-17.2,0c-4.7-4.7-4.7-12.4,0-17.2
|
||||
c4.7-4.7,12.4-4.7,17.2,0C128.025,350.025,128.725,357.425,124.225,362.325z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
@ -2,7 +2,11 @@
|
||||
|
||||
#include <DeviceTreeView.hpp>
|
||||
#include <DeviceModel.hpp>
|
||||
#include <DeviceProxyModel.hpp>
|
||||
#include <FlagEditor.hpp>
|
||||
#include <QDebug>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QWidget>
|
||||
|
||||
@ -12,21 +16,50 @@ public:
|
||||
DeviceBrowser(DeviceModel &model,
|
||||
QWidget *parent = nullptr) : QWidget(parent), m_deviceModel(model) {
|
||||
m_layout = new QGridLayout(this);
|
||||
m_treeView = new DeviceTreeView(model);
|
||||
m_proxyModel = new DeviceProxyModel(model, this);
|
||||
m_treeView = new DeviceTreeView(this);
|
||||
m_treeView->setModel(m_proxyModel);
|
||||
m_flagLabel = new QLabel("Showing:");
|
||||
m_apply = new QPushButton("Apply changes");
|
||||
m_apply->setEnabled(true);
|
||||
|
||||
connect(m_apply, &QPushButton::pressed, &m_deviceModel, &DeviceModel::applyChanges);
|
||||
m_flagEditor = new FlagEditor(
|
||||
QVector({
|
||||
std::tuple(
|
||||
QString("Assignables"),
|
||||
DeviceModel::assignableIcon(),
|
||||
DeviceModel::Assignable
|
||||
),
|
||||
std::tuple(
|
||||
QString("Dynamic Values"),
|
||||
DeviceModel::dynamicReadableIcon(),
|
||||
DeviceModel::DynamicReadable
|
||||
)
|
||||
}), this);
|
||||
|
||||
m_layout->addWidget(m_treeView, 0, 0);
|
||||
m_layout->addWidget(m_apply, 1, 0);
|
||||
connect(m_apply, &QPushButton::pressed, &m_deviceModel,
|
||||
&DeviceModel::applyChanges);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private:
|
||||
DeviceModel &m_deviceModel;
|
||||
DeviceProxyModel *m_proxyModel;
|
||||
DeviceTreeView *m_treeView;
|
||||
FlagEditor<DeviceModel::InterfaceFlag> *m_flagEditor;
|
||||
QLabel *m_flagLabel;
|
||||
QPushButton *m_apply;
|
||||
QGridLayout *m_layout;
|
||||
};
|
||||
|
@ -1,12 +1,13 @@
|
||||
#include "DeviceTreeView.hpp"
|
||||
|
||||
#include <DragChartView.hpp>
|
||||
#include <QCheckBox>
|
||||
#include <QDebug>
|
||||
|
||||
Q_DECLARE_METATYPE(AssignableItemData)
|
||||
|
||||
DeviceTreeView::DeviceTreeView(DeviceModel &model, QWidget *parent)
|
||||
: QTreeView(parent), m_deviceModel(model) {
|
||||
DeviceTreeView::DeviceTreeView(QWidget *parent)
|
||||
: QTreeView(parent) {
|
||||
auto triggers = editTriggers() ^= DoubleClicked;
|
||||
triggers |= SelectedClicked;
|
||||
setEditTriggers(SelectedClicked | EditKeyPressed);
|
||||
@ -16,7 +17,7 @@ DeviceTreeView::DeviceTreeView(DeviceModel &model, QWidget *parent)
|
||||
auto data = index.data(DeviceModel::AssignableRole);
|
||||
QMenu menu;
|
||||
if (data.canConvert<AssignableItemData>()) {
|
||||
auto a_data = data.value<AssignableItemData>();
|
||||
/*auto a_data = data.value<AssignableItemData>();
|
||||
QCheckBox commitCb("Commit");
|
||||
auto commitAct = new QWidgetAction(&menu);
|
||||
commitAct->setDefaultWidget(&commitCb);
|
||||
@ -26,17 +27,18 @@ DeviceTreeView::DeviceTreeView(DeviceModel &model, QWidget *parent)
|
||||
});
|
||||
// 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());
|
||||
menu.exec(QCursor::pos());*/
|
||||
|
||||
auto dragView = new DragChartView;
|
||||
dragView->show();
|
||||
}
|
||||
});
|
||||
m_delegate = new DeviceModelDelegate(this);
|
||||
|
||||
setItemDelegate(m_delegate);
|
||||
setModel(&m_deviceModel);
|
||||
}
|
||||
|
@ -10,9 +10,9 @@
|
||||
// Class for handling menus on DeviceModel
|
||||
class DeviceTreeView : public QTreeView {
|
||||
public:
|
||||
DeviceTreeView(DeviceModel &model, QWidget *parent = nullptr);
|
||||
DeviceTreeView(QWidget *parent = nullptr);
|
||||
// Accessor method for connecting everything in the browser
|
||||
const DeviceModel &deviceModel() {return m_deviceModel;}
|
||||
//const DeviceModel &deviceModel() {return m_deviceModel;}
|
||||
protected:
|
||||
/* Workaround for the retarded behavior of waiting for a double click,
|
||||
you can't even disable it! */
|
||||
@ -23,6 +23,6 @@ protected:
|
||||
QAbstractItemView::AllEditTriggers : trigger, event);
|
||||
}
|
||||
private:
|
||||
DeviceModel &m_deviceModel;
|
||||
//DeviceModel &m_deviceModel;
|
||||
DeviceModelDelegate *m_delegate;
|
||||
};
|
||||
|
360
src/tuxclocker-qt/widgets/DragChartView.cpp
Normal file
360
src/tuxclocker-qt/widgets/DragChartView.cpp
Normal file
@ -0,0 +1,360 @@
|
||||
#include "DragChartView.hpp"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QToolTip>
|
||||
#include <QApplication>
|
||||
#include <QValueAxis>
|
||||
#include <QScreen>
|
||||
#include <QWindow>
|
||||
|
||||
DragChartView::DragChartView(QWidget *parent) : QChartView(parent)
|
||||
{
|
||||
chart()->installEventFilter(this);
|
||||
|
||||
setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
m_toolTipLabel = new QLabel;
|
||||
m_toolTipLabel->setWindowFlag(Qt::ToolTip);
|
||||
|
||||
m_dragCanStart = false;
|
||||
|
||||
m_mouseInLimitArea = false;
|
||||
|
||||
m_scatterPressed = false;
|
||||
|
||||
m_leftLineFillerItem = new QGraphicsLineItem;
|
||||
m_rightLineFillerItem = new QGraphicsLineItem;
|
||||
m_leftLineFillerItem->setPen(fillerLinePen());
|
||||
m_rightLineFillerItem->setPen(fillerLinePen());
|
||||
|
||||
chart()->scene()->addItem(m_leftLineFillerItem);
|
||||
chart()->scene()->addItem(m_rightLineFillerItem);
|
||||
chart()->legend()->setVisible(false);
|
||||
|
||||
m_chartMargin = m_series.markerSize() / 2;
|
||||
|
||||
// Resize axes by margin
|
||||
connect(&m_xAxis, &QValueAxis::rangeChanged, [=](qreal min, qreal max) {
|
||||
m_limitRect.setLeft(min);
|
||||
m_limitRect.setRight(max);
|
||||
|
||||
m_limitRect.setTopLeft(QPointF(min, m_limitRect.top()));
|
||||
m_limitRect.setBottomRight(QPointF(max, m_limitRect.bottom()));
|
||||
|
||||
if (chart()->rect().isNull()) {
|
||||
return;
|
||||
}
|
||||
// Convert m_chartMargin to chart value
|
||||
auto valueDelta = abs(chart()->mapToValue(QPointF(0, 0)).x() - chart()->mapToValue(QPointF(m_chartMargin, 0)).x());
|
||||
|
||||
m_xAxis.blockSignals(true); // Don't go to an infinite loop
|
||||
m_xAxis.setRange(min - valueDelta, max + valueDelta);
|
||||
m_xAxis.blockSignals(false);
|
||||
});
|
||||
|
||||
connect(&m_yAxis, &QValueAxis::rangeChanged, [=](qreal min, qreal max) {
|
||||
// Update limit rect
|
||||
m_limitRect.setBottom(min);
|
||||
m_limitRect.setTop(max);
|
||||
|
||||
m_limitRect.setTopLeft(QPointF(m_limitRect.left(), max));
|
||||
m_limitRect.setBottomRight(QPointF(m_limitRect.right(), min));
|
||||
|
||||
if (chart()->rect().isNull()) {
|
||||
return;
|
||||
}
|
||||
auto valueDelta = abs(chart()->mapToValue(QPointF(0, 0)).x() - chart()->mapToValue(QPointF(m_chartMargin, 0)).x());
|
||||
m_yAxis.blockSignals(true); // Don't go to an infinite loop
|
||||
m_yAxis.setRange(min - valueDelta, max + valueDelta);
|
||||
m_yAxis.blockSignals(false);
|
||||
});
|
||||
|
||||
// Delete filler items when points are removed
|
||||
connect(&m_series, &QScatterSeries::pointRemoved, [=]() {
|
||||
chart()->scene()->removeItem(m_lineFillerItems.last());
|
||||
delete m_lineFillerItems.last();
|
||||
m_lineFillerItems.pop_back();
|
||||
drawFillerLines(&m_series);
|
||||
});
|
||||
|
||||
// Add filler item when point is added
|
||||
connect(&m_series, &QScatterSeries::pointAdded, [=]() {
|
||||
if (m_series.pointsVector().length() < 2)
|
||||
return;
|
||||
|
||||
auto item = new QGraphicsLineItem;
|
||||
item->setPen(fillerLinePen());
|
||||
m_lineFillerItems.append(item);
|
||||
chart()->scene()->addItem(item);
|
||||
drawFillerLines(&m_series);
|
||||
});
|
||||
|
||||
connect(&m_series, &QScatterSeries::pointsReplaced, [=]() {
|
||||
// Delete filler items
|
||||
for (auto item : m_lineFillerItems) {
|
||||
delete item;
|
||||
}
|
||||
m_lineFillerItems.clear();
|
||||
// Create new ones
|
||||
for (int i = 0; i < m_series.pointsVector().length(); i++) {
|
||||
auto item = new QGraphicsLineItem;
|
||||
item->setPen(fillerLinePen());
|
||||
m_lineFillerItems.append(item);
|
||||
chart()->scene()->addItem(item);
|
||||
}
|
||||
drawFillerLines(&m_series);
|
||||
});
|
||||
|
||||
connect(&m_series, &QScatterSeries::pressed, [=](QPointF point) {
|
||||
m_dragCanStart = true;
|
||||
m_latestScatterPoint = point;
|
||||
m_scatterPressed = true;
|
||||
});
|
||||
|
||||
connect(&m_series, &QScatterSeries::clicked, [=](const QPointF point) {
|
||||
m_scatterPressed = false;
|
||||
if (m_dragActive) {
|
||||
m_dragActive = false;
|
||||
emit dragEnded(point);
|
||||
}
|
||||
else {
|
||||
// Just a click, delete point
|
||||
m_series.remove(point);
|
||||
}
|
||||
});
|
||||
|
||||
chart()->addSeries(&m_series);
|
||||
|
||||
chart()->addAxis(&m_xAxis, Qt::AlignBottom);
|
||||
chart()->addAxis(&m_yAxis, Qt::AlignLeft);
|
||||
|
||||
m_series.attachAxis(&m_xAxis);
|
||||
m_series.attachAxis(&m_yAxis);
|
||||
|
||||
m_xAxis.setRange(0, 25);
|
||||
m_yAxis.setRange(0, 25);
|
||||
|
||||
chart()->setBackgroundRoundness(0);
|
||||
|
||||
// Set theme colors
|
||||
chart()->setBackgroundBrush(QBrush(QPalette().color(QPalette::Background)));
|
||||
|
||||
chart()->legend()->setLabelColor(QPalette().color(QPalette::Text));
|
||||
|
||||
m_yAxis.setLabelsColor(QPalette().color(QPalette::Text));
|
||||
m_xAxis.setLabelsColor(QPalette().color(QPalette::Text));
|
||||
|
||||
m_yAxis.setTitleBrush(QBrush(QPalette().color(QPalette::Text)));
|
||||
m_xAxis.setTitleBrush(QBrush(QPalette().color(QPalette::Text)));
|
||||
|
||||
// Set cursor to indicate dragging
|
||||
connect(this, &DragChartView::dragStarted, [=]() {
|
||||
setCursor(Qt::ClosedHandCursor);
|
||||
});
|
||||
|
||||
connect(this, &DragChartView::dragEnded, [=]() {
|
||||
if (m_mouseInLimitArea) {
|
||||
setCursor(Qt::CrossCursor);
|
||||
}
|
||||
else {
|
||||
setCursor(Qt::ArrowCursor);
|
||||
}
|
||||
});
|
||||
|
||||
connect(this, &DragChartView::limitAreaEntered, [=]() {
|
||||
setCursor(Qt::CrossCursor);
|
||||
});
|
||||
|
||||
connect(this, &DragChartView::limitAreaExited, [=]() {
|
||||
if (cursor().shape() != Qt::ClosedHandCursor) {
|
||||
setCursor(Qt::ArrowCursor);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void DragChartView::setVector(const QVector <QPointF> vector) {
|
||||
m_series.replace(vector);
|
||||
}
|
||||
|
||||
bool DragChartView::event(QEvent *event) {
|
||||
//qDebug() << event->type();
|
||||
|
||||
if (event->type() == QEvent::Resize || event->type() == QEvent::UpdateLater) {
|
||||
// Chart has a geometry when this is true
|
||||
drawFillerLines(&m_series);
|
||||
}
|
||||
|
||||
if (event->type() == QEvent::Leave && !m_dragActive) {
|
||||
// Set to normal cursor
|
||||
setCursor(Qt::ArrowCursor);
|
||||
}
|
||||
|
||||
return QChartView::event(event);
|
||||
}
|
||||
|
||||
void DragChartView::mousePressEvent(QMouseEvent *event) {
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
m_dragStartPosition = event->pos();
|
||||
}
|
||||
|
||||
QChartView::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void DragChartView::mouseMoveEvent(QMouseEvent *event) {
|
||||
if (m_limitRect.contains(chart()->mapToValue(event->pos())) && !m_mouseInLimitArea) {
|
||||
m_mouseInLimitArea = true;
|
||||
emit limitAreaEntered();
|
||||
}
|
||||
|
||||
if (!m_limitRect.contains(chart()->mapToValue(event->pos())) && m_mouseInLimitArea) {
|
||||
m_mouseInLimitArea = false;
|
||||
emit limitAreaExited();
|
||||
}
|
||||
|
||||
if (!(event->buttons() & Qt::LeftButton)) {
|
||||
return;
|
||||
}
|
||||
if ((event->pos() - m_dragStartPosition).manhattanLength() < QApplication::startDragDistance() || !m_dragCanStart) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Start drag
|
||||
emit dragStarted(m_dragStartPosition);
|
||||
|
||||
m_dragActive = true;
|
||||
|
||||
if (m_toolTipLabel->isHidden()) {
|
||||
m_toolTipLabel->show();
|
||||
}
|
||||
|
||||
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()));
|
||||
|
||||
// Don't move point out of bounds
|
||||
if (m_limitRect.contains(chart()->mapToValue(event->pos()))) {
|
||||
replaceMovedPoint(m_latestScatterPoint, chart()->mapToValue(event->pos(), &m_series));
|
||||
}
|
||||
else {
|
||||
QPointF point(chart()->mapToValue(event->pos()));
|
||||
// Set the point value to the constraint where it exceeds it
|
||||
if (chart()->mapToValue(event->pos()).x() > m_limitRect.right()) {
|
||||
point.setX(m_limitRect.right());
|
||||
}
|
||||
if (chart()->mapToValue(event->pos()).x() < m_limitRect.left()) {
|
||||
point.setX(m_limitRect.left());
|
||||
}
|
||||
if (chart()->mapToValue(event->pos()).y() > m_limitRect.top()) {
|
||||
point.setY(m_limitRect.top());
|
||||
}
|
||||
if (chart()->mapToValue(event->pos()).y() < m_limitRect.bottom()) {
|
||||
point.setY(m_limitRect.bottom());
|
||||
}
|
||||
replaceMovedPoint(m_latestScatterPoint, point);
|
||||
}
|
||||
|
||||
drawFillerLines(&m_series);
|
||||
}
|
||||
|
||||
void DragChartView::mouseReleaseEvent(QMouseEvent *event) {
|
||||
m_dragCanStart = false;
|
||||
|
||||
if (!m_scatterPressed) {
|
||||
// Add a new point to series
|
||||
m_series.append(chart()->mapToValue(event->pos()));
|
||||
drawFillerLines(&m_series);
|
||||
}
|
||||
|
||||
m_toolTipLabel->hide();
|
||||
QChartView::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
void DragChartView::wheelEvent(QWheelEvent *event) {
|
||||
qreal factor = event->angleDelta().y() > 0 ? 0.5 : 2.0;
|
||||
|
||||
//chart()->zoom(factor);
|
||||
event->accept();
|
||||
|
||||
drawFillerLines(&m_series);
|
||||
|
||||
QChartView::wheelEvent(event);
|
||||
}
|
||||
|
||||
void DragChartView::replaceMovedPoint(const QPointF old, const QPointF new_) {
|
||||
m_series.replace(old, new_);
|
||||
|
||||
m_latestScatterPoint = new_;
|
||||
}
|
||||
|
||||
QVector <QPointF> DragChartView::sortPointFByAscendingX(const QVector<QPointF> points) {
|
||||
QVector <qreal> ascendingX;
|
||||
QVector <qreal> originalY;
|
||||
|
||||
for (QPointF point : points) {
|
||||
ascendingX.append(point.x());
|
||||
originalY.append(point.y());
|
||||
}
|
||||
|
||||
std::sort(ascendingX.begin(), ascendingX.end());
|
||||
|
||||
QVector <QPointF> sorted;
|
||||
|
||||
// Find the original y values for x values
|
||||
for (qreal x : ascendingX) {
|
||||
for (QPointF point : points) {
|
||||
if (qFuzzyCompare(x, point.x())) {
|
||||
sorted.append(QPointF(x, point.y()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
void DragChartView::drawFillerLines(QScatterSeries *series) {
|
||||
// Sort points by ascending x
|
||||
QVector <QPointF> sorted = sortPointFByAscendingX(series->pointsVector());
|
||||
|
||||
if (sorted.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < sorted.length() - 1; i++) {
|
||||
m_lineFillerItems[i]->setLine(QLineF(chart()->mapToPosition(sorted[i]),
|
||||
chart()->mapToPosition(sorted[i + 1])));
|
||||
}
|
||||
|
||||
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()))));
|
||||
|
||||
chart()->update();
|
||||
}
|
||||
|
||||
bool DragChartView::eventFilter(QObject *obj, QEvent *event) {
|
||||
static bool resized = false;
|
||||
|
||||
if (obj == chart() && event->type() == QEvent::WindowActivate && !resized) {
|
||||
resized = true;
|
||||
emit m_xAxis.rangeChanged(m_xAxis.min(), m_xAxis.max());
|
||||
emit m_yAxis.rangeChanged(m_yAxis.min(), m_yAxis.max());
|
||||
}
|
||||
return QObject::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
QPoint DragChartView::toolTipOffset(QWidget *widget, const QPoint windowCursorPos) {
|
||||
QRect screenRect = widget->window()->windowHandle()->screen()->geometry();
|
||||
|
||||
if (screenRect.width() > screenRect.height()) {
|
||||
// Use x offset for screens that are wider than high
|
||||
// Set the offset to the side that has more space
|
||||
int xOffset = (windowCursorPos.x() > widget->window()->rect().width() / 2) ? -qRound(toolTipMargin() * screenRect.width()) : qRound(toolTipMargin() * screenRect.width());
|
||||
return QPoint(xOffset, 0);
|
||||
}
|
||||
int yOffset = (windowCursorPos.y() > widget->window()->rect().height() / 2) ? -qRound(toolTipMargin() * screenRect.height()) :qRound(toolTipMargin() * screenRect.height());
|
||||
|
||||
return QPoint(0, yOffset);
|
||||
}
|
64
src/tuxclocker-qt/widgets/DragChartView.hpp
Normal file
64
src/tuxclocker-qt/widgets/DragChartView.hpp
Normal file
@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtCharts/QChartView>
|
||||
#include <QtCharts/QScatterSeries>
|
||||
#include <QLabel>
|
||||
#include <QValueAxis>
|
||||
|
||||
using namespace QtCharts;
|
||||
|
||||
class DragChartView : public QChartView {
|
||||
public:
|
||||
DragChartView(QWidget *parent = nullptr);
|
||||
QValueAxis *xAxis() {return &m_xAxis;}
|
||||
QValueAxis *yAxis() {return &m_yAxis;}
|
||||
|
||||
void setVector(const QVector <QPointF> vector);
|
||||
QVector <QPointF> vector() {return m_series.pointsVector();}
|
||||
protected:
|
||||
bool event(QEvent*);
|
||||
void mousePressEvent(QMouseEvent*);
|
||||
void mouseMoveEvent(QMouseEvent*);
|
||||
void mouseReleaseEvent(QMouseEvent*);
|
||||
void wheelEvent(QWheelEvent*);
|
||||
|
||||
bool eventFilter(QObject *obj, QEvent *event); // Get notified when chart has geometry
|
||||
signals:
|
||||
void dragStarted(const QPointF point);
|
||||
void dragEnded(const QPointF point);
|
||||
|
||||
void limitAreaEntered();
|
||||
void limitAreaExited();
|
||||
private:
|
||||
Q_OBJECT
|
||||
|
||||
static QPoint toolTipOffset(QWidget *widget, const QPoint windowCursorPos); // Get tooltip offset based on current screen
|
||||
static double toolTipMargin() {return 0.02;}
|
||||
|
||||
QColor fillerLineColor() {
|
||||
return QPalette().color(QPalette::Highlight);
|
||||
}
|
||||
QPen fillerLinePen() {
|
||||
return QPen(fillerLineColor(), 3);
|
||||
}
|
||||
|
||||
qreal m_chartMargin; // Margin in pixels between a scatter point and axis edge. Add this value on either edge of the axes in order for chatter points to be unable to go partially out of the viewport.
|
||||
QRectF m_limitRect; // Scatter points can't be moved outside this rect
|
||||
bool m_mouseInLimitArea;
|
||||
|
||||
bool m_scatterPressed; // For detecting if mouse release should delete or add a new point
|
||||
|
||||
QVector <QPointF> sortPointFByAscendingX(const QVector <QPointF> points);
|
||||
void drawFillerLines(QScatterSeries *series);
|
||||
QLabel *m_toolTipLabel;
|
||||
QScatterSeries m_series;
|
||||
QPoint m_dragStartPosition;
|
||||
bool m_dragCanStart; // Was a point clicked and not released before drag should start
|
||||
bool m_dragActive;
|
||||
QPointF m_latestScatterPoint;
|
||||
void replaceMovedPoint(const QPointF old, const QPointF new_);
|
||||
QVector <QGraphicsLineItem*> m_lineFillerItems; // Filler lines between points whose amount is n - 1 for nonzero n
|
||||
QGraphicsLineItem *m_leftLineFillerItem;
|
||||
QGraphicsLineItem *m_rightLineFillerItem;
|
||||
QValueAxis m_xAxis, m_yAxis;
|
||||
};
|
2
src/tuxclocker-qt/widgets/FlagEditor.cpp
Normal file
2
src/tuxclocker-qt/widgets/FlagEditor.cpp
Normal file
@ -0,0 +1,2 @@
|
||||
#include "FlagEditor.hpp"
|
||||
|
127
src/tuxclocker-qt/widgets/FlagEditor.hpp
Normal file
127
src/tuxclocker-qt/widgets/FlagEditor.hpp
Normal file
@ -0,0 +1,127 @@
|
||||
#pragma once
|
||||
|
||||
// Combobox editor for QFlags types
|
||||
|
||||
// Qt can't use its own signals in templated classes
|
||||
#include <boost/signals2.hpp>
|
||||
#include <fplus/fplus.hpp>
|
||||
#include <QAbstractItemView>
|
||||
#include <QComboBox>
|
||||
#include <QEvent>
|
||||
#include <QFlags>
|
||||
#include <QHash>
|
||||
#include <QIcon>
|
||||
#include <QLineEdit>
|
||||
#include <QStandardItemModel>
|
||||
#include <QStylePainter>
|
||||
|
||||
template <typename T>
|
||||
class FlagEditor : public QComboBox {
|
||||
public:
|
||||
// Need to do the templated stuff in the header because C++ is retarded
|
||||
FlagEditor(QVector<std::tuple<QString, QIcon, T>> flagData,
|
||||
QWidget *parent = nullptr) : QComboBox(parent), m_skipNextHide(false) {
|
||||
view()->viewport()->installEventFilter(this);
|
||||
auto model = new QStandardItemModel(this);
|
||||
|
||||
for (const auto &flagItem : flagData) {
|
||||
auto item = new QStandardItem;
|
||||
|
||||
auto [text, icon, flag] = flagItem;
|
||||
//auto icon = style()->standardIcon(QStyle::SP_ComputerIcon);
|
||||
//item->setData(icon, Qt::DecorationRole);
|
||||
|
||||
item->setCheckable(true);
|
||||
item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
|
||||
item->setData(Qt::Checked, Qt::CheckStateRole);
|
||||
item->setData(icon ,Qt::DecorationRole);
|
||||
item->setText(text);
|
||||
// Cast to uint so we don't need to register the type
|
||||
auto uFlag = static_cast<uint>(flag);
|
||||
item->setData(uFlag, FlagRole);
|
||||
m_flagHash.insert(uFlag, item);
|
||||
model->appendRow(item);
|
||||
}
|
||||
setModel(model);
|
||||
|
||||
connect(model, &QStandardItemModel::itemChanged,
|
||||
[this](QStandardItem *item) {
|
||||
auto flag = static_cast<QFlags<T>>(item->data(FlagRole).toUInt());
|
||||
auto checkState = item->data(Qt::CheckStateRole)
|
||||
.value<Qt::CheckState>();
|
||||
// Shouldn't ever be partially checked
|
||||
if (checkState == Qt::Unchecked)
|
||||
// Remove flag
|
||||
m_flags &= ~flag;
|
||||
else
|
||||
m_flags |= flag;
|
||||
flagsChanged(m_flags);
|
||||
});
|
||||
|
||||
// Cause a repaint on changed flags
|
||||
// Boost won't let me discard the argument :(
|
||||
flagsChanged.connect([this](auto) {update();});
|
||||
}
|
||||
void setFlags(QFlags<T> flags) {
|
||||
m_flags = flags;
|
||||
flagsChanged(flags);
|
||||
auto handledFlags = m_flagHash.keys();
|
||||
auto uFlags = static_cast<uint>(flags);
|
||||
|
||||
for (const auto &flag : handledFlags) {
|
||||
if (uFlags & flag)
|
||||
m_flagHash.value(flag)->setData(Qt::Checked, Qt::CheckStateRole);
|
||||
}
|
||||
}
|
||||
//QFlags<T> flags() {}
|
||||
|
||||
bool eventFilter(QObject *obj, QEvent *ev) {
|
||||
if (ev->type() == QEvent::MouseButtonPress && obj == view()->viewport())
|
||||
m_skipNextHide = true;
|
||||
return QComboBox::eventFilter(obj, ev);
|
||||
}
|
||||
virtual void hidePopup() {
|
||||
if (m_skipNextHide)
|
||||
m_skipNextHide = false;
|
||||
else
|
||||
QComboBox::hidePopup();
|
||||
}
|
||||
boost::signals2::signal<void(QFlags<T>)> flagsChanged;
|
||||
protected:
|
||||
void paintEvent(QPaintEvent*) {
|
||||
// Override paintEvent to draw custom text in the box
|
||||
QStylePainter painter(this);
|
||||
QStyleOptionComboBox opt;
|
||||
initStyleOption(&opt);
|
||||
|
||||
// Show which flags are selected
|
||||
auto items = m_flagHash.values();
|
||||
// Not sure why using 'auto' breaks something mysteriously here
|
||||
QList<QStandardItem*> checked = fplus::keep_if([](QStandardItem *item) {
|
||||
return item->data(Qt::CheckStateRole)
|
||||
.value<Qt::CheckState>() == Qt::Checked;
|
||||
}, items);
|
||||
|
||||
// Where the first icon is drawn
|
||||
auto startY = opt.rect.top() + iconSize().height() / 2;
|
||||
auto startX = opt.rect.left() + iconSize().width() / 2;
|
||||
auto deltaX = iconSize().width() * 3;
|
||||
|
||||
painter.drawComplexControl(QStyle::CC_ComboBox, opt);
|
||||
|
||||
// Draw icons of selected items
|
||||
QRect drawRect(QPoint(startX, startY), iconSize());
|
||||
for (const auto &item : checked) {
|
||||
auto pixmap = item->data(Qt::DecorationRole)
|
||||
.value<QIcon>().pixmap(iconSize());
|
||||
painter.drawItemPixmap(drawRect,
|
||||
Qt::AlignCenter, pixmap);
|
||||
drawRect.moveRight(deltaX);
|
||||
}
|
||||
}
|
||||
private:
|
||||
bool m_skipNextHide;
|
||||
const int FlagRole = Qt::UserRole + 1;
|
||||
QFlags<T> m_flags;
|
||||
QHash<uint, QStandardItem*> m_flagHash;
|
||||
};
|
Loading…
Reference in New Issue
Block a user