Evgenii Legotckoi
Evgenii LegotckoiҚаз. 22, 2017, 4:54 Т.Ж.

Qt/C++ - сабақ 072. Qt бойынша векторлық редактордың мысалы

Көбінесе сұрақтар графикалық көріністің жұмысына, реттелетін пішіндерге, графикалық көріністегі сызбаларға, тіпті барлық сынық сызықтарға қатысты бір немесе басқа жолмен туындайды. Содан кейін тест тапсырмасы ретінде орындаған бір жоба есіме түсті.

Атап айтқанда, бұл векторлық редактор болды:

  • Тіктөртбұрыштар жасаңыз
  • Осы төртбұрыштардың өлшемін өзгертіңіз
  • Тіктөртбұрыштарды ортасына айналдырыңыз
  • Тіктөртбұрыштарды толтырыңыз
  • Тіктөртбұрыштарды градиентпен толтырыңыз
  • Тіктөртбұрыштың контурының енін өзгертіңіз
  • Тіктөртбұрыш контурының түсін орнатыңыз
  • Жолдарды жасаңыз
  • Жасалған жолдың енін және түсін орнатыңыз
  • Жолдарға үзу нүктелерін қосу арқылы екі рет басу арқылы жолдардан үзік сызықтар жасаңыз
  • Барлық графикалық нысандарды таңдап, оларды топтамаға сүйреңіз
  • Алынған кескінді SVG файлына сақтаңыз және осы файлдағы барлық графикалық нысандарды қалпына келтіріңіз

Бір кездері (1,5 жыл бұрын) мен бұл жобаға шамамен 36 сағат жұмыс уақытымды жұмсадым... енді оған аз уақыт кететін еді.


Жоба құрылымы

Ал енді жобаның негізгі тұстарымен айналысамыз. Құрылымнан бастайық.

Көріп отырғаныңыздай, бұл сайтта ұсынылған барлық мақалалардың ең үлкен жоба құрылымы. Бұл файлдардан басқа, GUI файлдары мен түймелерге арналған белгішелер де бар.

Негізгі терезе

Қолданбаның негізгі терезесі SVG файлдарын жүктеу және сақтау, графикалық объектілермен жұмыс істейтін графикалық көріністі көрсету, сонымен қатар графикалық объектілерді құру мен өңдеуді басқаратын түймелер үшін қолданылады.

Мұнда бірнеше интерфейс түймелері берілген.

  • SVG файлын ашыңыз
  • SVG файлын сақтаңыз
  • Графикалық көріністе объектілерді таңдауға болатын курсор құралын таңдау
  • Полилиния құралын таңдау
  • Тіктөртбұрыш құралын таңдау

Полилиния құралы түс және ені опциялары бар бір жолды құруға мүмкіндік береді, содан кейін курсор құралының көмегімен екі рет шерту арқылы сызықта жаңа нүктелерді жасауға және оларды жылжытуға болады, бұл полисызық жасауға мүмкіндік береді.

Тіктөртбұрыш құралы тіктөртбұрышты толтыру немесе градиент, сонымен қатар контурдың ені мен түсін беру арқылы жасауға мүмкіндік береді. Курсор құралының көмегімен тіктөртбұрыштың параметрлерін өңдеуге, сондай-ақ оның өлшемін және айналу бұрышын өзгертуге болады.

SVG файлдарынан нысандарды қалпына келтіру

SVG пішімі XML пішімі болып табылады, оның көптеген әртүрлі іске асырулары болуы мүмкін, сондықтан бұл өңдегіш қарапайым және барлық мүмкін пішімдерді жүктей алмайды, бірақ тек өзі жасай алатын файлдармен жұмыс істеуге кепілдік беріледі. Бірақ бұл жерде де жобаның Qt нұсқасының қай нұсқасы бойынша құрастырылғанына байланысты қателер болуы мүмкін.

Графикалық объектілерді қалпына келтіру үшін SvgReader көмекші класы қолданылады. Мұнда трансформация матрицасымен жұмыс істеу де қолданылады.

QList<QGraphicsItem *> SvgReader::getElements(const QString filename)
{
    QList<QGraphicsItem *> graphicsList;
    QList<QLinearGradient> gradientList;

    QDomDocument doc;
    QFile file(filename);
    if (!file.open(QIODevice::ReadOnly) || !doc.setContent(&file))
        return graphicsList;

    QDomNodeList linearList = doc.elementsByTagName("linearGradient");
    for(int i = 0; i < linearList.size(); i++) {
        QDomNode linearNode = linearList.item(i);
        QDomNodeList stopList = linearNode.childNodes();
        QLinearGradient gradient;
        for(int j = 0; j < stopList.size(); j++){
            QDomElement stopElement = stopList.item(j).toElement();
            QColor color(stopElement.attribute("stop-color"));
            gradient.setColorAt(stopElement.attribute("offset").toFloat(),color);
        }
        gradientList.append(gradient);
    }

    QDomNodeList gList = doc.elementsByTagName("g");

    for (int i = 0; i < gList.size(); i++) {
        QDomNode gNode = gList.item(i);

        QDomElement pathElement = gNode.firstChildElement("path");
        if (!pathElement.isNull()){
            VEPolyline *polyline = new VEPolyline();
            auto pElement = gNode.toElement();

            polyline->setBrush(QBrush(Qt::transparent));

            QColor strokeColor(pElement.attribute("stroke", "#000000"));
            strokeColor.setAlphaF(pElement.attribute("stroke-opacity").toFloat());
            polyline->setPen(QPen(strokeColor, pElement.attribute("stroke-width", "0").toInt()));

            QPainterPath path;
            QStringList listDotes = pathElement.attribute("d").split(" ");
            QString first = listDotes.at(0);
            QStringList firstElement = first.replace(QString("M"),QString("")).split(",");
            path.moveTo(firstElement.at(0).toInt(),firstElement.at(1).toInt());
            for(int i = 1; i < listDotes.length(); i++){
                QString other = listDotes.at(i);
                QStringList dot = other.replace(QString("L"),QString("")).split(",");
                path.lineTo(dot.at(0).toInt(),dot.at(1).toInt());
            }
            polyline->setPath(path);
            graphicsList.append(polyline);
            continue;
        }

        QDomElement rectangle = gNode.firstChildElement("rect");
        if (!rectangle.isNull()){
            VERectangle *rect = new VERectangle();
            auto gElement = gNode.toElement();
            rect->setRect(rectangle.attribute("x").toInt(),
                          rectangle.attribute("y").toInt(),
                          rectangle.attribute("width").toInt(),
                          rectangle.attribute("height").toInt());

            QString fill = gElement.attribute("fill", "#ffffff");
            if(fill.contains("url(#gradient")){
                fill.replace(QString("url(#gradient"), QString(""));
                fill.replace(QString(")"), QString(""));
                QLinearGradient g = gradientList.at(fill.toInt() - 1);
                auto tmpRect = rect->rect();
                g.setStart(tmpRect.left() + tmpRect.width()/2,tmpRect.top());
                g.setFinalStop(tmpRect.left() + tmpRect.width()/2,tmpRect.bottom());
                rect->setBrush(QBrush(g));
            } else {
                QColor fillColor(gElement.attribute("fill", "#ffffff"));
                fillColor.setAlphaF(gElement.attribute("fill-opacity","0").toFloat());
                rect->setBrush(QBrush(fillColor));
            }

            QColor strokeColor(gElement.attribute("stroke", "#000000"));
            strokeColor.setAlphaF(gElement.attribute("stroke-opacity").toFloat());

            QString transString = gElement.attribute("transform");
            transString.replace(QString("matrix("),QString(""));
            transString.replace(QString(")"),QString(""));
            QStringList transList = transString.split(",");

            QTransform trans(rect->transform());

            qreal m11 = trans.m11();    // Horizontal scaling
            qreal m12 = trans.m12();    // Vertical shearing
            qreal m13 = trans.m13();    // Horizontal Projection
            qreal m21 = trans.m21();    // Horizontal shearing
            qreal m22 = trans.m22();    // vertical scaling
            qreal m23 = trans.m23();    // Vertical Projection
            qreal m31 = trans.m31();    // Horizontal Position (DX)
            qreal m32 = trans.m32();    // Vertical Position (DY)
            qreal m33 = trans.m33();    // Addtional Projection Factor

            m11 = transList.at(0).toFloat();
            m12 = transList.at(1).toFloat();
            m21 = transList.at(2).toFloat();
            m22 = transList.at(3).toFloat();
            m31 = transList.at(4).toFloat();
            m32 = transList.at(5).toFloat();

            trans.setMatrix(m11,m12,m13,m21,m22,m23,m31,m32,m33);
            rect->setTransform(trans);
            rect->setPen(QPen(strokeColor,gElement.attribute("stroke-width", "0").toInt()));

            graphicsList.append(rect);
            continue;
        }
    }
    file.close();

    return graphicsList;
}

