I want to draw a line in grid and implement snap functionality to it. I have drawn grid and line but snap functionality is not implemented. How can I implement that functionality in it. I have drawn grid as follows:
void CadGraphicsScene::drawBackground(QPainter *painter, const QRectF &rect)
{
if (isGridVisible)
{
const int gridSize = 50;
const int realLeft = static_cast<int>(std::floor(rect.left()));
const int realRight = static_cast<int>(std::ceil(rect.right()));
const int realTop = static_cast<int>(std::floor(rect.top()));
const int realBottom = static_cast<int>(std::ceil(rect.bottom()));
// Draw grid.
const int firstLeftGridLine = realLeft - (realLeft % gridSize);
const int firstTopGridLine = realTop - (realTop % gridSize);
QVarLengthArray<QLine, 100> lines;
for (qreal x = firstLeftGridLine; x <= realRight; x += gridSize)
lines.append(QLine(x, realTop, x, realBottom));
for (qreal y = firstTopGridLine; y <= realBottom; y += gridSize)
lines.append(QLine(realLeft, y, realRight, y));
painter->setPen(QPen(QColor(220, 220, 220), 0.0));
painter->drawLines(lines.data(), lines.size());
// Draw axes.
painter->setPen(QPen(Qt::lightGray, 0.0));
painter->drawLine(0, realTop, 0, realBottom);
painter->drawLine(realLeft, 0, realRight, 0);
}
}
My Line class is as follows:
#include "line.h"
Line::Line(int i, QPointF p1, QPointF p2)
{
// assigns id
id = i;
// set values of start point and end point of line
startP = p1;
endP = p2;
}
int Line::type() const
{
// Enable the use of qgraphicsitem_cast with line item.
return Type;
}
QRectF Line::boundingRect() const
{
qreal extra = 1.0;
// bounding rectangle for line
return QRectF(line().p1(), QSizeF(line().p2().x() - line().p1().x(),
line().p2().y() - line().p1().y()))
.normalized()
.adjusted(-extra, -extra, extra, extra);
}
void Line::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
// draws/paints the path of line
QPen paintpen;
painter->setRenderHint(QPainter::Antialiasing);
paintpen.setWidth(1);
if (isSelected())
{
// sets brush for end points
painter->setBrush(Qt::SolidPattern);
paintpen.setColor(Qt::red);
painter->setPen(paintpen);
painter->drawEllipse(startP, 2, 2);
painter->drawEllipse(endP, 2, 2);
// sets pen for line path
paintpen.setStyle(Qt::DashLine);
paintpen.setColor(Qt::black);
painter->setPen(paintpen);
painter->drawLine(startP, endP);
}
else
{
painter->setBrush(Qt::SolidPattern);
paintpen.setColor(Qt::black);
painter->setPen(paintpen);
painter->drawEllipse(startP, 2, 2);
painter->drawEllipse(endP, 2, 2);
painter->drawLine(startP, endP);
}
}
snap.cpp
#include "snap.h"
#include <QApplication>
Snap::Snap(const QRect& rect, QGraphicsItem* parent,
QGraphicsScene* scene):
QGraphicsRectItem(QRectF())
{
setFlags(QGraphicsItem::ItemIsSelectable |
QGraphicsItem::ItemIsMovable |
QGraphicsItem::ItemSendsGeometryChanges);
}
void Snap::mousePressEvent(QGraphicsSceneMouseEvent *event){
offset = pos() - computeTopLeftGridPoint(pos());
QGraphicsRectItem::mousePressEvent(event);
}
QVariant Snap::itemChange(GraphicsItemChange change,
const QVariant &value)
{
if (change == ItemPositionChange && scene()) {
QPointF newPos = value.toPointF();
if(QApplication::mouseButtons() == Qt::LeftButton &&
qobject_cast<CadGraphicsScene*> (scene())){
QPointF closestPoint = computeTopLeftGridPoint(newPos);
return closestPoint+=offset;
}
else
return newPos;
}
else
return QGraphicsItem::itemChange(change, value);
}
QPointF Snap::computeTopLeftGridPoint(const QPointF& pointP){
CadGraphicsScene* customScene = qobject_cast<CadGraphicsScene*> (scene());
int gridSize = customScene->getGridSize();
qreal xV = floor(pointP.x()/gridSize)*gridSize;
qreal yV = floor(pointP.y()/gridSize)*gridSize;
return QPointF(xV, yV);
}
Can anyone please help me to solve this?
In the MouseMoveEvent of the Item you want to snap:
Check if the distance between the Item and the Next GridLine is below a certain distance (the distance from which the item would snap).
Then change the Position such that your Item moves to the next GridLine.
In Pseudo:
if( distance(Item.Position, Grid.NextLine) < SnapDistance)
line.Position = Grid.MextLine.Position
You have to seperate X and Y though to make this work properly.
Related
I have QVector<QPoint> m_vertices and QVector<QLine> in my drawingWidget.h
I currently trying to do a simple drawing tool, where I can add vertices and lines. I managed to draw multiple vertices on my "MainWindow", but I can't really understand, how can I implement a line between created vertices.
Basically I have two points created and by pressing the left mouse button on the first vertex and left mouse button on the second vertex... it's should create a line between them.
How can I do that?
void DrawingWidget::paintEvent(QPaintEvent *event) {
QPainter painter(this);
painter.fillRect(event->rect(), Qt::blue);
painter.setBrush(Qt::black);
for(int i = 0; i < m_vertices.size() ; i++) {
painter.drawEllipse(m_vertices[i], 20, 20);
}
}
void DrawingWidget::mousePressEvent(QMouseEvent *event) {
if(m_state == ADD_VERTEX_SELECTED) {
if(event->button() == Qt::LeftButton) {
//m_x = event->x();
//m_y = event->y();
//update();
QPoint point = event->pos();
m_vertices.append(point);
update();
}
}
if(m_state == ADD_LINE_SELECTED) {
if(event->button() == Qt::LeftButton) {
QPoint Startpoint = event->x();
QPoint Endpoint = event->y();
}
}
}
You are almost there. You need to use QPainter::drawLine for this and pass the two consecutive mouse QPoints from m_vertices.
For example
void DrawingWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.fillRect(event->rect(), Qt::blue);
painter.setPen(QPen(Qt::black, 3)); // width of the line
if(m_vertices.size() == 1) // for the first mouse click
painter.drawEllipse(m_vertices[0], 4, 4);
for(int i = 0; i < m_vertices.size()-1 ; i++)
{
const QPoint& point1 = m_vertices[i];
const QPoint& point2 = m_vertices[i+1];
// draw the mouse point
painter.drawEllipse(point1, 4, 4);
painter.drawEllipse(point2, 4, 4);
// draw the line
painter.drawLine(point1, point2);
}
}
I am trying to extract collision coordinate between two rectangles moving inside a QGraphicsScene and send these coordinates to a QLineEdit. Basically the position of the rectangle when they collide.
I was reading this source and this source.
They advice to use QGraphicsItem::collidesWithPath but no success.
This is the snipped of code that does the collision with my own implementation:
MyItem::MyItem()
{
angle = (qrand() % 360);
setRotation(angle);
speed = 5;
int startX = 0;
int startY = 0;
if((qrand() % 1)){
startX = (qrand() % 200);
startY = (qrand() % 200);
}else{
startX = (qrand() % -100);
startY = (qrand() % -100);
}
setPos(mapToParent(startX, startY));
}
void MyItem::paint(QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
QRectF rec = boundingRect();
QBrush brush(Qt::gray);
if(scene()->collidingItems(this).isEmpty()){
brush.setColor(Qt::green); // no collision
}else{
brush.setColor(Qt::red); // yes collision
doCollision();
}
painter->fillRect(rec, brush);
painter->drawRect(rec);
}
void MyItem::advance(int phase)
{
if(!phase) return;
QPointF location = this->pos();
setPos(mapToParent(0, -(speed)));
}
void MyItem::doCollision()
{
if((qrand() %1)) {
setRotation(rotation() + (180+(qrand() % 10)));
}else{
setRotation(rotation() + (180+(qrand() % -10)));
}
// see if the new position is in bounds
QPointF newPoint = mapToParent(-(boundingRect().width()), -(boundingRect().width() + 2));
if(!scene()->sceneRect().contains((newPoint))){
newPoint = mapToParent(0, 0); // move it back in bounds
}else{
setPos(newPoint); // set new position
}
}
How to extract collision coordinates (x,y) between objects in QGraphicsScene and send the coordinates (x,y) to QLineEdit?
Thanks for shedding light on this issue.
I have a qt app which has a big QGraphicsView where I draw a grid.
This grid reacts on mouse clicks. Basically, if you click on some box -> the box is made red. And if you double click on it -> it goes back to its original state (its white).
This is the implementation of my QGraphicsView:
#include "mapview.h"
// Constructors
mapview::mapview()
{
setUpGui();
}
mapview::mapview(QWidget *parent) : QGraphicsView(parent)
{
setUpGui();
}
// GUI setup
void mapview::setUpGui()
{
scene = new QGraphicsScene(this);
this->setScene(scene);
this->setRenderHint(QPainter::Antialiasing);
// this->setRenderHint(QPainter::HighQualityAntialiasing);
}
// Events
void mapview::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
lastPoint = event->pos();
scribbling = true;
}
}
void mapview::mouseMoveEvent(QMouseEvent *event)
{
if ((event->buttons() & Qt::LeftButton) && scribbling)
{
drawWall(event->pos());
}
}
void mapview::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton && scribbling)
{
drawWall(event->pos());
scribbling = false;
}
}
void mapview::mouseDoubleClickEvent(QMouseEvent *event)
{
removeWall(event->pos());
}
// Drawing methods
void mapview::drawGrid(const int box_count)
{
scene->clear();
auto x = 0.0;
auto y = 0.0;
this->margin = 20.0;
_width = this->width() - 2 * margin;
_height = this->height() - 2 * margin;
if (fabs(_width - _height) >= std::numeric_limits<double>::epsilon())
{
// qDebug() << "width (" << width << ") != height (" << height << ")";
return;
}
this->box_count = box_count;
this->box_size = _width / box_count;
// Horizontal
for (auto i = 0; i <= box_count; i++)
{
QGraphicsLineItem *line = new QGraphicsLineItem(x, y, x + _width, y);
QPen pen;
pen.setColor(Qt::black);
line->setPen(pen);
// scene->addLine(x, y, x + _width, y);
scene->addItem(line);
y += box_size;
}
y = 0.0;
// Vertical
for (auto i = 0; i <= box_count; i++)
{
scene->addLine(x, y, x, y + _height);
x += box_size;
}
}
void mapview::drawWall(const QPointF &endPoint)
{
auto x = endPoint.x() - margin;
auto y = endPoint.y() - margin;
x = static_cast<int>(x / box_size) * box_size;
y = static_cast<int>(y / box_size) * box_size;
QGraphicsRectItem* rect = new QGraphicsRectItem(x, y, this->box_size, this->box_size);
rect->setBrush(QBrush(Qt::red));
rect->setPen(QPen());
scene->addItem(rect);
}
void mapview::removeWall(const QPointF &point)
{
auto x = point.x() - margin;
auto y = point.y() - margin;
x = static_cast<int>(x / box_size) * box_size;
y = static_cast<int>(y / box_size) * box_size;
QGraphicsRectItem* rect = new QGraphicsRectItem(x, y, this->box_size, this->box_size);
rect->setBrush(QBrush(Qt::white));
rect->setPen(QPen());
scene->addItem(rect);
}
As you can see at the top, I set up antialiasing. The problem is, that when I set it up, it somehow changes the view of my drawing:
This is the view of an untouched grid:
Now for the box 1x1 (indexed from 0) I've clicked it (made it red) and then I double clicked it (so it got back to its original state). The problem is, as you can see, the border of this box is kinda thicker:
Funny thing is, that it's only thicker on this view. If I zoom it in, there's no difference between its border and the borders of other boxes. The other thing is, if I set Qt::HighQualityAntialiasing -> the border is the same, but then the zooming gets laggy, I guess it has to do more heavy computations or something like this.
So my question here would be: Is there a way, to make the antialiasing not change the thickness of the border? (I mean, I know it doesn't really change it, but you can definitely see a difference here)
I am building a map widget (something like the google map) using Qt, basically I used a QGraphicsScene to display the map tile.
Now I want to add a scale ruler to the widget just like the one in google map.
Any suggestions about how could I realize this?
Take a look at this example:
Structure your code base as following:
Write a class inheriting descendants class of QAbstractScrollArea (As example QGraphicsView, QMdiArea, QPlainTextEdit, QScrollArea, QTextEdit, QColumnView, QHeaderView, QListView, QTableView, QTreeView etc.)
In the constructor of your class call setViewportMargins and set the margins of left/top/right/bottom areas length.
Create a QGridLayout and adds your custom Ruler/Scale in the layout.
Set this layout calling setLayout
Example:
setViewportMargins(RULER_BREADTH,RULER_BREADTH,0,0);
QGridLayout* gridLayout = new QGridLayout();
gridLayout->setSpacing(0);
gridLayout->setMargin(0);
mHorzRuler = new QDRuler(QDRuler::Horizontal);
mVertRuler = new QDRuler(QDRuler::Vertical);
QWidget* fake = new QWidget();
fake->setBackgroundRole(QPalette::Window);
fake->setFixedSize(RULER_BREADTH,RULER_BREADTH);
gridLayout->addWidget(fake,0,0);
gridLayout->addWidget(mHorzRuler,0,1);
gridLayout->addWidget(mVertRuler,1,0);
gridLayout->addWidget(this->viewport(),1,1);
this->setLayout(gridLayout);
QDRuler: The ruler class
#define RULER_BREADTH 20
class QDRuler : public QWidget
{
Q_OBJECT
Q_ENUMS(RulerType)
Q_PROPERTY(qreal origin READ origin WRITE setOrigin)
Q_PROPERTY(qreal rulerUnit READ rulerUnit WRITE setRulerUnit)
Q_PROPERTY(qreal rulerZoom READ rulerZoom WRITE setRulerZoom)
public:
enum RulerType { Horizontal, Vertical };
QDRuler(QDRuler::RulerType rulerType, QWidget* parent)
: QWidget(parent), mRulerType(rulerType), mOrigin(0.), mRulerUnit(1.),
mRulerZoom(1.), mMouseTracking(false), mDrawText(false)
{
setMouseTracking(true);
QFont txtFont("Goudy Old Style", 5,20);
txtFont.setStyleHint(QFont::TypeWriter,QFont::PreferOutline);
setFont(txtFont);
}
QSize minimumSizeHint() const
{
return QSize(RULER_BREADTH,RULER_BREADTH);
}
QDRuler::RulerType rulerType() const
{
return mRulerType;
}
qreal origin() const
{
return mOrigin;
}
qreal rulerUnit() const
{
return mRulerUnit;
}
qreal rulerZoom() const
{
return mRulerZoom;
}
public slots:
void setOrigin(const qreal origin)
{
if (mOrigin != origin)
{
mOrigin = origin;
update();
}
}
void setRulerUnit(const qreal rulerUnit)
{
if (mRulerUnit != rulerUnit)
{
mRulerUnit = rulerUnit;
update();
}
}
void setRulerZoom(const qreal rulerZoom)
{
if (mRulerZoom != rulerZoom)
{
mRulerZoom = rulerZoom;
update();
}
}
void setCursorPos(const QPoint cursorPos)
{
mCursorPos = this->mapFromGlobal(cursorPos);
mCursorPos += QPoint(RULER_BREADTH,RULER_BREADTH);
update();
}
void setMouseTrack(const bool track)
{
if (mMouseTracking != track)
{
mMouseTracking = track;
update();
}
}
protected:
void mouseMoveEvent(QMouseEvent* event)
{
mCursorPos = event->pos();
update();
QWidget::mouseMoveEvent(event);
}
void paintEvent(QPaintEvent* event)
{
QPainter painter(this);
painter.setRenderHints(QPainter::TextAntialiasing | QPainter::HighQualityAntialiasing);
QPen pen(Qt::black,0); // zero width pen is cosmetic pen
//pen.setCosmetic(true);
painter.setPen(pen);
// We want to work with floating point, so we are considering
// the rect as QRectF
QRectF rulerRect = this->rect();
// at first fill the rect
//painter.fillRect(rulerRect,QColor(220,200,180));
painter.fillRect(rulerRect,QColor(236,233,216));
// drawing a scale of 25
drawAScaleMeter(&painter,rulerRect,25,(Horizontal == mRulerType ? rulerRect.height()
: rulerRect.width())/2);
// drawing a scale of 50
drawAScaleMeter(&painter,rulerRect,50,(Horizontal == mRulerType ? rulerRect.height()
: rulerRect.width())/4);
// drawing a scale of 100
mDrawText = true;
drawAScaleMeter(&painter,rulerRect,100,0);
mDrawText = false;
// drawing the current mouse position indicator
painter.setOpacity(0.4);
drawMousePosTick(&painter);
painter.setOpacity(1.0);
// drawing no man's land between the ruler & view
QPointF starPt = Horizontal == mRulerType ? rulerRect.bottomLeft()
: rulerRect.topRight();
QPointF endPt = Horizontal == mRulerType ? rulerRect.bottomRight()
: rulerRect.bottomRight();
painter.setPen(QPen(Qt::black,2));
painter.drawLine(starPt,endPt);
}
private:
void drawAScaleMeter(QPainter* painter, QRectF rulerRect, qreal scaleMeter, qreal startPositoin)
{
// Flagging whether we are horizontal or vertical only to reduce
// to cheching many times
bool isHorzRuler = Horizontal == mRulerType;
scaleMeter = scaleMeter * mRulerUnit * mRulerZoom;
// Ruler rectangle starting mark
qreal rulerStartMark = isHorzRuler ? rulerRect.left() : rulerRect.top();
// Ruler rectangle ending mark
qreal rulerEndMark = isHorzRuler ? rulerRect.right() : rulerRect.bottom();
// Condition A # If origin point is between the start & end mard,
//we have to draw both from origin to left mark & origin to right mark.
// Condition B # If origin point is left of the start mark, we have to draw
// from origin to end mark.
// Condition C # If origin point is right of the end mark, we have to draw
// from origin to start mark.
if (mOrigin >= rulerStartMark && mOrigin <= rulerEndMark)
{
drawFromOriginTo(painter, rulerRect, mOrigin, rulerEndMark, 0, scaleMeter, startPositoin);
drawFromOriginTo(painter, rulerRect, mOrigin, rulerStartMark, 0, -scaleMeter, startPositoin);
}
else if (mOrigin < rulerStartMark)
{
int tickNo = int((rulerStartMark - mOrigin) / scaleMeter);
drawFromOriginTo(painter, rulerRect, mOrigin + scaleMeter * tickNo,
rulerEndMark, tickNo, scaleMeter, startPositoin);
}
else if (mOrigin > rulerEndMark)
{
int tickNo = int((mOrigin - rulerEndMark) / scaleMeter);
drawFromOriginTo(painter, rulerRect, mOrigin - scaleMeter * tickNo,
rulerStartMark, tickNo, -scaleMeter, startPositoin);
}
}
void drawFromOriginTo(QPainter* painter, QRectF rulerRect, qreal startMark, qreal endMark, int startTickNo, qreal step, qreal startPosition)
{
bool isHorzRuler = Horizontal == mRulerType;
int iterate = 0;
for (qreal current = startMark;
(step < 0 ? current >= endMark : current <= endMark); current += step)
{
qreal x1 = isHorzRuler ? current : rulerRect.left() + startPosition;
qreal y1 = isHorzRuler ? rulerRect.top() + startPosition : current;
qreal x2 = isHorzRuler ? current : rulerRect.right();
qreal y2 = isHorzRuler ? rulerRect.bottom() : current;
painter->drawLine(QLineF(x1,y1,x2,y2));
if (mDrawText)
{
QPainterPath txtPath;
txtPath.addText(x1 + 1,y1 + (isHorzRuler ? 7 : -2),this->font(),QString::number(qAbs(int(step) * startTickNo++)));
painter->drawPath(txtPath);
iterate++;
}
}
}
void drawMousePosTick(QPainter* painter)
{
if (mMouseTracking)
{
QPoint starPt = mCursorPos;
QPoint endPt;
if (Horizontal == mRulerType)
{
starPt.setY(this->rect().top());
endPt.setX(starPt.x());
endPt.setY(this->rect().bottom());
}
else
{
starPt.setX(this->rect().left());
endPt.setX(this->rect().right());
endPt.setY(starPt.y());
}
painter->drawLine(starPt,endPt);
}
}
private:
RulerType mRulerType;
qreal mOrigin;
qreal mRulerUnit;
qreal mRulerZoom;
QPoint mCursorPos;
bool mMouseTracking;
bool mDrawText;
};
I have my own derived class of type QGraphicsLineItem where I override paint() in order to render it as an arrow.
My test line is 160, 130, 260, 230
And my paint() implementation:
void MyQGraphicsLineItem::paint( QPainter* aPainter, const QStyleOptionGraphicsItem* aOption, QWidget* aWidget /*= nullptr*/ )
{
Q_UNUSED( aWidget );
aPainter->setClipRect( aOption->exposedRect );
// Get the line and its angle
QLineF cLine = line();
const qreal cLineAngle = cLine.angle();
// Create two copies of the line
QLineF head1 = cLine;
QLineF head2 = cLine;
// Shorten each line and set its angle relative to the main lines angle
// this gives up the "arrow head" lines
head1.setLength( 12 );
head1.setAngle( cLineAngle+-32 );
head2.setLength( 12 );
head2.setAngle( cLineAngle+32 );
// Draw shaft
aPainter->setPen( QPen( Qt::black, 1, Qt::SolidLine ) );
aPainter->drawLine( cLine );
// Draw arrow head
aPainter->setPen( QPen( Qt::red, 1, Qt::SolidLine ) );
aPainter->drawLine( head1 );
aPainter->setPen( QPen( Qt::magenta, 1, Qt::SolidLine ) );
aPainter->drawLine( head2 );
}
This draws an arrow which looks like this:
What I would like to do is be able to calculate the "outline" of this item, such that I can draw a filled QPolygon from the data.
I can't use any shortcuts such as drawing two lines with different pen widths because I want the outline to be an animated "dashed" line (aka marching ants).
I'm sure this is simple to calculate but my maths skills are very bad - I attempt to create a parallel line by doing the following:
Store the line angle.
Set the angle to 0.
Copy the line.
Use QLineF::translate() on the copy.
Set both lines angles back to the value you stored in 1 - this then causes the start and end pos of each line to be misaligned.
Hopefully someone can put me on the right track to creating a thick QPolygonF (or anything else if it makes sense) from this line which can then have an outline and fill set for painting.
Also I plan to have 1000's of these in my scene so ideally I'd also want a solution which won't take too much execution time or has a simple way of being optimized.
This image here is what I'm trying to achieve - imagine the red line is a qt dashed line rather than my very bad mspaint attempt at drawing it!
This solution works even if the arrow is moved and rotated in the scene later on:
arrow.h
#ifndef ARROW_H
#define ARROW_H
#include <QGraphicsLineItem>
#include <QObject>
#include <QtCore/qmath.h>
class Arrow : public QGraphicsLineItem, public QObject
{
public:
Arrow(qreal x1, qreal y1, qreal x2, qreal y2, QGraphicsItem* parent = 0);
virtual ~Arrow();
QPointF objectEndPoint1();
QPointF objectEndPoint2();
void setObjectEndPoint1(qreal x1, qreal y1);
void setObjectEndPoint2(qreal x2, qreal y2);
protected:
void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*);
void timerEvent(QTimerEvent* event);
private:
inline qreal pi() { return (qAtan(1.0)*4.0); }
inline qreal radians(qreal degrees) { return (degrees*pi()/180.0); }
void createArrow(qreal penWidth);
QPainterPath arrowPath;
QPainterPath strokePath;
QPainterPath fillPath;
int timerID_Anim;
int animFrame;
qreal animLength;
QVector<qreal> dashPattern;
};
#endif
arrow.cpp
#include "arrow.h"
#include <QPen>
#include <QPainter>
#include <QTimerEvent>
Arrow::Arrow(qreal x1, qreal y1, qreal x2, qreal y2, QGraphicsItem* parent) : QGraphicsLineItem(0, 0, x2, y2, parent)
{
setFlag(QGraphicsItem::ItemIsSelectable, true);
setObjectEndPoint1(x1, y1);
setObjectEndPoint2(x2, y2);
qreal dashLength = 3;
qreal dashSpace = 3;
animLength = dashLength + dashSpace;
dashPattern << dashLength << dashSpace;
createArrow(1.0);
animFrame = 0;
timerID_Anim = startTimer(100);
}
Arrow::~Arrow()
{
}
void Arrow::timerEvent(QTimerEvent* event)
{
if(event->timerId() == timerID_Anim)
{
animFrame++;
if(animFrame >= animLength) animFrame = 0;
}
update(); //This forces a repaint, even if the mouse isn't moving
}
void Arrow::createArrow(qreal penWidth)
{
QPen arrowPen = pen();
arrowPen.setWidthF(penWidth);
arrowPen.setDashPattern(dashPattern);
setPen(arrowPen);
QPointF p1 = line().p1();
QPointF p2 = line().p2();
qreal angle = line().angle();
qreal arrowHeadAngle = 32.0;
qreal length = line().length();
qreal arrowHeadLength = length/10.0;
QLineF arrowLine1(p1, p2);
QLineF arrowLine2(p1, p2);
arrowLine1.setAngle(angle + arrowHeadAngle);
arrowLine2.setAngle(angle - arrowHeadAngle);
arrowLine1.setLength(arrowHeadLength);
arrowLine2.setLength(arrowHeadLength);
QPainterPath linePath;
linePath.moveTo(p1);
linePath.lineTo(p2);
QPainterPath arrowheadPath;
arrowheadPath.moveTo(arrowLine1.p2());
arrowheadPath.lineTo(p1);
arrowheadPath.lineTo(arrowLine2.p2());
arrowheadPath.lineTo(p1);
arrowheadPath.lineTo(arrowLine1.p2());
arrowPath = QPainterPath();
arrowPath.addPath(linePath);
arrowPath.addPath(arrowheadPath);
}
void Arrow::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/)
{
QPen paintPen = pen();
QPainterPathStroker stroker;
stroker.setWidth(paintPen.widthF());
stroker.setCapStyle(Qt::FlatCap);
stroker.setJoinStyle(Qt::MiterJoin);
strokePath = stroker.createStroke(arrowPath);
strokePath = strokePath.simplified();
stroker.setDashOffset(animFrame);
stroker.setDashPattern(dashPattern);
fillPath = stroker.createStroke(strokePath);
paintPen.setDashOffset(animFrame);
painter->fillPath(fillPath, QBrush(QColor(255,0,0)));
painter->fillPath(strokePath, QBrush(QColor(0,255,0)));
}
QPointF Arrow::objectEndPoint1()
{
return scenePos();
}
QPointF Arrow::objectEndPoint2()
{
QLineF lyne = line();
qreal rot = radians(rotation());
qreal cosRot = qCos(rot);
qreal sinRot = qSin(rot);
qreal x2 = lyne.x2();
qreal y2 = lyne.y2();
qreal rotEnd2X = x2*cosRot - y2*sinRot;
qreal rotEnd2Y = x2*sinRot + y2*cosRot;
return (scenePos() + QPointF(rotEnd2X, rotEnd2Y));
}
void Arrow::setObjectEndPoint1(qreal x1, qreal y1)
{
QPointF endPt2 = objectEndPoint2();
qreal x2 = endPt2.x();
qreal y2 = endPt2.y();
qreal dx = x2 - x1;
qreal dy = y2 - y1;
setRotation(0);
setLine(0, 0, dx, dy);
setPos(x1, y1);
}
void Arrow::setObjectEndPoint2(qreal x2, qreal y2)
{
QPointF endPt1 = scenePos();
qreal x1 = endPt1.x();
qreal y1 = endPt1.y();
qreal dx = x2 - x1;
qreal dy = y2 - y1;
setRotation(0);
setLine(0, 0, dx, dy);
setPos(x1, y1);
}
I almost forgot about this question, here was my PyQt solution, I'm not sure if there is any way its performance can be improved.
class ArrowItem(QGraphicsLineItem):
def __init__(self, x, y , w, h, parent = None):
super(ArrowItem, self).__init__( x, y, w, h, parent)
self.init()
def paint(self, painter, option, widget):
painter.setClipRect( option.exposedRect )
painter.setBrush( Qt.yellow )
if self.isSelected():
p = QPen( Qt.red, 2, Qt.DashLine )
painter.setPen( p )
else:
p = QPen( Qt.black, 2, Qt.SolidLine )
p.setJoinStyle( Qt.RoundJoin )
painter.setPen( p )
painter.drawPath( self.shape() )
def shape(self):
# Calc arrow head lines based on the angle of the current line
cLine = self.line()
kArrowHeadLength = 13
kArrowHeadAngle = 32
cLineAngle = cLine.angle()
head1 = QLineF(cLine)
head2 = QLineF(cLine)
head1.setLength( kArrowHeadLength )
head1.setAngle( cLineAngle+-kArrowHeadAngle )
head2.setLength( kArrowHeadLength )
head2.setAngle( cLineAngle+kArrowHeadAngle )
# Create paths for each section of the arrow
mainLine = QPainterPath()
mainLine.moveTo( cLine.p2() )
mainLine.lineTo( cLine.p1() )
headLine1 = QPainterPath()
headLine1.moveTo( cLine.p1() )
headLine1.lineTo( head1.p2() )
headLine2 = QPainterPath()
headLine2.moveTo( cLine.p1() )
headLine2.lineTo( head2.p2() )
stroker = QPainterPathStroker()
stroker.setWidth( 4 )
# Join them together
stroke = stroker.createStroke( mainLine )
stroke.addPath( stroker.createStroke( headLine1 ) )
stroke.addPath( stroker.createStroke( headLine2 ) )
return stroke.simplified()
def boundingRect(self):
pPath = self.shape()
bRect = pPath.controlPointRect()
adjusted = QRectF( bRect.x()-1, bRect.y()-1, bRect.width()+2, bRect.height()+2 )
return adjusted
.. and of course set the item to be movable/selectable.
And so you can see the required class to get the "outlines" is QPainterPathStroker.
http://doc.qt.io/qt-5/qpainterpathstroker.html#details