Досить часто постають питання так чи інакше пов'язані з роботою графічної сцени, з кастомними фігурами, з малюванням ліній на графічній сцені, причому навіть усіляких ламаних ліній. І тут я згадав про один проект, який робив як тестове завдання.
А саме це був векторний редактор, який може:
- Створювати прямокутники
- Змінювати розмір цих прямокутників
- Крутити прямокутники навколо центру
- Робити заливку прямокутників
- Робити заливку прямокутників градієнтом
- Змінювати ширину абрису прямокутника
- Встановлювати колір абрису прямокутника
- Створювати лінії
- Задавати ширину і колір лінії, що створюється
- Робити ламані лінії з ліній по подвійному клацанню додаючи на лінії точки зламу
- Виділяти всі графічні об'єкти та перетягувати їх купкою
- Зберігати отримане зображення у SVG файл, а також відновлювати всі графічні об'єкти з цього файлу
На виконання цього проекту свого часу (1,5 роки тому) я витратив близько 36 годин робочого часу... зараз на це пішло менше часу.
Структура проекту
А тепер розбиратимемося з ключовими моментами в проекті. Почнемо із структури.
Як бачите, це найбільша структура проекту із усіх статей, представлених на сайті. Крім цих файлів є і файли графічного інтерфейсу, а іконки для кнопок.
Головне вікно
Головне вікно програми служить для завантаження та збереження 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);
dotsignal.h
#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; }
прямокутник.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); } }
Висновок
Повертаючись до даного коду, я мабуть переписав дуже багато тут, а також зайнявся б своєю будь-якою справою у програмуванні - "Видалення коду". Серйозно, мій улюблений інструмент у програмуванні - це бритва Оккама.
Але як я сказав раніше, я витратив 36 годин робочого часу на розробку цього проекту за своїх навичок півтора роки тому, і витрачати ще годин 8-10 на переробку проекту я просто не можу собі дозволити, але ми завжди можемо обговорити з вами спірні моменти на форумі сайту.
Цей проект також показує, скільки коду потрібно написати і скільки часу витратити, щоб реалізувати навіть невеликий функціонал. А особисто я, працюючи зараз над проектом у якому близько декількох тисяч файлів вихідних кодів, а білд папка розпухає після закінчення складання проекту до 12 Гб, вважаю на цей проект мікроскопічним. Проте навіть на реалізацію подібного функціоналу можна витратити досить великі обсяги часу. Тому однією з важливих навичок розробника програмного забезпечення я вважаю здатність тверезо оцінювати свої можливості та передбачувані терміни, а також у процесі роботи не відволікатися на побічні завдання або завдання, які не належать до вашого поточного завдання.
Really awesome tutorial sir, thank you very much :)
Sir could you please explain me,how can I design oval shape same as this rectangle design.what should I need to do ?
Try inherit your Oval Graphics Item from QGraphicsEllipseItem and QObject, and implement logic, which similar to logic for Rectangle.
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.
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
Thank you very much !!!
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.
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.
In there you design rectangle using mouse,how to design a rectangle when button click...
Is this works only windows OS ?
It should work on Windows, Mac OS and Linux. Because it not contains platform-dependent components.
доброго времени,
большое спасибо за пример для начинающего)
при адаптации к своему проекту столкнулся с таким ньансом:
в vepolyline.h в 47 строке нужна инициализация по умолчанию: int m_pointForCheck = -1;
у меня в проекте если нажать на дот, не двигать и отпустить, то в эту переменную попадает случайное число
Добрый день! Спасибо за комментарий. Там действительно лучше будет сделать с инициализацией по умолчанию.
thanks for the application, it helps me a lot