QRectF SvgReader::getSizes(const QString filename)
{
    QDomDocument doc;
    QFile file(filename);
    if (!file.open(QIODevice::ReadOnly) || !doc.setContent(&file))
        return QRectF(0,0,200,200);

    QDomNodeList list = doc.elementsByTagName("svg");
    if(list.length() > 0) {
        auto svgElement = list.item(0).toElement();
        auto parameters = svgElement.attribute("viewBox").split(" ");
        return QRectF(parameters.at(0).toInt(),
                      parameters.at(1).toInt(),
                      parameters.at(2).toInt(),
                      parameters.at(3).toInt());
    }
    return QRectF(0,0,200,200);
}

Сынық сызық

Бұл жағдайда графикалық объектілермен жұмыс істеудің барлық мәні мынада: тінтуірдің объектінің үстінен қозғалуына, шертулерге және т.б. жауап беру үшін тінтуірдің оқиғаларын өңдеу әдістерін қайта анықтау керек.

vepolyline.h

#ifndef VEPOLYLINE_H
#define VEPOLYLINE_H

#include <QObject>
#include <QGraphicsPathItem>

class DotSignal;
class QGraphicsSceneMouseEvent;

class VEPolyline : public QObject, public QGraphicsPathItem
{
    Q_OBJECT
    Q_PROPERTY(QPointF previousPosition READ previousPosition WRITE setPreviousPosition NOTIFY previousPositionChanged)

public:
    explicit VEPolyline(QObject *parent = 0);
    ~VEPolyline();

    QPointF previousPosition() const;
    void setPreviousPosition(const QPointF previousPosition);
    void setPath(const QPainterPath &path);

signals:
    void previousPositionChanged();
    void clicked(VEPolyline *rect);
    void signalMove(QGraphicsItem *item, qreal dx, qreal dy);

protected:
    void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
    void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
    void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
    void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
    void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
    void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override;
    void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;

public slots:

private slots:
    void slotMove(QGraphicsItem *signalOwner, qreal dx, qreal dy);
    void checkForDeletePoints();

private:
    QPointF m_previousPosition;
    bool m_leftMouseButtonPressed;
    QList<DotSignal *> listDotes;
    int m_pointForCheck;

    void updateDots();
};

#endif // VEPOLYLINE_H

vepolyline.cpp

Графикалық көріністе сызық нүктелерінің дұрыс орналасуы үшін графикалық көріністегі алдыңғы шертудің орны сақталатын көмекші айнымалыны пайдалану қажет - m_leftMouseButtonPressed . Осы айнымалыға қатысты жаңа нүктенің дұрыс орнын немесе полисызықтың ескі нүктесінің жаңа орнын есептеу үшін дельта позициясы есептеледі.

Түзуде жаңа нүктенің құрылу орнын анықтау үшін mouseDoubleClickEvent әдісі қолданылады. Әдістің мәні мынада: нүктенің түзу жолындағы орнын анықтау қажет QPainterPath берілген графикалық нысанға жаңа жол.

Сондай-ақ, қызықты жері, графикалық көрініс айналасында нүктелерді жылжыту арнайы графикалық объектілерді, DotSignal класының сигнал нүктелерін қолдану арқылы жүзеге асырылады, олар нүктелердің координаталарын және координаттардың жаңа орындарын хабарлайтын қарапайым тіктөртбұрыштар болып табылады. нүктелердің саны өзгерген кезде автоматты өзгерту орын алады * QPainterPath. *

#include "vepolyline.h"
#include <QGraphicsSceneMouseEvent>
#include <QPainterPath>
#include <QGraphicsScene>
#include <QGraphicsPathItem>
#include <QDebug>
#include "dotsignal.h"

VEPolyline::VEPolyline(QObject *parent) :
    QObject(parent)
{
    setAcceptHoverEvents(true);
    setFlags(ItemIsSelectable|ItemSendsGeometryChanges);
}

VEPolyline::~VEPolyline()
{

}

QPointF VEPolyline::previousPosition() const
{
    return m_previousPosition;
}

void VEPolyline::setPreviousPosition(const QPointF previousPosition)
{
    if (m_previousPosition == previousPosition)
        return;

    m_previousPosition = previousPosition;
    emit previousPositionChanged();
}

void VEPolyline::setPath(const QPainterPath &path)
{
    QGraphicsPathItem::setPath(path);
}

void VEPolyline::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    if (m_leftMouseButtonPressed) {
        auto dx = event->scenePos().x() - m_previousPosition.x();
        auto dy = event->scenePos().y() - m_previousPosition.y();
        moveBy(dx,dy);
        setPreviousPosition(event->scenePos());
        emit signalMove(this, dx, dy);
    }
    QGraphicsItem::mouseMoveEvent(event);
}

void VEPolyline::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    if (event->button() & Qt::LeftButton) {
        m_leftMouseButtonPressed = true;
        setPreviousPosition(event->scenePos());
        emit clicked(this);
    }
    QGraphicsItem::mousePressEvent(event);
}

void VEPolyline::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    if (event->button() & Qt::LeftButton) {
        m_leftMouseButtonPressed = false;
    }
    QGraphicsItem::mouseReleaseEvent(event);
}

void VEPolyline::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
    QPointF clickPos = event->pos();
    QLineF checkLineFirst(clickPos.x() - 5, clickPos.y() - 5, clickPos.x() + 5, clickPos.y() + 5);
    QLineF checkLineSecond(clickPos.x() + 5, clickPos.y() - 5, clickPos.x() - 5, clickPos.y() + 5);
    QPainterPath oldPath = path();
    QPainterPath newPath;
    for(int i = 0; i < oldPath.elementCount(); i++){
        QLineF checkableLine(oldPath.elementAt(i), oldPath.elementAt(i+1));
        if(checkableLine.intersect(checkLineFirst,0) == 1 || checkableLine.intersect(checkLineSecond,0) == 1){
            if(i == 0){
                newPath.moveTo(oldPath.elementAt(i));
                newPath.lineTo(clickPos);
            } else {
                newPath.lineTo(oldPath.elementAt(i));
                newPath.lineTo(clickPos);
            }
        } else {
            if(i == 0){
                newPath.moveTo(oldPath.elementAt(i));
            } else {
                newPath.lineTo(oldPath.elementAt(i));
            }
        }
        if(i == (oldPath.elementCount() - 2)) {
            newPath.lineTo(oldPath.elementAt(i + 1));
            i++;
        }
    }
    setPath(newPath);
    updateDots();
    QGraphicsItem::mouseDoubleClickEvent(event);
}

