qt: add filtering based on node type

This commit is contained in:
Jussi Kuokkanen 2020-06-15 13:01:50 +03:00
parent b09f5e3848
commit ad81d8d13c
17 changed files with 768 additions and 18 deletions

View File

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

View File

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

View File

@ -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) {

View File

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

View 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;
}

View 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;
};

View File

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

View File

@ -0,0 +1,2 @@
flaticon.com:
Kiranshastry

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>ruler.svg</file>
</qresource>
</RCC>

View 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

View File

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

View File

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

View File

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

View 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);
}

View 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;
};

View File

@ -0,0 +1,2 @@
#include "FlagEditor.hpp"

View 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;
};