Reputation: 504
I have an application where i need to draw lines on a QTableView
showing the range of frames. I have a QGraphicsView
and QGraphicsScene
which holds a QTableView
As shown below:
Criteria
The span of the lines should stay relative to position of the columns i.e. first line should stay between 3 and 7 all the time even when scrolled out of the view. Should again be visible when scrolled back
I would like to retrieve the starting index and ending index of the line as in QModelIndex.
Line must always maintain the center of the width of the row
I have hereby created a MVCE
Mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
MainWindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "timelineview.h"
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
TimelineView* graphicsView = new TimelineView(this);
setCentralWidget(graphicsView);
}
MainWindow::~MainWindow()
{
delete ui;
}
TimelineView.cpp
// qt
#include <QHeaderView>
// local
#include "timelineview.h"
TimelineView::TimelineView(QWidget* parent) :
QGraphicsView(parent),
m_scene{new TimelineScene}
{
setScene(m_scene);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_table = new QTableView;
// settings for table view
m_table->setSelectionMode(QAbstractItemView::NoSelection);
m_table->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_table->verticalHeader()->hide();
m_table->horizontalHeader()->setHighlightSections(false);
m_tableModel = new QStandardItemModel(10, 100, m_table);
m_table->setModel(m_tableModel);
m_scene->addWidget(m_table);
setMouseTracking(true);
}
void TimelineView::resizeEvent(QResizeEvent* event)
{
m_scene->setSceneRect(0, 0, width(), height());
m_table->setGeometry(m_scene->sceneRect().toRect());
fitInView(m_scene->sceneRect(), Qt::KeepAspectRatioByExpanding);
QGraphicsView::resizeEvent(event);
}
TimelineView.h
#ifndef TIMELINEVIEW_H
#define TIMELINEVIEW_H
// qt
#include <QGraphicsView>
#include <QTableView>
#include <QStandardItemModel>
// local
#include "timelinescene.h"
class TimelineView : public QGraphicsView
{
public:
explicit TimelineView(QWidget* parent = nullptr);
protected:
virtual void resizeEvent(QResizeEvent* event) override;
private:
QTableView* m_table;
QStandardItemModel* m_tableModel;
TimelineScene* m_scene;
};
#endif // TIMELINEVIEW_H
TimelineScene.cpp
#include "timelinescene.h"
TimelineScene::TimelineScene(QObject* parent) :
QGraphicsScene(parent)
, m_lineItem{nullptr}
, m_isPressed{false}
{
}
void TimelineScene::mousePressEvent(QGraphicsSceneMouseEvent* event)
{
if(event->button() == Qt::LeftButton)
{
m_origPoint = event->scenePos();
m_isPressed = true;
}
QGraphicsScene::mousePressEvent(event);
}
void TimelineScene::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
{
if(m_isPressed)
{
if(m_lineItem == nullptr)
{
// create a pen for the line to be drawn
QPen pen;
pen.setStyle(Qt::SolidLine);
pen.setBrush(QColor(255, 102, 0));
pen.setWidth(8);
m_lineItem = new QGraphicsLineItem(m_origPoint.x(), m_origPoint.y(), event->scenePos().x(), event->scenePos().y());
// set the pen
m_lineItem->setPen(pen);
// add the item to the scene
addItem(m_lineItem);
}
m_lineItem->setLine(m_origPoint.x(), m_origPoint.y(), event->scenePos().x(), m_origPoint.y());
update();
}
QGraphicsScene::mouseMoveEvent(event);
}
void TimelineScene::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
{
m_lineItem = nullptr;
m_isPressed = false;
QGraphicsScene::mouseReleaseEvent(event);
}
TimelineScene.h
#ifndef TIMELINESCENE_H
#define TIMELINESCENE_H
// qt
#include <QGraphicsScene>
#include <QObject>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsLineItem>
#include <QPointF>
class TimelineScene : public QGraphicsScene
{
public:
explicit TimelineScene(QObject* parent = nullptr);
protected:
virtual void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override;
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override;
private:
QGraphicsLineItem* m_lineItem;
QPointF m_origPoint;
bool m_isPressed;
};
#endif // TIMELINESCENE_H
Problems:
I am not able to get the index of the cell i.e. stating and ending point of the line in terms of QModelIndex
I could even draw the lines on the QHeaderView
and also on the QScrollbar
. I know the reason for this is because the whole canvas is a scene and i can draw everywhere. But i want to restrict drawing only to the cells of the table
I know i could handle all of these issues if i could find a way to retrieve info about the underlying QTableView
but not sure how.
Upvotes: 1
Views: 960
Reputation: 222
As already discussed, using a delegate is the right way to do it.
In case it's impossible for some reason, I strongly recommend you not mix the model view with the graphics scene in such way.
In case it's absolutely impossible to use delegates, I'd think about drawing the lines on QTableView
— it has API for accessing model indices through pixel coordinates, it does know its scrolling position and it does have handlers for all the necessary user input. So all that you have to do is to store the collection of QLine
in a class derived from the QTableView
and draw the lines which fit the current viewport via QPainter
.
I'm afraid that even if the "overlay" with QGraphicsScene
looks like the easiest solution now, in the nuts it's tons of issues related to the user input handling/routing. And it's definitely not the Qt-way.
UPD: a delegate-based sample:
The idea is quite simple:
start
and end
x-coordinate to the
range [0.0;1.0];Then you can draw your custom delegate as follows:
void LineDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyledItemDelegate::paint(painter, option, index);
const QVariant &lineData = index.data(Line::DataRole);
if (lineData.isValid() && lineData.canConvert<Line>()) {
const Line &line = lineData.value<Line>();
const QLineF &lineF = line.toQLine(option.rect);
painter->save();
painter->setPen(m_linePen);
painter->drawLine(lineF);
painter->restore();
}
}
The mouse handling is a bit more tricky:
bool LineDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option,
const QModelIndex &index)
{
switch (event->type()) {
case QEvent::MouseButtonPress:
handleMousePress(static_cast<QMouseEvent *>(event), model, option, index);
break;
case QEvent::MouseMove:
handleMouseMove(static_cast<QMouseEvent *>(event), model, option, index);
break;
case QEvent::MouseButtonRelease:
handleMouseRelease(static_cast<QMouseEvent *>(event), model, option, index);
break;
default:
break;
}
return QStyledItemDelegate::editorEvent(event, model, option, index);
}
void LineDelegate::handleMousePress(QMouseEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option,
const QModelIndex &index)
{
Q_UNUSED(event);
Q_UNUSED(model);
if (index.isValid()) {
m_startIndex = index;
m_startPoint = { event->x() - option.rect.x(), option.rect.center().y() };
}
}
void LineDelegate::handleMouseMove(QMouseEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option,
const QModelIndex &index)
{
const QPoint endPoint = { event->x() - option.rect.x(), option.rect.center().y() };
if (m_startIndex != index) {
m_startIndex = index;
m_startPoint = endPoint;
}
const Line &line = Line::fromQLine({ m_startPoint, endPoint }, option.rect);
model->setData(index, QVariant::fromValue(line), Line::DataRole);
}
void LineDelegate::handleMouseRelease(QMouseEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option,
const QModelIndex &index)
{
Q_UNUSED(event);
Q_UNUSED(model);
Q_UNUSED(option);
Q_UNUSED(index);
m_startIndex = QModelIndex();
m_startPoint = { -1, -1 };
}
Here is how it looks like:
The complete code of the sample above is available on github
Please note — that's just a brief prototype. There is no support for things like right-to-left mouse movement, nor limitation to a current row only, etc. But that should be enough for you to start, I hope.
Upvotes: 2
Reputation: 52347
Apart from our discussion about delegates in the comments, to answer your two questions:
QGraphicsItem::mapFromScene
.QTableView::indexAt()
to obtain the model index.You can override QGraphicsItem::itemChange()
to manipulate any movement of an item even while dragged by the user. It looks like this:
QVariant QGraphicsItem::itemChange(GraphicsItemChange change,
const QVariant &value)
{
if (change == ItemPositionChange) {
// restr.Position() returns a QPointF limited by the allowed are
return restrictPosition(value.toPointF());
}
…
}
Upvotes: 0