void VEPolyline::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
    if(!listDotes.isEmpty()){
        foreach (DotSignal *dot, listDotes) {
            dot->deleteLater();
        }
        listDotes.clear();
    }
    QGraphicsItem::hoverLeaveEvent(event);
}

void VEPolyline::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
{
    QGraphicsItem::hoverMoveEvent(event);
}

void VEPolyline::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
    QPainterPath linePath = path();
    for(int i = 0; i < linePath.elementCount(); i++){
        QPointF point = linePath.elementAt(i);
        DotSignal *dot = new DotSignal(point, this);
        connect(dot, &DotSignal::signalMove, this, &VEPolyline::slotMove);
        connect(dot, &DotSignal::signalMouseRelease, this, &VEPolyline::checkForDeletePoints);
        dot->setDotFlags(DotSignal::Movable);
        listDotes.append(dot);
    }
    QGraphicsItem::hoverEnterEvent(event);
}

void VEPolyline::slotMove(QGraphicsItem *signalOwner, qreal dx, qreal dy)
{
    QPainterPath linePath = path();
    for(int i = 0; i < linePath.elementCount(); i++){
        if(listDotes.at(i) == signalOwner){
            QPointF pathPoint = linePath.elementAt(i);
            linePath.setElementPositionAt(i, pathPoint.x() + dx, pathPoint.y() + dy);
            m_pointForCheck = i;
        }
    }
    setPath(linePath);
}

void VEPolyline::checkForDeletePoints()
{
    if(m_pointForCheck != -1){
        QPainterPath linePath = path();

        QPointF pathPoint = linePath.elementAt(m_pointForCheck);
        if(m_pointForCheck > 0){
            QLineF lineToNear(linePath.elementAt(m_pointForCheck-1),pathPoint);
            if(lineToNear.length() < 6.0) {
                QPainterPath newPath;
                newPath.moveTo(linePath.elementAt(0));
                for(int i = 1; i < linePath.elementCount(); i++){
                    if(i != m_pointForCheck){
                        newPath.lineTo(linePath.elementAt(i));
                    }
                }
                setPath(newPath);
            }
        }
        if(m_pointForCheck < linePath.elementCount() - 1){
            QLineF lineToNear(linePath.elementAt(m_pointForCheck+1),pathPoint);
            if(lineToNear.length() < 6.0) {
                QPainterPath newPath;
                newPath.moveTo(linePath.elementAt(0));
                for(int i = 1; i < linePath.elementCount(); i++){
                    if(i != m_pointForCheck){
                        newPath.lineTo(linePath.elementAt(i));
                    }
                }
                setPath(newPath);
            }
        }
        updateDots();
        m_pointForCheck = -1;
    }
}

void VEPolyline::updateDots()
{
    if(!listDotes.isEmpty()){
        foreach (DotSignal *dot, listDotes) {
            dot->deleteLater();
        }
        listDotes.clear();
    }
    QPainterPath linePath = path();
    for(int i = 0; i < linePath.elementCount(); i++){
        QPointF point = linePath.elementAt(i);
        DotSignal *dot = new DotSignal(point, this);
        connect(dot, &DotSignal::signalMove, this, &VEPolyline::slotMove);
        connect(dot, &DotSignal::signalMouseRelease, this, &VEPolyline::checkForDeletePoints);
        dot->setDotFlags(DotSignal::Movable);
        listDotes.append(dot);
    }
}

Сигнал нүктелері

Сигнал нүктелері сигнал нүктесінің негізгі бөлігінің қозғалысын көрсету үшін қолданылады. Яғни, әрбір сигнал нүктесі белгілі бір ата-анаға байланысты және оның кейбір бөлігін жылжытуға жауапты.

Сигнал қозғалысты көрсету үшін қолданылады.

void signalMove(QGraphicsItem *signalOwner, qreal dx, qreal dy);

нүктелік сигнал.сағ

#ifndef DOTSIGNAL_H
#define DOTSIGNAL_H

#include <QObject>
#include <QGraphicsRectItem>

class QGraphicsSceneHoverEventPrivate;
class QGraphicsSceneMouseEvent;

class DotSignal : public QObject, public QGraphicsRectItem
{
    Q_OBJECT
    Q_PROPERTY(QPointF previousPosition READ previousPosition WRITE setPreviousPosition NOTIFY previousPositionChanged)

public:
    explicit DotSignal(QGraphicsItem *parentItem = 0, QObject *parent = 0);
    explicit DotSignal(QPointF pos, QGraphicsItem *parentItem = 0, QObject *parent = 0);
    ~DotSignal();

    enum Flags {
        Movable = 0x01
    };

    QPointF previousPosition() const;
    void setPreviousPosition(const QPointF previousPosition);

    void setDotFlags(unsigned int flags);

signals:
    void previousPositionChanged();
    void signalMouseRelease();
    void signalMove(QGraphicsItem *signalOwner, qreal dx, qreal dy);

protected:
    void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
    void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
    void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
    void hoverEnterEvent(QGraphicsSceneHoverEvent *event);
    void hoverLeaveEvent(QGraphicsSceneHoverEvent *event);

public slots:

private:
    unsigned int m_flags;
    QPointF m_previousPosition;
};

#endif // DOTSIGNAL_H

dotsignal.cpp

Келесі кодтан көрініп тұрғандай, барлық әдісті қайта анықтау координаталар дельтасын есептеу және нүктенің өзін жобалау үшін ғана қолданылады, мысалы, тінтуір курсоры нүктенің үстінде болса, онда нүкте қызыл болады, егер ол нүктеден кетсе нүктенің ауданы болса, онда нүкте қара болады.

#include "dotsignal.h"
#include <QBrush>
#include <QColor>
#include <QGraphicsSceneHoverEvent>
#include <QGraphicsSceneMouseEvent>

DotSignal::DotSignal(QGraphicsItem *parentItem, QObject *parent) :
    QObject(parent)
{
    setParentItem(parentItem);
    setAcceptHoverEvents(true);
    setBrush(QBrush(Qt::black));
    setRect(-4,-4,8,8);
    setDotFlags(0);
}

DotSignal::DotSignal(QPointF pos, QGraphicsItem *parentItem, QObject *parent) :
    QObject(parent)
{
    setParentItem(parentItem);
    setAcceptHoverEvents(true);
    setBrush(QBrush(Qt::black));
    setRect(-4,-4,8,8);
    setPos(pos);
    setPreviousPosition(pos);
    setDotFlags(0);
}

DotSignal::~DotSignal()
{

}

QPointF DotSignal::previousPosition() const
{
    return m_previousPosition;
}

void DotSignal::setPreviousPosition(const QPointF previousPosition)
{
    if (m_previousPosition == previousPosition)
        return;

    m_previousPosition = previousPosition;
    emit previousPositionChanged();
}

void DotSignal::setDotFlags(unsigned int flags)
{
    m_flags = flags;
}

void DotSignal::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    if(m_flags & Movable){
        auto dx = event->scenePos().x() - m_previousPosition.x();
        auto dy = event->scenePos().y() - m_previousPosition.y();
        moveBy(dx,dy);
        setPreviousPosition(event->scenePos());
        emit signalMove(this, dx, dy);
    } else {
        QGraphicsItem::mouseMoveEvent(event);
    }
}

void DotSignal::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    if(m_flags & Movable){
        setPreviousPosition(event->scenePos());
    } else {
        QGraphicsItem::mousePressEvent(event);
    }
}

void DotSignal::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    emit signalMouseRelease();
    QGraphicsItem::mouseReleaseEvent(event);
}

