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);
}
}
Related
I have QVector<QPoint> m_vertices; in my drawingwidget.h
class DrawingWidget: public QWidget {
Q_OBJECT
public:
DrawingWidget(MainWindow *parent = 0);
~DrawingWidget();
QVector<QPoint> m_vertices;
I trying to implement adding/deleting vertices on my mainwindow. I managed to make the add function, now it's should be easy to delete them, but i am a bit confused.
The main idea is, that i have a pop-up menu, where i can choose a "tool". I can add vertex, remove vertex, move vertex, add line, delete line. The idea is , when i choose for example "Add Vertex" then the "m_state" will change to "ADD_VERTEX_SELECTED" so i can only add vertices and nothing else.
enum DrawingWidgetState {
NO_TOOL_SELECTED,
ADD_VERTEX_SELECTED,
MOVE_VERTEX_SELECTED,
DELETE_VERTEX_SELECTED,
ADD_LINE_SELECTED,
DELETE_LINE_SELECTED
};
Drawing
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);
}
MousePress event. On left click, I have to delete the vertex that I clicked on
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 == DELETE_VERTEX_SELECTED) {
if(event->button() == Qt::LeftButton) {
m_vertices.clear();
}
}
}
How can i do that ?
This code uses std::find_if together with a lambda expression to find the closest point. You probably want to allow a bit more leeway than the exact circle radius, but that is up to you.
auto it = std::find_if(m_vertices.begin(), m_vertices.end(), [point = event->pos](QPoint v) {
auto d = v - point;
return QPoint::dotProduct(d, d) < 400; // dotProduct returns square of distance
});
if (it != m_vertices.end()) {
m_vertices.erase(it);
}
I'm trying to implement segments that can be draggable by mouse between two points (shown below where green and red lines must be draggable between black dash lines).
sample image
I can drag the lines by mouse.
As a next step I want to set a limit to this draggable area, which I'm having issues to implement with. I really appreciate if someone can help me to do this. I now explain the situation in a bit detail:
.h is:
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void paintEvent(QPaintEvent *e);
public slots:
void changeP1value(int);
private:
Ui::MainWindow *ui;
bool dragging = false;
QPoint p1 = QPoint(400,100);
QPoint p2 = QPoint(450,100);
QPoint p3 = QPoint(450,100);
QPoint p4 = QPoint(500,100);
QPoint *CurPoint1=nullptr;
QPoint *CurPoint2=nullptr;
protected:
virtual void mousePressEvent(QMouseEvent *event) override;
virtual void mouseReleaseEvent(QMouseEvent *event) override;
virtual void mouseMoveEvent(QMouseEvent *event) override;
};
#endif // MAINWINDOW_H
and in .cpp file:
// small helper to give us the distance
int distance(QPoint x1, QPoint x2)
{
return abs(x2.y() - x1.y());
}
// the Paint event to draw lines
void MainWindow::paintEvent(QPaintEvent *e)
{
QPainter painter1(this);
QPen pointpen(Qt::black);
pointpen.setWidth(5);
QPen linepen1(Qt::red);
linepen1.setWidth(2);
QPen linepen2(Qt::green);
linepen2.setWidth(2);
painter1.setPen(linepen1);
painter1.drawLine(p1,p2);
painter1.setPen(linepen2);
painter1.drawLine(p3,p4);
}
// when user clicks
void MainWindow::mousePressEvent(QMouseEvent *event)
{
QPoint mp = event->pos(); // where is mouse
// test if we hit the line. give user 20 pixels slack as its hard to hit one pixel
if (distance ( mp, p1) < 20 && ( mp.x() > p1.x() && mp.x() < p2.x() ) ) {
dragging = true; // flag we are dragging
CurPoint1 = &p1;
CurPoint2 = &p2;
}
else if (distance ( mp, p3) < 20 && ( mp.x() > p3.x() && mp.x() < p4.x() ) ) {
dragging = true;
CurPoint1 = &p3;
CurPoint2 = &p4;
}
}
void MainWindow::mouseReleaseEvent(QMouseEvent *event)
{
dragging = false; // if user release mouse we are not draggign anymore
}
// then when mouse move
void MainWindow::mouseMoveEvent(QMouseEvent *event)
{
// If we are dragging, call your normal slider changed function to update your points.
if (dragging)
{
if(CurPoint1->y()>50 && CurPoint1->y()<150)
{
changeP1value(event->y());
}
else
update();
}
}
void MainWindow::changeP1value(int value)
{
CurPoint1->setY(value);
CurPoint2->setY(value);
update();
}
In the mouseMoveEvent, I've condition to check if I'm dragging a line and if the hight is between 50 and 150:
if (dragging)
{
if(CurPoint1->y()>50 && CurPoint1->y()<150) //----> it get stucked and cannot be dragged down again
{
changeP1value(event->y());
}
else
update();
}
In this case I can drag , the line stops at the set limits but I cant drag down again. Can you spot an issue?
Thanks in advance
You need to check the new y value, not the current value:
void MainWindow::mouseMoveEvent(QMouseEvent *event)
{
if (dragging)
{
if(event->y() > 50 && event->y() < 150)
{
changeP1value(event->y());
}
else
update();
}
}
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.
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 want to change the color of an ellipse when I move my mouse over it.
But I haven't found anything from reference and auto-complete from Qt Creator.
Do you guys know how to do it?
Some of my code:
void DrawingWidget::paintEvent(QPaintEvent *event) {
QPainter painter(this);
painter.fillRect(event->rect(), Qt::white);
for(int i = 0; i < pointList.size(); i++) {
if (pointList[i].x() >= 0 && pointList[i].y() >= 0)
painter.drawEllipse(pointList[i], 10, 10);
}
painter.drawLines(lineList);
m_mainWindow->updateCount();
}
Mouse press event handler:
void DrawingWidget::mousePressEvent(QMouseEvent *event) {
if (event->button() == Qt::LeftButton
&& event->buttons() == Qt::LeftButton) {
// DO STUFFF
}
}
Mouse move event handler:
void DrawingWidget::mouseMoveEvent(QMouseEvent *event) {
if (m_mainWindow->getSelectedTool() == MainWindow::moveVertexTool) {
m_x = event->x();
m_y = event->y();
if (isPointNear(m_x, m_y)) {
//STUFF
}
update();
}
}
}
Now I just need a mouse OVER event (handler).
I think what you are looking for are enter and leave events.
Use QWidget::underMouse() for check if the widget is under the mouse cursor.
For example:
void IconButton::paintEvent(QPaintEvent *)
{
QPainter painter(this);
// Note isDown should really use the active state but in most styles
// this has no proper feedback
QIcon::Mode mode = QIcon::Disabled;
if (isEnabled()) {
if (isDown())
mode = QIcon::Selected;
else
mode = underMouse() ? QIcon::Active : QIcon::Normal;
}
QPixmap pixmap = icon().pixmap(iconSize(), mode);
QRect pixmapRect = QRect(0, 0, pixmap.width(), pixmap.height());
pixmapRect.moveCenter(rect().center());
if (m_autoHide)
painter.setOpacity(m_iconOpacity);
painter.drawPixmap(pixmapRect, pixmap);
}
This value is not updated properly during drag and drop operations.