void DotSignal::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
    Q_UNUSED(event)
    setBrush(QBrush(Qt::red));
}

void DotSignal::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
    Q_UNUSED(event)
    setBrush(QBrush(Qt::black));
}

Тіктөртбұрыш

Тіктөртбұрыштың жұмысы сызықтан ерекшеленеді, бізде оның өлшемін өзгерту мүмкіндігі бар, сонымен қатар орталықты айналдыру мүмкіндігі бар. Бұл үшін сигналдық нүктелер де қолданылады. Дегенмен, екі мүмкін күй бар. Өлшемді өзгерту үшін бір күй, айналдыру үшін басқа күй:

enum ActionStates {
    ResizeState = 0x01,
    RotationState = 0x02
};

Санақтардың өлшемін өзгертуге және айналдыруға жауапты сигнал нүктелерін басқару үшін де пайдаланылады.

enum CornerFlags {
    Top = 0x01,
    Bottom = 0x02,
    Left = 0x04,
    Right = 0x08,
    TopLeft = Top|Left,
    TopRight = Top|Right,
    BottomLeft = Bottom|Left,
    BottomRight = Bottom|Right
};

enum CornerGrabbers {
    GrabberTop = 0,
    GrabberBottom,
    GrabberLeft,
    GrabberRight,
    GrabberTopLeft,
    GrabberTopRight,
    GrabberBottomLeft,
    GrabberBottomRight
};

Сигнал нүктелерінің бірінің орнын өзгерту кезінде барлық басқа нүктелердің орнын өзгерту қажет, бұл үшін setPositionGrabbers() әдісі пайдаланылады, басқа нәрселермен қатар өңдеу режиміне, әдіске байланысты сигнал нүктелерінің көрінуін орнату үшін пайдаланылады. setVisibilityGrabbers().

Айналу өзгерген кезде айналу бұрышын қалыпқа келтіру үшін нормалау функциясы радиандағы айналу бұрышы 2Pi аспайтындай пайдаланылады.

static const double Pi = 3.14159265358979323846264338327950288419717;
static double TwoPi = 2.0 * Pi;

static qreal normalizeAngle(qreal angle)
{
    while (angle < 0)
        angle += TwoPi;
    while (angle > TwoPi)
        angle -= TwoPi;
    return angle;
}

verectangle.h

#ifndef RECTANGLE_H
#define RECTANGLE_H

#include <QObject>
#include <QGraphicsRectItem>

class DotSignal;
class QGraphicsSceneMouseEvent;

class VERectangle : public QObject, public QGraphicsRectItem
{
    Q_OBJECT
    Q_PROPERTY(QPointF previousPosition READ previousPosition WRITE setPreviousPosition NOTIFY previousPositionChanged)

public:
    explicit VERectangle(QObject * parent = 0);
    ~VERectangle();

    enum ActionStates {
        ResizeState = 0x01,
        RotationState = 0x02
    };

    enum CornerFlags {
        Top = 0x01,
        Bottom = 0x02,
        Left = 0x04,
        Right = 0x08,
        TopLeft = Top|Left,
        TopRight = Top|Right,
        BottomLeft = Bottom|Left,
        BottomRight = Bottom|Right
    };

    enum CornerGrabbers {
        GrabberTop = 0,
        GrabberBottom,
        GrabberLeft,
        GrabberRight,
        GrabberTopLeft,
        GrabberTopRight,
        GrabberBottomLeft,
        GrabberBottomRight
    };

    QPointF previousPosition() const;
    void setPreviousPosition(const QPointF previousPosition);

    void setRect(qreal x, qreal y, qreal w, qreal h);
    void setRect(const QRectF &rect);

signals:
    void rectChanged(VERectangle *rect);
    void previousPositionChanged();
    void clicked(VERectangle *rect);
    void signalMove(QGraphicsItem *item, qreal dx, qreal dy);

protected:
    void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
    void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
    void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
    void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
    void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
    void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
    void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override;
    QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;

private:
    unsigned int m_cornerFlags;
    unsigned int m_actionFlags;
    QPointF m_previousPosition;
    bool m_leftMouseButtonPressed;
    DotSignal *cornerGrabber[8];

    void resizeLeft( const QPointF &pt);
    void resizeRight( const QPointF &pt);
    void resizeBottom(const QPointF &pt);
    void resizeTop(const QPointF &pt);

    void rotateItem(const QPointF &pt);
    void setPositionGrabbers();
    void setVisibilityGrabbers();
    void hideGrabbers();
};

#endif // RECTANGLE_H

verectangle.cpp

#include "verectangle.h"
#include <QPainter>
#include <QDebug>
#include <QCursor>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsRectItem>
#include <math.h>
#include "dotsignal.h"

static const double Pi = 3.14159265358979323846264338327950288419717;
static double TwoPi = 2.0 * Pi;

static qreal normalizeAngle(qreal angle)
{
    while (angle < 0)
        angle += TwoPi;
    while (angle > TwoPi)
        angle -= TwoPi;
    return angle;
}

VERectangle::VERectangle(QObject *parent) :
    QObject(parent),
    m_cornerFlags(0),
    m_actionFlags(ResizeState)
{
    setAcceptHoverEvents(true);
    setFlags(ItemIsSelectable|ItemSendsGeometryChanges);
    for(int i = 0; i < 8; i++){
        cornerGrabber[i] = new DotSignal(this);
    }
    setPositionGrabbers();
}

VERectangle::~VERectangle()
{
    for(int i = 0; i < 8; i++){
        delete cornerGrabber[i];
    }
}

QPointF VERectangle::previousPosition() const
{
    return m_previousPosition;
}

void VERectangle::setPreviousPosition(const QPointF previousPosition)
{
    if (m_previousPosition == previousPosition)
        return;

    m_previousPosition = previousPosition;
    emit previousPositionChanged();
}

void VERectangle::setRect(qreal x, qreal y, qreal w, qreal h)
{
    setRect(QRectF(x,y,w,h));
}

void VERectangle::setRect(const QRectF &rect)
{
    QGraphicsRectItem::setRect(rect);
    if(brush().gradient() != 0){
        const QGradient * grad = brush().gradient();
        if(grad->type() == QGradient::LinearGradient){
            auto tmpRect = this->rect();
            const QLinearGradient *lGradient = static_cast<const QLinearGradient *>(grad);
            QLinearGradient g = *const_cast<QLinearGradient*>(lGradient);
            g.setStart(tmpRect.left() + tmpRect.width()/2,tmpRect.top());
            g.setFinalStop(tmpRect.left() + tmpRect.width()/2,tmpRect.bottom());
            setBrush(g);
        }
    }
}

void VERectangle::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    QPointF pt = event->pos();
    if(m_actionFlags == ResizeState){
        switch (m_cornerFlags) {
        case Top:
            resizeTop(pt);
            break;
        case Bottom:
            resizeBottom(pt);
            break;
        case Left:
            resizeLeft(pt);
            break;
        case Right:
            resizeRight(pt);
            break;
        case TopLeft:
            resizeTop(pt);
            resizeLeft(pt);
            break;
        case TopRight:
            resizeTop(pt);
            resizeRight(pt);
            break;
        case BottomLeft:
            resizeBottom(pt);
            resizeLeft(pt);
            break;
        case BottomRight:
            resizeBottom(pt);
            resizeRight(pt);
            break;
        default:
            if (m_leftMouseButtonPressed) {
                setCursor(Qt::ClosedHandCursor);
                auto dx = event->scenePos().x() - m_previousPosition.x();
                auto dy = event->scenePos().y() - m_previousPosition.y();
                moveBy(dx,dy);
                setPreviousPosition(event->scenePos());
                emit signalMove(this, dx, dy);
            }
            break;
        }
    } else {
        switch (m_cornerFlags) {
        case TopLeft:
        case TopRight:
        case BottomLeft:
        case BottomRight: {
            rotateItem(pt);
            break;
        }
        default:
            if (m_leftMouseButtonPressed) {
                setCursor(Qt::ClosedHandCursor);
                auto dx = event->scenePos().x() - m_previousPosition.x();
                auto dy = event->scenePos().y() - m_previousPosition.y();
                moveBy(dx,dy);
                setPreviousPosition(event->scenePos());
                emit signalMove(this, dx, dy);
            }
            break;
        }
    }
    QGraphicsItem::mouseMoveEvent(event);
}

void VERectangle::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    if (event->button() & Qt::LeftButton) {
        m_leftMouseButtonPressed = true;
        setPreviousPosition(event->scenePos());
        emit clicked(this);
    }
    QGraphicsItem::mousePressEvent(event);
}

void VERectangle::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    if (event->button() & Qt::LeftButton) {
        m_leftMouseButtonPressed = false;
    }
    QGraphicsItem::mouseReleaseEvent(event);
}

void VERectangle::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
    m_actionFlags = (m_actionFlags == ResizeState)?RotationState:ResizeState;
    setVisibilityGrabbers();
    QGraphicsItem::mouseDoubleClickEvent(event);
}

void VERectangle::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
    setPositionGrabbers();
    setVisibilityGrabbers();
    QGraphicsItem::hoverEnterEvent(event);
}

void VERectangle::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
    m_cornerFlags = 0;
    hideGrabbers();
    setCursor(Qt::CrossCursor);
    QGraphicsItem::hoverLeaveEvent( event );
}

void VERectangle::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
{
    QPointF pt = event->pos();              // The current position of the mouse
    qreal drx = pt.x() - rect().right();    // Distance between the mouse and the right
    qreal dlx = pt.x() - rect().left();     // Distance between the mouse and the left

    qreal dby = pt.y() - rect().top();      // Distance between the mouse and the top
    qreal dty = pt.y() - rect().bottom();   // Distance between the mouse and the bottom

    // If the mouse position is within a radius of 7
    // to a certain side( top, left, bottom or right)
    // we set the Flag in the Corner Flags Register

    m_cornerFlags = 0;
    if( dby < 7 && dby > -7 ) m_cornerFlags |= Top;       // Top side
    if( dty < 7 && dty > -7 ) m_cornerFlags |= Bottom;    // Bottom side
    if( drx < 7 && drx > -7 ) m_cornerFlags |= Right;     // Right side
    if( dlx < 7 && dlx > -7 ) m_cornerFlags |= Left;      // Left side

    if(m_actionFlags == ResizeState){
        QPixmap p(":/icons/arrow-up-down.png");
        QPixmap pResult;
        QTransform trans = transform();
        switch (m_cornerFlags) {
        case Top:
        case Bottom:
            pResult = p.transformed(trans);
            setCursor(pResult.scaled(24,24,Qt::KeepAspectRatio));
            break;
        case Left:
        case Right:
            trans.rotate(90);
            pResult = p.transformed(trans);
            setCursor(pResult.scaled(24,24,Qt::KeepAspectRatio));
            break;
        case TopRight:
        case BottomLeft:
            trans.rotate(45);
            pResult = p.transformed(trans);
            setCursor(pResult.scaled(24,24,Qt::KeepAspectRatio));
            break;
        case TopLeft:
        case BottomRight:
            trans.rotate(135);
            pResult = p.transformed(trans);
            setCursor(pResult.scaled(24,24,Qt::KeepAspectRatio));
            break;
        default:
            setCursor(Qt::CrossCursor);
            break;
        }
    } else {
        switch (m_cornerFlags) {
        case TopLeft:
        case TopRight:
        case BottomLeft:
        case BottomRight: {
            QPixmap p(":/icons/rotate-right.png");
            setCursor(QCursor(p.scaled(24,24,Qt::KeepAspectRatio)));
            break;
        }
        default:
            setCursor(Qt::CrossCursor);
            break;
        }
    }
    QGraphicsItem::hoverMoveEvent( event );
}

QVariant VERectangle::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
{
    switch (change) {
    case QGraphicsItem::ItemSelectedChange:
        m_actionFlags = ResizeState;
        break;
    default:
        break;
    }
    return QGraphicsItem::itemChange(change, value);
}

void VERectangle::resizeLeft(const QPointF &pt)
{
    QRectF tmpRect = rect();
    // if the mouse is on the right side we return
    if( pt.x() > tmpRect.right() )
        return;
    qreal widthOffset =  ( pt.x() - tmpRect.right() );
    // limit the minimum width
    if( widthOffset > -10 )
        return;
    // if it's negative we set it to a positive width value
    if( widthOffset < 0 )
        tmpRect.setWidth( -widthOffset );
    else
        tmpRect.setWidth( widthOffset );
    // Since it's a left side , the rectange will increase in size
    // but keeps the topLeft as it was
    tmpRect.translate( rect().width() - tmpRect.width() , 0 );
    prepareGeometryChange();
    // Set the ne geometry
    setRect( tmpRect );
    // Update to see the result
    update();
    setPositionGrabbers();
}

void VERectangle::resizeRight(const QPointF &pt)
{
    QRectF tmpRect = rect();
    if( pt.x() < tmpRect.left() )
        return;
    qreal widthOffset =  ( pt.x() - tmpRect.left() );
    if( widthOffset < 10 ) /// limit
        return;
    if( widthOffset < 10)
        tmpRect.setWidth( -widthOffset );
    else
        tmpRect.setWidth( widthOffset );
    prepareGeometryChange();
    setRect( tmpRect );
    update();
    setPositionGrabbers();
}

void VERectangle::resizeBottom(const QPointF &pt)
{
    QRectF tmpRect = rect();
    if( pt.y() < tmpRect.top() )
        return;
    qreal heightOffset =  ( pt.y() - tmpRect.top() );
    if( heightOffset < 11 ) /// limit
        return;
    if( heightOffset < 0)
        tmpRect.setHeight( -heightOffset );
    else
        tmpRect.setHeight( heightOffset );
    prepareGeometryChange();
    setRect( tmpRect );
    update();
    setPositionGrabbers();
}

void VERectangle::resizeTop(const QPointF &pt)
{
    QRectF tmpRect = rect();
    if( pt.y() > tmpRect.bottom() )
        return;
    qreal heightOffset =  ( pt.y() - tmpRect.bottom() );
    if( heightOffset > -11 ) /// limit
        return;
    if( heightOffset < 0)
        tmpRect.setHeight( -heightOffset );
    else
        tmpRect.setHeight( heightOffset );
    tmpRect.translate( 0 , rect().height() - tmpRect.height() );
    prepareGeometryChange();
    setRect( tmpRect );
    update();
    setPositionGrabbers();
}

void VERectangle::rotateItem(const QPointF &pt)
{
    QRectF tmpRect = rect();
    QPointF center = boundingRect().center();
    QPointF corner;
    switch (m_cornerFlags) {
    case TopLeft:
        corner = tmpRect.topLeft();
        break;
    case TopRight:
        corner = tmpRect.topRight();
        break;
    case BottomLeft:
        corner = tmpRect.bottomLeft();
        break;
    case BottomRight:
        corner = tmpRect.bottomRight();
        break;
    default:
        break;
    }

    QLineF lineToTarget(center,corner);
    QLineF lineToCursor(center, pt);
    // Angle to Cursor and Corner Target points
    qreal angleToTarget = ::acos(lineToTarget.dx() / lineToTarget.length());
    qreal angleToCursor = ::acos(lineToCursor.dx() / lineToCursor.length());

    if (lineToTarget.dy() < 0)
        angleToTarget = TwoPi - angleToTarget;
    angleToTarget = normalizeAngle((Pi - angleToTarget) + Pi / 2);

    if (lineToCursor.dy() < 0)
        angleToCursor = TwoPi - angleToCursor;
    angleToCursor = normalizeAngle((Pi - angleToCursor) + Pi / 2);

    // Result difference angle between Corner Target point and Cursor Point
    auto resultAngle = angleToTarget - angleToCursor;

    QTransform trans = transform();
    trans.translate( center.x(), center.y());
    trans.rotateRadians(rotation() + resultAngle, Qt::ZAxis);
    trans.translate( -center.x(),  -center.y());
    setTransform(trans);
}

void VERectangle::setPositionGrabbers()
{
    QRectF tmpRect = rect();
    cornerGrabber[GrabberTop]->setPos(tmpRect.left() + tmpRect.width()/2, tmpRect.top());
    cornerGrabber[GrabberBottom]->setPos(tmpRect.left() + tmpRect.width()/2, tmpRect.bottom());
    cornerGrabber[GrabberLeft]->setPos(tmpRect.left(), tmpRect.top() + tmpRect.height()/2);
    cornerGrabber[GrabberRight]->setPos(tmpRect.right(), tmpRect.top() + tmpRect.height()/2);
    cornerGrabber[GrabberTopLeft]->setPos(tmpRect.topLeft().x(), tmpRect.topLeft().y());
    cornerGrabber[GrabberTopRight]->setPos(tmpRect.topRight().x(), tmpRect.topRight().y());
    cornerGrabber[GrabberBottomLeft]->setPos(tmpRect.bottomLeft().x(), tmpRect.bottomLeft().y());
    cornerGrabber[GrabberBottomRight]->setPos(tmpRect.bottomRight().x(), tmpRect.bottomRight().y());
}

void VERectangle::setVisibilityGrabbers()
{
    cornerGrabber[GrabberTopLeft]->setVisible(true);
    cornerGrabber[GrabberTopRight]->setVisible(true);
    cornerGrabber[GrabberBottomLeft]->setVisible(true);
    cornerGrabber[GrabberBottomRight]->setVisible(true);

    if(m_actionFlags == ResizeState){
        cornerGrabber[GrabberTop]->setVisible(true);
        cornerGrabber[GrabberBottom]->setVisible(true);
        cornerGrabber[GrabberLeft]->setVisible(true);
        cornerGrabber[GrabberRight]->setVisible(true);
    } else {
        cornerGrabber[GrabberTop]->setVisible(false);
        cornerGrabber[GrabberBottom]->setVisible(false);
        cornerGrabber[GrabberLeft]->setVisible(false);
        cornerGrabber[GrabberRight]->setVisible(false);
    }
}

void VERectangle::hideGrabbers()
{
    for(int i = 0; i < 8; i++){
        cornerGrabber[i]->setVisible(false);
    }
}

Графикалық көрініс

Графикалық көрініспен жұмыс құралдың ағымдағы түріне байланысты барлық құралдардың жұмысын біріктіреді.

enum ActionTypes {
    DefaultType,
    LineType,
    RectangleType,
    SelectionType
};

Көріп отырғаныңыздай, мұнда қолданылатын құралдардың екі түрі бар. Олардың екеуі полисызық және тіктөртбұрыш. Бір құрал барлық объектілердің таңдауын жасау үшін пайдаланылады. Ал біріншісі объектілерді таңдауға және оларды өңдеуге мүмкіндік беретін кәдімгі курсор құралы ретінде қызмет етеді.

veworkplace.h

#ifndef WORKPLACE_H
#define WORKPLACE_H

#include <QObject>
#include <QGraphicsScene>

class QGraphicsSceneMouseEvent;
class QKeyEvent;

class VEWorkplace : public QGraphicsScene
{
    Q_OBJECT

    Q_PROPERTY(int currentAction READ currentAction WRITE setCurrentAction NOTIFY currentActionChanged)
    Q_PROPERTY(QPointF previousPosition READ previousPosition WRITE setPreviousPosition NOTIFY previousPositionChanged)

public:
    explicit VEWorkplace(QObject *parent = 0);
    ~VEWorkplace();

    enum ActionTypes {
        DefaultType,
        LineType,
        RectangleType,
        SelectionType
    };

    int currentAction() const;
    QPointF previousPosition() const;

    void setCurrentAction(const int type);
    void setPreviousPosition(const QPointF previousPosition);

signals:
    void previousPositionChanged();
    void currentActionChanged(int);
    void signalSelectItem(QGraphicsItem *item);
    void signalNewSelectItem(QGraphicsItem *item);

protected:
    void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
    void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
    void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
    void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
    void keyPressEvent(QKeyEvent *event) override;

private slots:
    void deselectItems();

public slots:
    void slotMove(QGraphicsItem *signalOwner, qreal dx, qreal dy);

private:
    QGraphicsItem *currentItem;
    int m_currentAction;
    int m_previousAction;
    QPointF m_previousPosition;
    bool m_leftMouseButtonPressed;
};

#endif // WORKPLACE_H

veworkplace.cpp

Графикалық көрініспен жұмыс істеудің маңызды сәті мынада, бұл жағдайда барлық графикалық объектілер қазір қандай құралмен жұмыс істеп жатқанымызға байланысты жай ғана қажетті түрге шығарылады.

Жалпы, шынымды айтсам, бір жарым жылдан кейін басқаша жазар едім. Өйткені, QGraphicsItem нысанында type() виртуалды әдісі бар, қайта анықталған кезде нысанның түрін қайтаруға болады. Ал бұл, өз кезегінде, әртүрлі функцияларды орындау үшін бір ғана әдісті қолдану мүмкіндігін ашады. Мысалы, нысанды жылжыту немесе оның түсін өзгерту үшін және әртүрлі нысандар үшін. Сондықтан, сіз осы жобаны редакторыңызды әзірлеуге негіз ретінде қабылдаған кезде, осы сәтті ескеруге тырысыңыз. Графикалық нысандарға нақты нөмір түрін тағайындау әзірлеуді айтарлықтай жеңілдетеді.

Мысалы, егер біз барлық осы графикалық нысандар үшін түймені басып тұрып жылжыту әдісін алсақ, онда біз жалпы бір setEndPoint () әдісін жасай аламыз және соңғы нүктенің орнатылуын басқара отырып, санды айтарлықтай жеңілдетуге және азайтуға болады. мысалы, осы әдісте.

void VEWorkplace::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    switch (m_currentAction) {
    case LineType: {
        if (m_leftMouseButtonPressed) {
            VEPolyline * polyline = qgraphicsitem_cast<VEPolyline *>(currentItem);
            QPainterPath path;
            path.moveTo(m_previousPosition);
            path.lineTo(event->scenePos());
            polyline->setPath(path);
        }
        break;
    }
    case RectangleType: {
        if (m_leftMouseButtonPressed) {
            auto dx = event->scenePos().x() - m_previousPosition.x();
            auto dy = event->scenePos().y() - m_previousPosition.y();
            VERectangle * rectangle = qgraphicsitem_cast<VERectangle *>(currentItem);
            rectangle->setRect((dx > 0) ? m_previousPosition.x() : event->scenePos().x(),
                                   (dy > 0) ? m_previousPosition.y() : event->scenePos().y(),
                                   qAbs(dx), qAbs(dy));
        }
        break;
    }
    case SelectionType: {
        if (m_leftMouseButtonPressed) {
            auto dx = event->scenePos().x() - m_previousPosition.x();
            auto dy = event->scenePos().y() - m_previousPosition.y();
            VESelectionRect * selection = qgraphicsitem_cast<VESelectionRect *>(currentItem);
            selection->setRect((dx > 0) ? m_previousPosition.x() : event->scenePos().x(),
                                   (dy > 0) ? m_previousPosition.y() : event->scenePos().y(),
                                   qAbs(dx), qAbs(dy));
        }
        break;
    }
    default: {
        QGraphicsScene::mouseMoveEvent(event);
        break;
    }
    }
}

Сыныптың толық мәтіні

#include "veworkplace.h"
#include <QApplication>
#include <QGraphicsSceneMouseEvent>
#include <QKeyEvent>
#include <QDebug>
#include "verectangle.h"
#include "veselectionrect.h"
#include "vepolyline.h"

VEWorkplace::VEWorkplace(QObject *parent) :
    QGraphicsScene(parent),
    currentItem(nullptr),
    m_currentAction(DefaultType),
    m_previousAction(0),
    m_leftMouseButtonPressed(false)
{

}

VEWorkplace::~VEWorkplace()
{
    delete currentItem;
}

int VEWorkplace::currentAction() const
{
    return m_currentAction;
}

QPointF VEWorkplace::previousPosition() const
{
    return m_previousPosition;
}

void VEWorkplace::setCurrentAction(const int type)
{
    m_currentAction = type;
    emit currentActionChanged(m_currentAction);
}

void VEWorkplace::setPreviousPosition(const QPointF previousPosition)
{
    if (m_previousPosition == previousPosition)
        return;

    m_previousPosition = previousPosition;
    emit previousPositionChanged();
}

void VEWorkplace::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    if (event->button() & Qt::LeftButton) {
        m_leftMouseButtonPressed = true;
        setPreviousPosition(event->scenePos());
        if(QApplication::keyboardModifiers() & Qt::ShiftModifier){
            m_previousAction = m_currentAction;
            setCurrentAction(SelectionType);
        }
    }
    switch (m_currentAction) {
    case LineType: {
        if (m_leftMouseButtonPressed && !(event->button() & Qt::RightButton) && !(event->button() & Qt::MiddleButton)) {
            deselectItems();
            VEPolyline *polyline = new VEPolyline();
            currentItem = polyline;
            addItem(currentItem);
            connect(polyline, &VEPolyline::clicked, this, &VEWorkplace::signalSelectItem);
            connect(polyline, &VEPolyline::signalMove, this, &VEWorkplace::slotMove);
            QPainterPath path;
            path.moveTo(m_previousPosition);
            polyline->setPath(path);
            emit signalNewSelectItem(polyline);
        }
        break;
    }
    case RectangleType: {
        if (m_leftMouseButtonPressed && !(event->button() & Qt::RightButton) && !(event->button() & Qt::MiddleButton)) {
            deselectItems();
            VERectangle *rectangle = new VERectangle();
            currentItem = rectangle;
            addItem(currentItem);
            connect(rectangle, &VERectangle::clicked, this, &VEWorkplace::signalSelectItem);
            connect(rectangle, &VERectangle::signalMove, this, &VEWorkplace::slotMove);
            emit signalNewSelectItem(rectangle);
        }
        break;
    }
    case SelectionType: {
        if (m_leftMouseButtonPressed && !(event->button() & Qt::RightButton) && !(event->button() & Qt::MiddleButton)) {
            deselectItems();
            VESelectionRect *selection = new VESelectionRect();
            currentItem = selection;
            addItem(currentItem);
        }
        break;
    }
    default: {
        QGraphicsScene::mousePressEvent(event);
        break;
    }
    }
}

void VEWorkplace::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    switch (m_currentAction) {
    case LineType: {
        if (m_leftMouseButtonPressed) {
            VEPolyline * polyline = qgraphicsitem_cast<VEPolyline *>(currentItem);
            QPainterPath path;
            path.moveTo(m_previousPosition);
            path.lineTo(event->scenePos());
            polyline->setPath(path);
        }
        break;
    }
    case RectangleType: {
        if (m_leftMouseButtonPressed) {
            auto dx = event->scenePos().x() - m_previousPosition.x();
            auto dy = event->scenePos().y() - m_previousPosition.y();
            VERectangle * rectangle = qgraphicsitem_cast<VERectangle *>(currentItem);
            rectangle->setRect((dx > 0) ? m_previousPosition.x() : event->scenePos().x(),
                                   (dy > 0) ? m_previousPosition.y() : event->scenePos().y(),
                                   qAbs(dx), qAbs(dy));
        }
        break;
    }
    case SelectionType: {
        if (m_leftMouseButtonPressed) {
            auto dx = event->scenePos().x() - m_previousPosition.x();
            auto dy = event->scenePos().y() - m_previousPosition.y();
            VESelectionRect * selection = qgraphicsitem_cast<VESelectionRect *>(currentItem);
            selection->setRect((dx > 0) ? m_previousPosition.x() : event->scenePos().x(),
                                   (dy > 0) ? m_previousPosition.y() : event->scenePos().y(),
                                   qAbs(dx), qAbs(dy));
        }
        break;
    }
    default: {
        QGraphicsScene::mouseMoveEvent(event);
        break;
    }
    }
}

void VEWorkplace::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    if (event->button() & Qt::LeftButton) m_leftMouseButtonPressed = false;
    switch (m_currentAction) {
    case LineType:
    case RectangleType: {
        if (!m_leftMouseButtonPressed &&
                !(event->button() & Qt::RightButton) &&
                !(event->button() & Qt::MiddleButton)) {
            currentItem = nullptr;
        }
        break;
    }
    case SelectionType: {
        if (!m_leftMouseButtonPressed &&
                !(event->button() & Qt::RightButton) &&
                !(event->button() & Qt::MiddleButton)) {
            VESelectionRect * selection = qgraphicsitem_cast<VESelectionRect *>(currentItem);
            if(!selection->collidingItems().isEmpty()){
                foreach (QGraphicsItem *item, selection->collidingItems()) {
                    item->setSelected(true);
                }
            }
            selection->deleteLater();
            if(selectedItems().length() == 1){
                signalSelectItem(selectedItems().at(0));
            }
            setCurrentAction(m_previousAction);
            currentItem = nullptr;
        }
        break;
    }
    default: {
        QGraphicsScene::mouseReleaseEvent(event);
        break;
    }
    }
}

void VEWorkplace::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
    switch (m_currentAction) {
    case LineType:
    case RectangleType:
    case SelectionType:
        break;
    default:
        QGraphicsScene::mouseDoubleClickEvent(event);
        break;
    }
}

void VEWorkplace::keyPressEvent(QKeyEvent *event)
{
    switch (event->key()) {
    case Qt::Key_Delete: {
        foreach (QGraphicsItem *item, selectedItems()) {
            removeItem(item);
            delete item;
        }
        deselectItems();
        break;
    }
    case Qt::Key_A: {
        if(QApplication::keyboardModifiers() & Qt::ControlModifier){
            foreach (QGraphicsItem *item, items()) {
                item->setSelected(true);
            }
            if(selectedItems().length() == 1) signalSelectItem(selectedItems().at(0));
        }
        break;
    }
    default:
        break;
    }
    QGraphicsScene::keyPressEvent(event);
}

void VEWorkplace::deselectItems()
{
    foreach (QGraphicsItem *item, selectedItems()) {
        item->setSelected(false);
    }
    selectedItems().clear();
}

void VEWorkplace::slotMove(QGraphicsItem *signalOwner, qreal dx, qreal dy)
{
    foreach (QGraphicsItem *item, selectedItems()) {
        if(item != signalOwner) item->moveBy(dx,dy);
    }
}

Қорытынды

Осы кодқа қайта оралатын болсам, мен мұнда көп нәрсені қайта жазған шығармын, сонымен қатар бағдарламалауда өз ісіммен айналысар едім - "Кодты жою". Шынымды айтсам, бағдарламалаудағы менің сүйікті құралым Occam ұстарасы.

Бірақ бұрын айтқанымдай, мен бір жарым жыл бұрын осы жобаны әзірлеуге өз дағдыларыммен 36 сағат жұмыс уақытымды жұмсадым, мен жобаны қайта өңдеуге тағы 8-10 сағат жұмсай алмаймын, бірақ біз әрқашан жасай аламыз. сайт форумында сізбен даулы мәселелерді талқылаңыз.

Бұл жоба сондай-ақ қанша код жазу керектігін және тіпті шағын функционалдылықты жүзеге асыру үшін қанша уақыт қажет екенін көрсетеді. Ал жеке өзім, қазірде бірнеше мыңға жуық бастапқы код файлдары бар жобада жұмыс істеп жатырмын және жоба салынғаннан кейін құрастыру қалтасы 12 ГБ дейін өседі, менің ойымша, бұл жоба микроскопиялық. Дегенмен, тіпті мұндай функционалдылықты жүзеге асыруға көп уақыт жұмсауға болады. Сондықтан, бағдарламалық жасақтаманы әзірлеушінің маңызды дағдыларының бірі, мен сіздің мүмкіндіктеріңізді және болжалды мерзімдерді мұқият бағалау, сонымен қатар жұмыс процесінде ағымдағы тапсырмаңызға қатысы жоқ қосымша тапсырмалар немесе тапсырмалармен алаңдамау мүмкіндігін қарастырамын.

Жобаны жүктеп алу

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Ол саған ұнайды ма? Әлеуметтік желілерде бөлісіңіз!

c
  • Қаз. 22, 2017, 12:49 Т.Қ.

Really awesome tutorial sir, thank you very much :)

c
  • Қар. 5, 2017, 1:01 Т.Қ.

Sir could you please explain me,how can I design oval shape same as this rectangle design.what should I need to do ?

Evgenii Legotckoi
  • Қар. 6, 2017, 2:52 Т.Қ.

Try inherit your Oval Graphics Item from QGraphicsEllipseItem and QObject, and implement logic, which similar to logic for Rectangle.

c
  • Қар. 14, 2017, 4:33 Т.Қ.

Sir,In this form design how did you add verectanglesettings.ui,vepolylinesettings.ui UI's to this mainwindow.ui ? have any QT tool to add so. this image shows what I meaning.

Evgenii Legotckoi
  • Қар. 15, 2017, 2:56 Т.Ж.

You need add common QWidget to form, after that you need right-click on this QWidget in the form and select "Promote to..." in Context Menu. After that You will see dialog. In dialog choose Base class name, write promoted class name and header file. After that click add and promote

c
  • Қар. 15, 2017, 3:27 Т.Ж.

Thank you very much !!!

v
  • Қар. 22, 2017, 5:25 Т.Ж.

As far as I can see your classes inherit from both QObject and QGraphics*. QGraphics* are already QObjects and multiple inheritance from QObjects lead to some problems with moc.

Evgenii Legotckoi
  • Қар. 22, 2017, 5:30 Т.Ж.

If you see sources of class QGraphicsObject or if you see documentation of QGraphicsObject, then you will understand, that it is not problem in this case, because of QGraphicsRectItem inherits from QGraphicsItem , which has not inheritance from QObject. You have invalid information about inheritance of these classes.

c
  • Қар. 22, 2017, 3:33 Т.Қ.

In there you design rectangle using mouse,how to design a rectangle when button click...


I tried like this,

in mainwindow.h

 private:
 QGraphicsRectItem *rect;

mainwindow.cpp

void MainWindow::on_btnCreate_clicked()
{
    //VERectangle *rectA = new VERectangle();
   // rect->setRect(20,20,100,100);*/
    rect = workplaceScene->addRect(20,20,100,100);
}

In my code,rectangle is created,but which unable to resize.How can I create a rectangle with a re-size functionality when button click
c
  • Жел. 5, 2017, 9:28 Т.Ж.

Is this works only windows OS ?

Evgenii Legotckoi
  • Жел. 5, 2017, 3:45 Т.Қ.

It should work on Windows, Mac OS and Linux. Because it not contains platform-dependent components.

u
  • Сәуір 17, 2019, 10:15 Т.Қ.

доброго времени,
большое спасибо за пример для начинающего)
при адаптации к своему проекту столкнулся с таким ньансом:
в vepolyline.h в 47 строке нужна инициализация по умолчанию: int m_pointForCheck = -1;
у меня в проекте если нажать на дот, не двигать и отпустить, то в эту переменную попадает случайное число

Evgenii Legotckoi
  • Сәуір 21, 2019, 11:48 Т.Ж.

Добрый день! Спасибо за комментарий. Там действительно лучше будет сделать с инициализацией по умолчанию.

o
  • Қыр. 4, 2019, 3:54 Т.Ж.
  • (өңделген)

thanks for the application, it helps me a lot

Пікірлер

Тек рұқсаты бар пайдаланушылар ғана пікір қалдыра алады.
Кіріңіз немесе Тіркеліңіз
Г

C++ - Тест 001. Первая программа и типы данных

  • Нәтиже:66ұпай,
  • Бағалау ұпайлары-1
t

C++ - Тест 001. Первая программа и типы данных

  • Нәтиже:33ұпай,
  • Бағалау ұпайлары-10
t

Qt - Тест 001. Сигналы и слоты

  • Нәтиже:52ұпай,
  • Бағалау ұпайлары-4
Соңғы пікірлер
G
GoattRockҚыр. 3, 2024, 1:50 Т.Қ.
Linux жүйесінде файлдарды қалай көшіруге болады Задумывались когда-нибудь о том, как мы привыкли доверять свои вещи службам грузоперевозок? Сейчас такие услуги стали неотъемлемой частью нашей жизни, особенно когда речь идет о переездах между …
d
dblas5Шілде 5, 2024, 11:02 Т.Ж.
QML - Сабақ 016. SQLite деректер қоры және онымен QML Qt-та жұмыс істеу Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
k
kmssrАқп. 8, 2024, 6:43 Т.Қ.
Qt Linux - Сабақ 001. Linux астында Autorun Qt қолданбасы как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий КононенкоАқп. 5, 2024, 1:50 Т.Ж.
Qt WinAPI - Сабақ 007. Qt ішінде ICMP Ping арқылы жұмыс істеу Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
Енді форумда талқылаңыз
Evgenii Legotckoi
Evgenii LegotckoiМаусым 24, 2024, 3:11 Т.Қ.
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
F
FynjyШілде 22, 2024, 4:15 Т.Ж.
при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …
BlinCT
BlinCTМаусым 25, 2024, 1 Т.Ж.
Нарисовать кривую в qml Всем привет. Имеется Лист листов с тосками, точки получаны интерполяцией Лагранжа. Вопрос, как этими точками нарисовать кривую? ChartView отпадает сразу, в qt6.7 появился новый элемент…
BlinCT
BlinCTМамыр 5, 2024, 5:46 Т.Ж.
Написать свой GraphsView Всем привет. В Qt есть давольно старый обьект дял работы с графиками ChartsView и есть в 6.7 новый но очень сырой и со слабым функционалом GraphsView. По этой причине я хочу написать х…
Evgenii Legotckoi
Evgenii LegotckoiМамыр 2, 2024, 2:07 Т.Қ.
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Добрый день. По моему мнению - да, но то, что будет касаться вызовов к функционалу Андроида, может создать огромные трудности.

Бізді әлеуметтік желілерде бақылаңыз