I have this program where i'm drawing a rectangular polygon which can be rotated, scaled and moved using a transform.
I've however bumped into a problem which i don't understand and although i would like to figure it out myself, I've come to realize that it's time to seek some help.
The problem:
when the polygon is rotated to certain angles above 45 degrees, dragging the top left anchor makes it go out of control, ie. it moves further away from the anchor until it goes completely haywire...
Without providing a direct answer to the problem, what kind of knowledge am I missing to solve this? I know the basics of matrices and how they work although I have a hard to visualizing it...
To me the calculations itself seem correct and scaling either X or Y independently works as expected but the combination does not.
To reproduce:
Hold Ctrl to rotate the polygon
Drag the top left corner
Here's a minimal example:
// project
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
SOURCES += \
main.cpp
HEADERS += mainwindow.h
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QMouseEvent>
#include <QTimer>
#include <QSlider>
#include <QtMath>
enum class MoveMode {
MIDDLE,
TOPLEFT,
TOPRIGHT,
BOTTOMLEFT,
BOTTOMRIGHT,
ROTATION,
SYMMETRY,
NONE
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow() override { };
void paintMe();
void updateMatrix();
void updateSelectionMatrix();
void updateTransformedRect();
void handleDragging();
void paintGrid(QPainter& painter, QTransform transform);
protected:
void paintEvent(QPaintEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
private:
QPointF getSelectionAnchorPoint(QVector<QPointF> selectionPolygon, MoveMode moveMode) const;
QRect mapRect(QRect rect, QTransform transform);
QRectF mSelection;
QPointF currentPoint;
QPointF anchorPoint;
QTransform viewTransform;
QTransform viewInverse;
bool isRotating = false;
bool mouseDragging = false;
bool isTranslating = false;
bool somethingDragged = false;
qreal mRotAngle = 0;
qreal mSelectionRot = 0;
qreal mPreviousAngle = 0;
qreal mScaleX;
qreal mScaleY;
QPointF mOffset;
MoveMode mMoveMode = MoveMode::NONE;
};
#endif // MAINWINDOW_H
// main.cpp
#include "mainwindow.h"
#include <QApplication>
#include <QMainWindow>
#include <QDebug>
#include <QtMath>
#include <QPainter>
#include <QHBoxLayout>
#include <QLine>
class SelectionManager : public QObject
{
public:
SelectionManager() {};
~SelectionManager() {};
const QPolygonF& mySelectionPolygon() { return mSelectionPolygon; }
MoveMode getMoveMode() const { return mMoveMode; }
void setMoveMode(MoveMode moveMode) { mMoveMode = moveMode; }
QPointF currentTransformAnchor() const { return mAnchorPoint; }
QTransform selectionTransform() const { return mSelectionTransform; }
QPointF mapToLocalSpace(QPointF point) const { return mSelectionTransform.map(point); };
QPointF mapFromLocalSpace(QPointF point) const { return mSelectionTransform.inverted().map(point); }
QPolygonF mapToLocalSpace(QPolygonF polygon) const { return mSelectionTransform.map(polygon); }
QPolygonF mapFromLocalSpace(QPolygonF polygon) const { return mSelectionTransform.inverted().map(polygon); }
void setMoveModeForAnchorInRange(QPointF point)
{
QPolygonF transformPoly = mapToLocalSpace(mSelectionPolygon);
const double calculatedSelectionTol = mSelectionTolerance;
MoveMode mode;
if (QLineF(point, transformPoly[0]).length() < calculatedSelectionTol)
{
mode = MoveMode::TOPLEFT;
}
else if (QLineF(point, transformPoly[1]).length() < calculatedSelectionTol)
{
mode = MoveMode::TOPRIGHT;
}
else if (QLineF(point, transformPoly[2]).length() < calculatedSelectionTol)
{
mode = MoveMode::BOTTOMRIGHT;
}
else if (QLineF(point, transformPoly[3]).length() < calculatedSelectionTol)
{
mode = MoveMode::BOTTOMLEFT;
}
else if (transformPoly.containsPoint(point, Qt::WindingFill))
{
mode = MoveMode::MIDDLE;
}
else {
mode = MoveMode::NONE;
}
mMoveMode = mode;
}
void adjustSelection(const QPointF& currentPoint, qreal offsetX, qreal offsetY, qreal rotationOffset)
{
QPointF offset(offsetX, offsetY);
MoveMode moveMode = mMoveMode;
switch (moveMode)
{
case MoveMode::MIDDLE: {
translate(currentPoint - offset);
break;
}
case MoveMode::TOPLEFT:
case MoveMode::TOPRIGHT:
case MoveMode::BOTTOMRIGHT:
case MoveMode::BOTTOMLEFT: {
QPolygonF fixedScalePolygon = anchorTransform().map(mSelectionPolygon);
qreal scaleX = 1;
qreal scaleY = 1;
if (moveMode == MoveMode::TOPLEFT) {
QPolygonF worldSelectionPolygon = mapToLocalSpace(mSelectionPolygon);
QLineF lineYCurrentPointFromBottomLeftAnchor(worldSelectionPolygon[3], currentPoint);
QLineF lineXCurrentPointFromTopRightAnchor(worldSelectionPolygon[1], currentPoint);
QLineF lineY(fixedScalePolygon[3], fixedScalePolygon[0]);
QLineF lineX(fixedScalePolygon[1], fixedScalePolygon[0]);
scaleY = lineYCurrentPointFromBottomLeftAnchor.dy() / lineY.dy();
scaleX = lineXCurrentPointFromTopRightAnchor.dx() / lineX.dx();
}
scale(scaleX, scaleY);
break;
}
case MoveMode::ROTATION: {
rotate(rotationOffset);
break;
}
default:
break;
}
calculateSelectionTransformation();
}
void translate(QPointF newPos)
{
mTranslation += newPos;
}
void rotate(qreal angle)
{
mRotatedAngle += angle;
}
void scale(qreal sX, qreal sY)
{
mScaleX = sX;
mScaleY = sY;
}
qreal angleFromPoint(QPointF point, QPointF anchorPoint) const
{
return qRadiansToDegrees(getDifferenceAngle(mSelectionTransform.map(anchorPoint), point));
}
void setSelection(QRectF rect)
{
mSelectionPolygon = rect;
mSomethingSelected = (rect.isValid() ? true : false);
mScaleX = 1;
mScaleY = 1;
calculateSelectionTransformation();
mClickTransform = mSelectionTransform;
}
void setTransformAnchor(QPointF point)
{
QPointF newPos = mapToLocalSpace(point);
QPointF oldPos = mapToLocalSpace(mAnchorPoint);
mTranslation = mTranslation - oldPos + newPos;
mAnchorPoint = point;
}
QTransform anchorTransform()
{
QPointF anchorPoint = mAnchorPoint;
QTransform t;
t.translate(-anchorPoint.x(), -anchorPoint.y());
QTransform t2;
t2.translate(mTranslation.x(), mTranslation.y());
QTransform r;
r.rotate(mRotatedAngle);
QTransform s;
s.scale(1, 1);
return (t * s * r * t2);
}
void calculateSelectionTransformation()
{
QPointF anchorPoint = mAnchorPoint;
QTransform t;
t.translate(-anchorPoint.x(), -anchorPoint.y());
QTransform t2;
t2.translate(mTranslation.x(), mTranslation.y());
QTransform r;
r.rotate(mRotatedAngle);
QTransform s;
s.scale(mScaleX, mScaleY);
mSelectionTransform = (t * s * r * t2);
}
qreal getDifferenceAngle(const QPointF a, const QPointF b) const
{
return qAtan2(b.y() - a.y(), b.x() - a.x());
}
private:
bool mSomethingSelected = false;
QPolygonF mSelectionPolygon;
QPointF mOffset;
qreal mScaleX;
qreal mScaleY;
QPointF mTranslation;
qreal mRotatedAngle = 0.0;
MoveMode mMoveMode = MoveMode::NONE;
QTransform mSelectionTransform;
QTransform mClickTransform;
const qreal mSelectionTolerance = 8.0;
QPointF mAnchorPoint;
};
SelectionManager selectMan;
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
selectMan.setSelection(QRectF(100,100, 300, 300));
mScaleX = 1;
mScaleY = 1;
updateMatrix();
setMouseTracking(true);
}
void MainWindow::mousePressEvent(QMouseEvent* event)
{
QPoint pos = event->pos();
QPolygonF selectionPoly = selectMan.mySelectionPolygon();
QPointF curPoint = pos;
selectMan.setMoveModeForAnchorInRange(pos);
QPointF anchor = getSelectionAnchorPoint(selectionPoly, selectMan.getMoveMode());
selectMan.setTransformAnchor(anchor);
QPointF anchorPoint = selectMan.currentTransformAnchor();
mRotAngle = selectMan.angleFromPoint(curPoint, anchorPoint) - mPreviousAngle;
currentPoint = pos;
mOffset = currentPoint;
update();
}
void MainWindow::mouseMoveEvent(QMouseEvent* event)
{
QTransform t = viewInverse;
if (event->buttons() & Qt::LeftButton) {
mouseDragging = true;
}
if (mouseDragging) {
if (event->modifiers() == Qt::CTRL) {
isRotating = true;
} else {
isRotating = false;
}
}
currentPoint = event->pos();
if (mouseDragging) {
handleDragging();
}
}
void MainWindow::handleDragging()
{
qreal newAngle = 0;
if (isRotating) {
selectMan.setMoveMode(MoveMode::ROTATION);
QPolygonF mSelectionPolygon = selectMan.mySelectionPolygon();
QPointF anchorPoint = selectMan.currentTransformAnchor();
newAngle = selectMan.angleFromPoint(currentPoint, anchorPoint) - mRotAngle;
}
selectMan.adjustSelection(currentPoint, mOffset.x(), mOffset.y(), newAngle - mPreviousAngle);
mPreviousAngle = newAngle;
mOffset = currentPoint;
update();
}
QPointF MainWindow::getSelectionAnchorPoint(QVector<QPointF> selectionPolygon, MoveMode moveMode) const
{
QPointF anchorPoint;
if (moveMode == MoveMode::BOTTOMRIGHT)
{
anchorPoint = selectionPolygon[0];
}
else if (moveMode == MoveMode::BOTTOMLEFT)
{
anchorPoint = selectionPolygon[1];
}
else if (moveMode == MoveMode::TOPLEFT)
{
anchorPoint = selectionPolygon[2];
}
else if (moveMode == MoveMode::TOPRIGHT)
{
anchorPoint = selectionPolygon[3];
} else {
anchorPoint = QLineF(selectionPolygon[0], selectionPolygon[2]).pointAt(0.5);
}
return anchorPoint;
}
void MainWindow::mouseReleaseEvent(QMouseEvent* event)
{
handleDragging();
currentPoint = event->pos();
mOffset = currentPoint;
isRotating = false;
mouseDragging = false;
somethingDragged = false;
}
void MainWindow::updateMatrix()
{
QTransform transform;
transform.translate(-this->width()/2,-this->height()/2);
viewTransform = transform;
viewInverse = viewTransform.inverted();
}
void MainWindow::paintEvent(QPaintEvent* )
{
QPainter painter(this);
painter.save();
QPen pen(QColor(180, 220, 255));
painter.setPen(pen);
QTransform selectionT = selectMan.selectionTransform();
painter.setPen(Qt::yellow);
QPolygonF mappedPol = selectionT.map(selectMan.mySelectionPolygon());
painter.drawEllipse(QRectF(currentPoint,QSize(10,10)));
painter.setPen(Qt::blue);
painter.drawPolygon((selectMan.mySelectionPolygon()));
painter.setPen(Qt::red);
painter.drawPolygon(mappedPol);
painter.setPen(QColor(255,100,255));
for (int corner = 0; corner < mappedPol.count()-1; corner++) {
QRectF cornerRect(QPointF(mappedPol[corner]-QPointF(10,10)),QSizeF(20,20));
if (corner == 0) {
painter.setBrush(Qt::yellow);
} else if (corner == 1) {
painter.setBrush(Qt::green);
} else if (corner == 2) {
painter.setBrush(Qt::blue);
} else if (corner == 3) {
painter.setBrush(Qt::red);
}
painter.drawRect(cornerRect);
}
QPolygonF localPolygon = selectMan.mySelectionPolygon();
painter.save();
painter.setBrush(Qt::NoBrush);
for (int corner = 0; corner < localPolygon.count()-1; corner++) {
QRectF cornerRect(QPointF(localPolygon[corner]-QPointF(10,10)),QSizeF(20,20));
if (corner == 0) {
painter.setPen(Qt::yellow);
} else if (corner == 1) {
painter.setPen(Qt::green);
} else if (corner == 2) {
painter.setPen(Qt::blue);
} else if (corner == 3) {
painter.setPen(Qt::red);
}
painter.drawRect(cornerRect);
}
painter.restore();
paintGrid(painter, selectionT);
paintGrid(painter, QTransform());
painter.restore();
}
void MainWindow::paintGrid(QPainter& painter, QTransform transform) {
int left = this->rect().left();
int right = this->rect().right();
int top = this->rect().top();
int bottom = this->rect().bottom();
QPen pen(Qt::lightGray);
pen.setCosmetic(true);
painter.setPen(pen);
painter.setOpacity(0.5);
painter.setBrush(Qt::NoBrush);
painter.setRenderHint(QPainter::Antialiasing, false);
painter.setTransform(transform);
for (int x = left; x < right; x += 20) {
painter.drawLine(x, top, x, bottom);
}
for (int y = top; y < bottom; y += 20) {
painter.drawLine(left, y, right, y);
}
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.resize(500, 500);
w.show();
return a.exec();
}
Solved the problem!
I was missing some fundamental knowledge about calculating the distance between two points. Found a nice recap of how to do that by using the dot product.
Normally calculating the distance between two points will always result in a positive value but i also needed to be able to scale into negative coordinates, thus what i was looking for was the signed distance.
Here's how i solved it:
Given we define the following
a = currentPos
b = bottomRightAnchor
c = topLeftAnchor
First get the direction the vector of the current position and the bottomRightAnchor, we subtract the two vectors
abDir = (a - b)
Next we need to get the normalized direction between the topLeftAnchor and the bottomRightAnchor, which will give us the direction ranging from -1 to 1
cbDir = (c - b)
normCbDir = sqrt((cbDir.x * cbDir.x) + (cbDir.y * cbDir.y))
We can now take the dot product of those vectors which will result in the signed distance.
distance = dot(abDir, normCbDir)
Thus we now have the signed distance from the current position to the bottom right corner.
The code is mostly the same, I only had to rework the logic inside the adjustSelection function.
...
QPolygonF projectedPolygon = mapToTransform(mSelectionPolygon);
QVector2D pointVec = QVector2D(currentPoint);
QVector2D movingAnchor = QVector2D(projectedPolygon[0]);
QVector2D staticAnchor = QVector2D(projectedPolygon[1]);
QVector2D directionVecX = staticAnchor - pointVec;
// Calculates the signed distance
qreal distanceX = QVector2D::dotProduct(directionVecX, (staticAnchor - movingAnchor).normalized());
staticAnchor = QVector2D(projectedPolygon[3]);
QVector2D directionVecY = staticAnchor - pointVec;
qreal distanceY = QVector2D::dotProduct(directionVecY, (staticAnchor - movingAnchor).normalized());
qreal originWidth = mSelectionPolygon[1].x() - mSelectionPolygon[0].x();
qreal originHeight = mSelectionPolygon[3].y() - mSelectionPolygon[0].y();
scaleX = distanceX / originWidth;
scaleY = distanceY / originHeight;
...
Related
I have a Player class that inherits from RectangleShape and I have created a function to move my player left and right on the A & D keys, but when enabling and disabling VSync I find that the movement speeds up rapidly, my guess is that my delta time implementation is incorrect but it seems to be the same as most tutorials online:
class Player : public sf::RectangleShape {
private:
sf::Vector2f velocity;
sf::Vector2f acceleration;
const float maxSpeed = 20.f;
public:
Player(sf::Vector2f size) : sf::RectangleShape(size) {
velocity = sf::Vector2f(0.f, 0.f);
acceleration = sf::Vector2f(0.05f, 27.f);
}
void Move(int& dt) {
if (Keyboard::isKeyPressed(Keyboard::D)) {
if (velocity.x < maxSpeed) {
velocity.x += acceleration.x * dt;
}
}
else if (Keyboard::isKeyPressed(Keyboard::A)) {
if (velocity.x > -maxSpeed) {
velocity.x -= acceleration.x * dt;
}
}
else if (velocity.x < 0.2f && velocity.x > - 0.2f) {
velocity.x = 0;
}
else {
if (velocity.x > 0.f) {
velocity.x -= acceleration.x * dt;
}
else {
velocity.x += acceleration.x * dt;
}
}
}
This is my main loop (stripped down)(I also have a custom Window class):
sf::Clock dtClock;
int dt;
int main() {
Window win("Test Window", sf::VideoMode::getDesktopMode().width, sf::VideoMode::getDesktopMode().height, false);
Player player(sf::Vector2f(50.f, 50.f));
player.setFillColor(sf::Color::Blue);
player.move(sf::Vector2f(200.f, 150.f));
sf::Clock clock;
float lastTime = 0;
while (win.IsOpen()) {
dt = dtClock.restart().asMilliseconds();
player.Move(dt);
win.SetCenter(player.getPosition());
float currentTime = clock.restart().asSeconds();
short int fps = 1 / currentTime;
lastTime = currentTime;
win.Clear();
win.Draw(player);
win.Disp(fps);
}
}
EDIT: here is the minimal reproducible code:
#include <string>
#define BAR_SIZE 16.f
//this is where scale will be 1:1
#define STANDARD_WIDTH 1920
sf::Font firaCode;
enum class RenderObjectType {
Rectangle = 0,
Circle,
Sprite,
count
};
typedef RenderObjectType TROT;
struct TestRenderObject { //node
RenderObjectType type;
void* val;
TestRenderObject(void* val, RenderObjectType type) { this->val = val; this->type = type; }
};
typedef TestRenderObject TRO;
class Window {
private:
std::string title;
sf::RenderWindow win;
sf::View view;
sf::View staticView; //for use with GUI and cinematic bars
sf::Event evnt;
sf::Color background;
bool vsyncEnabled = true;
public:
Window(std::string name = "", unsigned pWidth = 1920, unsigned pHeight = 1080, bool fullscreen = false, sf::Color backgroundColour = sf::Color(135, 206, 235)) {
this->title = name;
this->win.create(sf::VideoMode(pWidth, pHeight), name, (fullscreen ? sf::Style::Fullscreen : sf::Style::Default));
this->win.setVerticalSyncEnabled(this->vsyncEnabled);
this->view.setSize(sf::Vector2f(pWidth, pHeight));
this->view.setCenter(pWidth / 2, pHeight / 2);
this->staticView.setSize(sf::Vector2f(pWidth, pHeight));
this->staticView.setCenter(pWidth / 2, pHeight / 2);
this->background = backgroundColour;
}
inline void toggleVSync() {
this->vsyncEnabled = !this->vsyncEnabled;
this->win.setVerticalSyncEnabled(this->vsyncEnabled);
}
inline void Clear() {
this->win.clear(background);
this->win.setView(this->view);
}
inline void Draw(sf::RectangleShape& r) {
this->win.draw(r);
}
inline void Draw(sf::CircleShape& c) {
this->win.draw(c);
}
inline void Draw(sf::Sprite& s) {
this->win.draw(s);
}
inline void Draw(std::initializer_list<TestRenderObject>& renderObjects) { //multiple types for debugging
for (auto& ele : renderObjects) {
switch (ele.type)
{
case(RenderObjectType::Rectangle):
this->Draw(*((sf::RectangleShape*)ele.val));
break;
case(RenderObjectType::Circle):
this->Draw(*((sf::CircleShape*)ele.val));
break;
case(RenderObjectType::Sprite):
this->Draw(*((sf::Sprite*)ele.val));
break;
default:
break;
}
}
}
inline void Draw(std::initializer_list<sf::Sprite> renderObjects) {
for (auto& ele : renderObjects)
this->win.draw(ele);
}
inline void Disp() {
this->win.setView(this->staticView); //UI elements
this->win.setView(this->view); //game elements
this->win.display();
}
inline bool IsOpen() {
return this->win.isOpen();
}
inline bool PollEvnt() {
return this->win.pollEvent(this->evnt);
}
inline sf::Event& GetEvnt() {
return this->evnt;
}
inline sf::Vector2f GetPosition() {
return sf::Vector2f(this->win.getPosition().x, this->win.getPosition().y);
}
inline void Shift(sf::Vector2f amount) {
this->view.move(amount);
} inline void Move(sf::Vector2f amount) { this->Shift(amount); }
inline void SetCenter(sf::Vector2f position) {
this->view.setCenter(position);
}
inline void Rotate(float amount) {
this->view.rotate(amount);
}
inline void Zoom(float amount) {
this->view.zoom(amount);
}
inline void ResetRotation() {
this->view.setRotation(0);
}
inline void ResetZoom() {
this->view.setSize(sf::Vector2f(this->win.getSize()));
} inline void ResetScale() { this->ResetZoom(); }
inline void ResetViewRatio() {
this->ResetZoom();
}
void Close() {
this->win.close();
}
};
typedef Window Win;
using Keyboard = sf::Keyboard;
using WinEvnt = sf::Event;
const float gravity{ 1.f };
class Player : public sf::RectangleShape {
private:
sf::Vector2f velocity;
sf::Vector2f acceleration;
const float maxSpeed = 20.f;
const float maxFallSpeed = 30.f;
public:
Player(sf::Vector2f size) : sf::RectangleShape(size) {
velocity = sf::Vector2f(0.f, 0.f);
acceleration = sf::Vector2f(0.05f, 27.f);
}
void Move(const int& dt) {
if (Keyboard::isKeyPressed(Keyboard::D)) {
if (velocity.x < maxSpeed) {
velocity.x += acceleration.x * dt;
}
}
else if (Keyboard::isKeyPressed(Keyboard::A)) {
if (velocity.x > -maxSpeed) {
velocity.x -= acceleration.x * dt;
}
}
else if (velocity.x < 0.2f && velocity.x > -0.2f) {
velocity.x = 0;
}
else {
if (velocity.x > 0.f) {
velocity.x -= acceleration.x * dt;
}
else {
velocity.x += acceleration.x * dt;
}
}
if (this->getPosition().y >= 500) {
velocity.y = 0;
if (this->getPosition().y >= 500)
this->setPosition(this->getPosition().x, 500);
if (Keyboard::isKeyPressed(Keyboard::Space) || Keyboard::isKeyPressed(Keyboard::W)) {
velocity.y -= acceleration.y;
}
}
else {
if (velocity.y < maxFallSpeed)
velocity.y += gravity;
}
this->move(velocity);
}
};
sf::Clock dtClock;
int dt;
int main() {
Window win("Test Window", sf::VideoMode::getDesktopMode().width, sf::VideoMode::getDesktopMode().height, false);
sf::CircleShape c(100.f);
c.setFillColor(sf::Color::Green);
Player player(sf::Vector2f(50.f, 50.f));
player.setFillColor(sf::Color::Blue);
player.move(sf::Vector2f(200.f, 150.f));
sf::RectangleShape rect(sf::Vector2f(100.f, 50.f));
rect.setFillColor(sf::Color::Magenta);
rect.move(sf::Vector2f(600.f, 200.f));
sf::CircleShape circle(200.f);
circle.setFillColor(sf::Color::Yellow);
circle.setOutlineThickness(-15);
circle.setOutlineColor(sf::Color(255, 99, 200));
circle.move(sf::Vector2f(1000.f, 1000.f));
sf::RectangleShape big(sf::Vector2f(6000.f, 3000.f));
big.setFillColor(sf::Color::Red);
big.move(sf::Vector2f(900.f, 600.f));
std::initializer_list<TestRenderObject> objects =
{
TRO((void*)&c, TROT::Circle),
TRO((void*)&rect, TROT::Rectangle),
TRO((void*)&big, TROT::Rectangle),
TRO((void*)&circle, TROT::Circle)
};
sf::Clock clock;
float lastTime = 0;
while (win.IsOpen()) {
while (win.PollEvnt()) {
if (win.GetEvnt().type == WinEvnt::KeyReleased && win.GetEvnt().key.code == Keyboard::V) {
win.toggleVSync();
}
else if (win.GetEvnt().type == sf::Event::Resized) {
win.ResetViewRatio();
}
else if (win.GetEvnt().type == WinEvnt::Closed) {
win.Close();
}
}
dt = dtClock.restart().asMilliseconds();
player.Move(dt);
win.SetCenter(player.getPosition());
float currentTime = clock.restart().asSeconds();
short int fps = 1 / currentTime;
lastTime = currentTime;
win.Clear();
win.Draw(objects);
win.Draw(player);
win.Disp();
}
}
With basic drawing code and modern GPUs you can easily get up to 2-3k frames per second, which can then turn into a frame time of 1f / 3000fps = 0.00033s which is less than one millisecond, thus when you call asMilliseconds() you may end up with 0.
An easy workaround here is to always work with seconds (asSeconds()) and floats, that way you should never hit 0. Keep in mind though, that you should always set a limit, either by enabling VSync or calling setFramerateLimit().
As for a design point of view, I recommend to not derive from sf::RectangleShape and instead use composition over inheritance. You can then derive from the more interface-like classes such as `sf
How can we draw axis tick marks in both inward and outwards? I reimplemented QwtScaleDraw and overrode with drawTick but I don't know how to match the tick position and draw additional line using
QwtPainter::drawLine(painter,QPointF,QPointF)
I tried :
inside Plot::drawItems(QPainter *painter, const QRectF &rect, const QwtScaleMap map[axisCnt]) const
const QwtScaleMap ↦
for (j = 0; j < majTicks; j++)
{
y = map.transform(majTickList[j]);
QwtPainter::drawLine(painter, x, y, x + m_majTickLength, y);
}
but the axis margin is not matching with the corners of the out axis, small deviation is coming. I took a screenshot here :
my complete drawInward
void CustomScaleDraw::draw(QPainter *painter, const QPalette &palette) const
{
QwtScaleDraw::draw(painter, palette);
painter->save();
QPen pen = painter->pen();
pen.setColor(palette.color(QPalette::Foreground));
painter->setPen(pen);
int majLen = m_pPlotWidget->majorTickLength();
if (m_majTickStyle >= Both && majLen > 0){
QList<double> ticks = this->scaleDiv().ticks(QwtScaleDiv::MajorTick);
for (int i = 0; i < (int)ticks.count(); i++){
const double v = ticks[i];
if (this->scaleDiv().contains(v))
drawInwardTick(painter, v, majLen);
}
}
}
and
void CustomScaleDraw::drawInwardTick(QPainter *painter, double value, int len) const
{
int pw2 = qMin((int)painter->pen().width(), len) / 2;
QwtScaleMap scaleMap = this->scaleMap();
QPointF pos = this->pos();
int majLen = tickLength(QwtScaleDiv::MajorTick);
const int clw = m_pPlotWidget->lineWidth();
const int tval = scaleMap.transform(value);
bool draw = false;
if ( orientation() == Qt::Vertical ){
int low = (int)scaleMap.p2() + majLen;
int high = (int)scaleMap.p1() - majLen;
if ((tval > low && tval < high) ||
(tval > high && !m_pPlotWidget->axisEnabled (QwtPlot::xBottom) && !clw) ||
(tval < low && !m_pPlotWidget->axisEnabled(QwtPlot::xTop) && !clw)) draw = true;
} else {
int low = (int)scaleMap.p1() + majLen;
int high = (int)scaleMap.p2() - majLen;
if ((tval > low && tval < high) ||
(tval > high && !m_pPlotWidget->axisEnabled(QwtPlot::yRight) && !clw) ||
(tval < low && !m_pPlotWidget->axisEnabled(QwtPlot::yLeft) && !clw)) draw = true;
}
if (draw){
switch(alignment()){
case LeftScale:
{
QwtPainter::drawLine(painter, pos.x() + pw2, tval, pos.x() + len, tval);
break;
}
case RightScale:
{
QwtPainter::drawLine(painter, pos.x() - pw2, tval, pos.x() - len, tval);
break;
}
case BottomScale:
{
QwtPainter::drawLine(painter, tval, pos.y() - pw2, tval, pos.y() - len);
break;
}
case TopScale:
{
QwtPainter::drawLine(painter, tval, pos.y() + pw2, tval, pos.y() + len);
break;
}
}
}
// QwtPainter::setMetricsMap(metricsMap); // restore metrics map
}
my scale setup in QwtPlot.cpp
for (int i = 0; i < QwtPlot::axisCnt; i++)
{
QwtScaleWidget *scale = (QwtScaleWidget *) axisWidget(i);
if(scale)
{
scale->setMargin(0);
//the axis title color must be initialized...
QwtText title = scale->title();
title.setColor(Qt::black);
scale->setTitle(title);
//...same for axis color
QPalette pal = scale->palette();
pal.setColor(QPalette::Foreground, QColor(Qt::black));
scale->setPalette(pal);
CustomScaleDraw *sd = new CustomScaleDraw(this);
sd->setTickLength(QwtScaleDiv::MinorTick, m_minTickLength);
sd->setTickLength(QwtScaleDiv::MediumTick, m_minTickLength);
sd->setTickLength(QwtScaleDiv::MajorTick, m_majTickLength);
setAxisScaleDraw(i,sd);
}
}
plotLayout()->setAlignCanvasToScales( true );
m_minTickLength = 5;
m_majTickLength = 9;
You can achieve the effect of inward-pointed ticks by adding additional scale items to the plot, which have the opposite alignment property to what their position would normally require.
This requires way less code than a custom painter, and you don't have the alignment issues.
Output
Code
#include <qapplication.h>
#include <qwt_plot.h>
#include <qwt_plot_curve.h>
#include <qwt_plot_grid.h>
#include <qwt_plot_layout.h>
#include <qwt_symbol.h>
#include <qwt_legend.h>
#include <qwt_scale_widget.h>
#include <qwt_plot_scaleitem.h>
int main( int argc, char **argv )
{
// Enable high-DPI scaling with Qt 5.6+
#if (QT_VERSION >= QT_VERSION_CHECK(5,6,0))
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QApplication a( argc, argv );
QwtPlot plot;
plot.setTitle( "Plot Demo" );
plot.canvas()->setContentsMargins(0, 0, 0, 0);
plot.setStyleSheet("QwtPlot{ border: 0; }");
plot.canvas()->setStyleSheet("QwtPlotCanvas {border: none; margin: 1; background-color: white;}");
plot.plotLayout()->setCanvasMargin(0);
plot.enableAxis(QwtPlot::yLeft);
plot.enableAxis(QwtPlot::yRight);
plot.enableAxis(QwtPlot::xBottom);
plot.enableAxis(QwtPlot::xTop);
plot.setAxisScale( QwtPlot::yLeft, 0.0, 1000.0 );
plot.setAxisScale(QwtPlot::yRight, 0.0, 1000.0);
plot.setAxisScale(QwtPlot::xBottom, 0.0, 1000.0);
plot.setAxisScale(QwtPlot::xTop, 0.0, 1000.0);
plot.axisWidget(QwtPlot::yLeft)->setMargin(0);
plot.axisWidget(QwtPlot::yLeft)->setSpacing(0);
plot.axisWidget(QwtPlot::yRight)->setMargin(0);
plot.axisWidget(QwtPlot::xBottom)->setMargin(0);
plot.axisWidget(QwtPlot::xTop)->setMargin(0);
// create inward pointing ticks, and disable their labels
// notice the alignment is *opposite* to the position.
// in production code, don't hardcode the positions obviously.
QwtPlotScaleItem *yLeftScaleItem = new QwtPlotScaleItem(QwtScaleDraw::RightScale, 0);
yLeftScaleItem->scaleDraw()->enableComponent(QwtAbstractScaleDraw::Labels, false);
yLeftScaleItem->attach(&plot);
QwtPlotScaleItem *yRightScaleItem = new QwtPlotScaleItem(QwtScaleDraw::LeftScale, 1000);
yRightScaleItem->scaleDraw()->enableComponent(QwtAbstractScaleDraw::Labels, false);
yRightScaleItem->attach(&plot);
QwtPlotScaleItem *xBottomScaleItem = new QwtPlotScaleItem(QwtScaleDraw::TopScale, 0);
xBottomScaleItem->scaleDraw()->enableComponent(QwtAbstractScaleDraw::Labels, false);
xBottomScaleItem->attach(&plot);
QwtPlotScaleItem *xTopScaleItem = new QwtPlotScaleItem(QwtScaleDraw::BottomScale, 1000);
xTopScaleItem->scaleDraw()->enableComponent(QwtAbstractScaleDraw::Labels, false);
xTopScaleItem->attach(&plot);
plot.updateCanvasMargins();
plot.resize( 500, 400 );
plot.show();
return a.exec();
}
I want to implement arc in QGraphicsScene. I want that on clicking of three points my arc should be drawn such that on clicking of three points arc is drawn where first point will be starting of arc, second will be any point on arc and third will be end point of arc. I have tried studing drawArc function but got confused with startangle and spanangle. I was unable to set them dynamically. Please suggest me some way to proceed.
I tried the solution to embend it in my project but got the following error:
error: cannot allocate an object of abstract type 'arc'
arcItem = new arc(++id, startP, midP, endP);
Can you please help me out to solve the problem. I am giving a part of code to my project.
In mousepress event of cadgraphicsscene I have done following thing.
cadgraphicsscene.cpp
void CadGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
// mousePressEvent in the graphicsScene
if(mouseEvent->button() == Qt::LeftButton)
{
switch (entityMode)
{
case ArcMode:
if (mFirstClick)
{
startP = mouseEvent->scenePos();
mFirstClick = false;
mSecondClick = true;
}
else if (!mFirstClick && mSecondClick)
{
midP = mouseEvent->scenePos();
mFirstClick = false;
mSecondClick = false;
mThirdClick = true;
}
else if (!mSecondClick && mThirdClick)
{
endP = mouseEvent->scenePos();
mThirdClick = false;
mPaintFlag = true;
}
if (mPaintFlag)
{
arcItem = new arc(++id, startP, midP, endP);
itemList.append(arcItem);
mUndoStack->push(new CadCommandAdd(this, arcItem));
setFlags();
}
}
}
}
arc.cpp
#include "arc.h"
arc::arc(int i, QPointF point1, QPointF point2, QPointF point3)
{
// assigns id
id = i;
p1 = point1;
p2 = point2;
p3 = point3;
lineBC(point2, point3);
lineAC(point1, point3);
lineBA(point2, point1);
rad = qAbs(lineBC.length()/(2*qSin(qDegreesToRadians(lineAC.angleTo(lineBA)))));
bisectorBC(lineBC.pointAt(0.5), lineBC.p2());
bisectorBC.setAngle(lineBC.normalVector().angle());
bisectorBA(lineBA.pointAt(0.5), lineBA.p2());
bisectorBA.setAngle(lineBA.normalVector().angle());
bisectorBA.intersect(bisectorBC, ¢er);
ellipse = new QGraphicsEllipseItem(center.x() - rad, center.y() - rad, rad*2, rad*2);
lineOA(center, point1);
lineOC(center, point3);
}
arc::arc(int i, QLineF start, QLineF end)
{
// assigns id
id = i;
lineOA.angle() = start;
lineOC.angle() - lineOA.angle() = end;
}
int arc::type() const
{
// Enable the use of qgraphicsitem_cast with arc item.
return Type;
}
void arc::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
QPen paintpen;
painter->setRenderHint(QPainter::Antialiasing);
paintpen.setWidth(1);
if (isSelected())
{
// sets brush for end points
painter->setBrush(Qt::SolidPattern);
paintpen.setColor(Qt::red);
painter->setPen(paintpen);
paintpen.setStyle(Qt::DashLine);
paintpen.setColor(Qt::black);
painter->setPen(paintpen);
painter->drawArc(ellipse->boundingRect(),lineOA.angle(),lineOC.angle() - lineOA.angle());
}
else
{
painter->setBrush(Qt::SolidPattern);
paintpen.setColor(Qt::black);
painter->setPen(paintpen);
painter->drawArc(ellipse->boundingRect(),lineOA.angle(),lineOC.angle() - lineOA.angle());
}
}
arc.h
include <QGraphicsItem>
#include "qmath.h"
class arc : public QObject, public QGraphicsItem
{
Q_OBJECT
public:
arc(int, QPointF, QPointF, QPointF);
arc(int, QLineF, QLineF);
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget);
enum { Type = UserType + 6 };
int type() const;
int id;
QPointF startP, midP, endP, p1, p2, p3,center;
QLineF lineBC;
QLineF lineAC;
QLineF lineBA;
QLineF lineOA;
QLineF lineOC;
QLineF bisectorBC;
QLineF bisectorBA;
QGraphicsEllipseItem *ellipse;
qreal rad;
private:
QVector<QPointF> stuff;
};
#endif // ARC_H
Please help me out to solve the error.
This sounds like it could be solved with some relatively simple math:
https://www.google.com/search?q=define%20circle%20three%20points
https://math.stackexchange.com/a/213678
https://www.khanacademy.org/math/geometry/triangle-properties/perpendicular_bisectors/v/three-points-defining-a-circle
Here is my translation of the math into Qt goodness
// m_points is a QList<QPointF>
// use math to define the circle
QLineF lineBC(m_points.at(1), m_points.at(2));
QLineF lineAC(m_points.at(0), m_points.at(2));
QLineF lineBA(m_points.at(1), m_points.at(0));
qreal rad = qAbs(lineBC.length()/(2*qSin(qDegreesToRadians(lineAC.angleTo(lineBA)))));
QLineF bisectorBC(lineBC.pointAt(0.5), lineBC.p2());
bisectorBC.setAngle(lineBC.normalVector().angle());
QLineF bisectorBA(lineBA.pointAt(0.5), lineBA.p2());
bisectorBA.setAngle(lineBA.normalVector().angle());
QPointF center;
bisectorBA.intersect(bisectorBC, ¢er);
qDebug() << rad << center;
QT QGraphicsScene Drawing Arc
QPainterPath* path = new QPainterPath();
path->arcMoveTo(0,0,50,50,20);
path->arcTo(0,0,50,50,20, 90);
scene.addPath(*path);
Putting all of this together into a nice little project turns into this:
https://github.com/peteristhegreat/ThreePointsCircle
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include <QGraphicsView>
#include <QGraphicsScene>
#include "graphicsscene.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
QGraphicsView * view = new QGraphicsView;
GraphicsScene * scene = new GraphicsScene();
view->setScene(scene);
view->setSceneRect(-300,-300, 300, 300);
this->resize(600, 600);
this->setCentralWidget(view);
}
MainWindow::~MainWindow()
{
}
graphicsscene.h
#ifndef GRAPHICSSCENE_H
#define GRAPHICSSCENE_H
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QPointF>
#include <QList>
class GraphicsScene : public QGraphicsScene
{
Q_OBJECT
public:
explicit GraphicsScene(QObject *parent = 0);
virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent * mouseEvent);
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * mouseEvent);
virtual void mousePressEvent(QGraphicsSceneMouseEvent * mouseEvent);
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * mouseEvent);
signals:
public slots:
private:
QList <QPointF> m_points;
};
#endif // GRAPHICSSCENE_H
graphicsscene.cpp
#include "graphicsscene.h"
#include <QDebug>
#include <QGraphicsEllipseItem>
#include <QGraphicsPathItem>
#include <QPainterPath>
#include "qmath.h"
GraphicsScene::GraphicsScene(QObject *parent) :
QGraphicsScene(parent)
{
this->setBackgroundBrush(Qt::gray);
}
void GraphicsScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent * mouseEvent)
{
qDebug() << Q_FUNC_INFO << mouseEvent->scenePos();
QGraphicsScene::mouseDoubleClickEvent(mouseEvent);
}
void GraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent * mouseEvent)
{
qDebug() << Q_FUNC_INFO << mouseEvent->scenePos();
QGraphicsScene::mouseMoveEvent(mouseEvent);
}
void GraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent * mouseEvent)
{
qDebug() << Q_FUNC_INFO << mouseEvent->scenePos();
QGraphicsScene::mousePressEvent(mouseEvent);
}
void GraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent * me)
{
qDebug() << Q_FUNC_INFO << me->scenePos();
int radius = 20;
QGraphicsEllipseItem * ellipse = this->addEllipse(me->scenePos().x() - radius, me->scenePos().y() - radius, radius*2, radius*2);
ellipse->setBrush(Qt::white);
m_points.append(me->scenePos());
if(m_points.size() == 3)
{
// use math to define the circle
QLineF lineBC(m_points.at(1), m_points.at(2));
QLineF lineAC(m_points.at(0), m_points.at(2));
QLineF lineBA(m_points.at(1), m_points.at(0));
qreal rad = qAbs(lineBC.length()/(2*qSin(qDegreesToRadians(lineAC.angleTo(lineBA)))));
QLineF bisectorBC(lineBC.pointAt(0.5), lineBC.p2());
bisectorBC.setAngle(lineBC.normalVector().angle());
QLineF bisectorBA(lineBA.pointAt(0.5), lineBA.p2());
bisectorBA.setAngle(lineBA.normalVector().angle());
QPointF center;
bisectorBA.intersect(bisectorBC, ¢er);
qDebug() << rad << center;
bool drawCircle = true;
QGraphicsEllipseItem * ellipse = new QGraphicsEllipseItem(center.x() - rad, center.y() - rad, rad*2, rad*2);
if(drawCircle)
this->addItem(ellipse);
// add arc
// this->addItem(path);
QPainterPath path;
QLineF lineOA(center, m_points.at(0));
QLineF lineOC(center, m_points.at(2));
path.arcMoveTo(ellipse->boundingRect(),lineOA.angle());
path.arcTo(ellipse->boundingRect(), lineOA.angle(), lineOC.angle() - lineOA.angle());
QGraphicsPathItem * pathItem = new QGraphicsPathItem(path);
pathItem->setPen(QPen(Qt::red,10));
this->addItem(pathItem);
if(!drawCircle)
delete ellipse;
m_points.clear();
}
QGraphicsScene::mouseReleaseEvent(me);
}
A Subclass of QGraphicsItem, that takes 3 points, and intersects the three with an arc of a circle. The second point is always in the middle. (Selectablity and other properties haven't been fully implemented or tested).
Note: Qt Creator includes more advanced examples of subclassed QGraphicsItems such as Colliding Mice, and 40,000 chips examples.
http://qt-project.org/doc/qt-5/examples-graphicsview.html
Also to enable QObject signals and slots and properties from a QGraphicsItem, you should use QGraphicsObject.
Note: added onto github here.
arcgraphicsitem.h
#ifndef ARCGRAPHICSITEM_H
#define ARCGRAPHICSITEM_H
#include <QGraphicsItem>
#include <QLineF>
#include <QPointF>
#include <QPainter>
#include <QStyleOptionGraphicsItem>
#include <QWidget>
class ArcGraphicsItem : public QGraphicsItem
{
public:
ArcGraphicsItem();
ArcGraphicsItem(int i, QPointF point0, QPointF point1, QPointF point2);
~ArcGraphicsItem();
QRectF boundingRect() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
int type() const { return Type;}
int id() {return m_id;}
QPainterPath shape() const;
protected:
private:
void init();
enum { Type = UserType + 6 };
int m_id;
QPointF startP, midP, endP, p1, p2, p3, center;
QLineF lineBC;
QLineF lineAC;
QLineF lineBA;
QLineF lineOA;
QLineF lineOB;
QLineF lineOC;
QLineF bisectorBC;
QLineF bisectorBA;
qreal startAngle;
qreal spanAngle;
QRectF circle;
QRectF boundingRectTemp;
qreal rad;
};
#endif // ARCGRAPHICSITEM_H
arcgraphicsitem.cpp
#include "arcgraphicsitem.h"
#include "qmath.h"
#include <QPen>
#include <QDebug>
#include <QPainterPath>
ArcGraphicsItem::ArcGraphicsItem(int i,
QPointF point1,
QPointF point2,
QPointF point3)
: m_id(i), p1(point1), p2(point2), p3(point3)
{
init();
}
ArcGraphicsItem::ArcGraphicsItem()
{
p1 = QPointF(0,0);
p2 = QPointF(0,1);
p3 = QPointF(1,1);
m_id = -1;
init();
}
ArcGraphicsItem::~ArcGraphicsItem()
{
}
void ArcGraphicsItem::init()
{
lineBC = QLineF(p2, p3);
lineAC = QLineF(p1, p3);
lineBA = QLineF(p2, p1);
rad = qAbs(lineBC.length()/(2*qSin(qDegreesToRadians(lineAC.angleTo(lineBA)))));
bisectorBC = QLineF(lineBC.pointAt(0.5), lineBC.p2());
bisectorBC.setAngle(lineBC.normalVector().angle());
bisectorBA = QLineF(lineBA.pointAt(0.5), lineBA.p2());
bisectorBA.setAngle(lineBA.normalVector().angle());
bisectorBA.intersect(bisectorBC, ¢er);
circle = QRectF(center.x() - rad, center.y() - rad, rad*2, rad*2);
lineOA = QLineF(center, p1);
lineOB = QLineF(center, p2);
lineOC = QLineF(center, p3);
startAngle = lineOA.angle();
spanAngle = lineOA.angleTo(lineOC);
// Make sure that the span angle covers all three points with the second point in the middle
if(qAbs(spanAngle) < qAbs(lineOA.angleTo(lineOB)) || qAbs(spanAngle) < qAbs(lineOB.angleTo(lineOC)))
{
// swap the end point and invert the spanAngle
startAngle = lineOC.angle();
spanAngle = 360 - spanAngle;
}
int w = 10;
boundingRectTemp = circle.adjusted(-w, -w, w, w);
}
QRectF ArcGraphicsItem::boundingRect() const
{
// outer most edges
return boundingRectTemp;
}
void ArcGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QPen paintpen;
painter->setRenderHint(QPainter::Antialiasing);
paintpen.setWidth(1);
// Draw arc
if(isSelected())
{
painter->setBrush(Qt::SolidPattern);
paintpen.setColor(Qt::black);
painter->setPen(paintpen);
}
else
{
paintpen.setStyle(Qt::DashLine);
paintpen.setColor(Qt::black);
painter->setPen(paintpen);
}
QPainterPath path;
path.arcMoveTo(circle,startAngle);
path.arcTo(circle, startAngle, spanAngle);
// Draw points
if (isSelected())
{
// sets brush for end points
painter->setBrush(Qt::SolidPattern);
paintpen.setColor(Qt::red);
painter->setPen(paintpen);
qreal ptRad = 10;
painter->drawEllipse(p1, ptRad, ptRad);
painter->drawEllipse(p2, ptRad, ptRad);
painter->drawEllipse(p3, ptRad, ptRad);
}
painter->setBrush(Qt::NoBrush);
painter->drawPath(path);
}
QPainterPath ArcGraphicsItem::shape() const
{
QPainterPath path;
path.arcMoveTo(circle,startAngle);
path.arcTo(circle, startAngle, spanAngle);
return path;
}
Hope that helps
I want to implement arc in QGraphicsScene. I want that on clicking of three points my arc should be drawn such that on clicking of three points arc is drawn where first point will be starting of arc, second will be any point on arc and third will be end point of arc. I have tried studing drawArc function but got confused with startangle and spanangle. I was unable to set them dynamically. Please suggest me some way to proceed.
I tried the solution to embend it in my project but got the following error:
error: cannot allocate an object of abstract type 'arc'
arcItem = new arc(++id, startP, midP, endP);
Can you please help me out to solve the problem. I am giving a part of code to my project.
In mousepress event of cadgraphicsscene I have done following thing.
cadgraphicsscene.cpp
void CadGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
// mousePressEvent in the graphicsScene
if(mouseEvent->button() == Qt::LeftButton)
{
switch (entityMode)
{
case ArcMode:
if (mFirstClick)
{
startP = mouseEvent->scenePos();
mFirstClick = false;
mSecondClick = true;
}
else if (!mFirstClick && mSecondClick)
{
midP = mouseEvent->scenePos();
mFirstClick = false;
mSecondClick = false;
mThirdClick = true;
}
else if (!mSecondClick && mThirdClick)
{
endP = mouseEvent->scenePos();
mThirdClick = false;
mPaintFlag = true;
}
if (mPaintFlag)
{
arcItem = new arc(++id, startP, midP, endP);
itemList.append(arcItem);
mUndoStack->push(new CadCommandAdd(this, arcItem));
setFlags();
}
}
}
}
arc.cpp
#include "arc.h"
arc::arc(int i, QPointF point1, QPointF point2, QPointF point3)
{
// assigns id
id = i;
p1 = point1;
p2 = point2;
p3 = point3;
lineBC(point2, point3);
lineAC(point1, point3);
lineBA(point2, point1);
rad = qAbs(lineBC.length()/(2*qSin(qDegreesToRadians(lineAC.angleTo(lineBA)))));
bisectorBC(lineBC.pointAt(0.5), lineBC.p2());
bisectorBC.setAngle(lineBC.normalVector().angle());
bisectorBA(lineBA.pointAt(0.5), lineBA.p2());
bisectorBA.setAngle(lineBA.normalVector().angle());
bisectorBA.intersect(bisectorBC, ¢er);
ellipse = new QGraphicsEllipseItem(center.x() - rad, center.y() - rad, rad*2, rad*2);
lineOA(center, point1);
lineOC(center, point3);
}
arc::arc(int i, QLineF start, QLineF end)
{
// assigns id
id = i;
lineOA.angle() = start;
lineOC.angle() - lineOA.angle() = end;
}
int arc::type() const
{
// Enable the use of qgraphicsitem_cast with arc item.
return Type;
}
void arc::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
QPen paintpen;
painter->setRenderHint(QPainter::Antialiasing);
paintpen.setWidth(1);
if (isSelected())
{
// sets brush for end points
painter->setBrush(Qt::SolidPattern);
paintpen.setColor(Qt::red);
painter->setPen(paintpen);
paintpen.setStyle(Qt::DashLine);
paintpen.setColor(Qt::black);
painter->setPen(paintpen);
painter->drawArc(ellipse->boundingRect(),lineOA.angle(),lineOC.angle() - lineOA.angle());
}
else
{
painter->setBrush(Qt::SolidPattern);
paintpen.setColor(Qt::black);
painter->setPen(paintpen);
painter->drawArc(ellipse->boundingRect(),lineOA.angle(),lineOC.angle() - lineOA.angle());
}
}
arc.h
include <QGraphicsItem>
#include "qmath.h"
class arc : public QObject, public QGraphicsItem
{
Q_OBJECT
public:
arc(int, QPointF, QPointF, QPointF);
arc(int, QLineF, QLineF);
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget);
enum { Type = UserType + 6 };
int type() const;
int id;
QPointF startP, midP, endP, p1, p2, p3,center;
QLineF lineBC;
QLineF lineAC;
QLineF lineBA;
QLineF lineOA;
QLineF lineOC;
QLineF bisectorBC;
QLineF bisectorBA;
QGraphicsEllipseItem *ellipse;
qreal rad;
private:
QVector<QPointF> stuff;
};
#endif // ARC_H
Please help me out to solve the error.
This sounds like it could be solved with some relatively simple math:
https://www.google.com/search?q=define%20circle%20three%20points
https://math.stackexchange.com/a/213678
https://www.khanacademy.org/math/geometry/triangle-properties/perpendicular_bisectors/v/three-points-defining-a-circle
Here is my translation of the math into Qt goodness
// m_points is a QList<QPointF>
// use math to define the circle
QLineF lineBC(m_points.at(1), m_points.at(2));
QLineF lineAC(m_points.at(0), m_points.at(2));
QLineF lineBA(m_points.at(1), m_points.at(0));
qreal rad = qAbs(lineBC.length()/(2*qSin(qDegreesToRadians(lineAC.angleTo(lineBA)))));
QLineF bisectorBC(lineBC.pointAt(0.5), lineBC.p2());
bisectorBC.setAngle(lineBC.normalVector().angle());
QLineF bisectorBA(lineBA.pointAt(0.5), lineBA.p2());
bisectorBA.setAngle(lineBA.normalVector().angle());
QPointF center;
bisectorBA.intersect(bisectorBC, ¢er);
qDebug() << rad << center;
QT QGraphicsScene Drawing Arc
QPainterPath* path = new QPainterPath();
path->arcMoveTo(0,0,50,50,20);
path->arcTo(0,0,50,50,20, 90);
scene.addPath(*path);
Putting all of this together into a nice little project turns into this:
https://github.com/peteristhegreat/ThreePointsCircle
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include <QGraphicsView>
#include <QGraphicsScene>
#include "graphicsscene.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
QGraphicsView * view = new QGraphicsView;
GraphicsScene * scene = new GraphicsScene();
view->setScene(scene);
view->setSceneRect(-300,-300, 300, 300);
this->resize(600, 600);
this->setCentralWidget(view);
}
MainWindow::~MainWindow()
{
}
graphicsscene.h
#ifndef GRAPHICSSCENE_H
#define GRAPHICSSCENE_H
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QPointF>
#include <QList>
class GraphicsScene : public QGraphicsScene
{
Q_OBJECT
public:
explicit GraphicsScene(QObject *parent = 0);
virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent * mouseEvent);
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * mouseEvent);
virtual void mousePressEvent(QGraphicsSceneMouseEvent * mouseEvent);
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * mouseEvent);
signals:
public slots:
private:
QList <QPointF> m_points;
};
#endif // GRAPHICSSCENE_H
graphicsscene.cpp
#include "graphicsscene.h"
#include <QDebug>
#include <QGraphicsEllipseItem>
#include <QGraphicsPathItem>
#include <QPainterPath>
#include "qmath.h"
GraphicsScene::GraphicsScene(QObject *parent) :
QGraphicsScene(parent)
{
this->setBackgroundBrush(Qt::gray);
}
void GraphicsScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent * mouseEvent)
{
qDebug() << Q_FUNC_INFO << mouseEvent->scenePos();
QGraphicsScene::mouseDoubleClickEvent(mouseEvent);
}
void GraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent * mouseEvent)
{
qDebug() << Q_FUNC_INFO << mouseEvent->scenePos();
QGraphicsScene::mouseMoveEvent(mouseEvent);
}
void GraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent * mouseEvent)
{
qDebug() << Q_FUNC_INFO << mouseEvent->scenePos();
QGraphicsScene::mousePressEvent(mouseEvent);
}
void GraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent * me)
{
qDebug() << Q_FUNC_INFO << me->scenePos();
int radius = 20;
QGraphicsEllipseItem * ellipse = this->addEllipse(me->scenePos().x() - radius, me->scenePos().y() - radius, radius*2, radius*2);
ellipse->setBrush(Qt::white);
m_points.append(me->scenePos());
if(m_points.size() == 3)
{
// use math to define the circle
QLineF lineBC(m_points.at(1), m_points.at(2));
QLineF lineAC(m_points.at(0), m_points.at(2));
QLineF lineBA(m_points.at(1), m_points.at(0));
qreal rad = qAbs(lineBC.length()/(2*qSin(qDegreesToRadians(lineAC.angleTo(lineBA)))));
QLineF bisectorBC(lineBC.pointAt(0.5), lineBC.p2());
bisectorBC.setAngle(lineBC.normalVector().angle());
QLineF bisectorBA(lineBA.pointAt(0.5), lineBA.p2());
bisectorBA.setAngle(lineBA.normalVector().angle());
QPointF center;
bisectorBA.intersect(bisectorBC, ¢er);
qDebug() << rad << center;
bool drawCircle = true;
QGraphicsEllipseItem * ellipse = new QGraphicsEllipseItem(center.x() - rad, center.y() - rad, rad*2, rad*2);
if(drawCircle)
this->addItem(ellipse);
// add arc
// this->addItem(path);
QPainterPath path;
QLineF lineOA(center, m_points.at(0));
QLineF lineOC(center, m_points.at(2));
path.arcMoveTo(ellipse->boundingRect(),lineOA.angle());
path.arcTo(ellipse->boundingRect(), lineOA.angle(), lineOC.angle() - lineOA.angle());
QGraphicsPathItem * pathItem = new QGraphicsPathItem(path);
pathItem->setPen(QPen(Qt::red,10));
this->addItem(pathItem);
if(!drawCircle)
delete ellipse;
m_points.clear();
}
QGraphicsScene::mouseReleaseEvent(me);
}
A Subclass of QGraphicsItem, that takes 3 points, and intersects the three with an arc of a circle. The second point is always in the middle. (Selectablity and other properties haven't been fully implemented or tested).
Note: Qt Creator includes more advanced examples of subclassed QGraphicsItems such as Colliding Mice, and 40,000 chips examples.
http://qt-project.org/doc/qt-5/examples-graphicsview.html
Also to enable QObject signals and slots and properties from a QGraphicsItem, you should use QGraphicsObject.
Note: added onto github here.
arcgraphicsitem.h
#ifndef ARCGRAPHICSITEM_H
#define ARCGRAPHICSITEM_H
#include <QGraphicsItem>
#include <QLineF>
#include <QPointF>
#include <QPainter>
#include <QStyleOptionGraphicsItem>
#include <QWidget>
class ArcGraphicsItem : public QGraphicsItem
{
public:
ArcGraphicsItem();
ArcGraphicsItem(int i, QPointF point0, QPointF point1, QPointF point2);
~ArcGraphicsItem();
QRectF boundingRect() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
int type() const { return Type;}
int id() {return m_id;}
QPainterPath shape() const;
protected:
private:
void init();
enum { Type = UserType + 6 };
int m_id;
QPointF startP, midP, endP, p1, p2, p3, center;
QLineF lineBC;
QLineF lineAC;
QLineF lineBA;
QLineF lineOA;
QLineF lineOB;
QLineF lineOC;
QLineF bisectorBC;
QLineF bisectorBA;
qreal startAngle;
qreal spanAngle;
QRectF circle;
QRectF boundingRectTemp;
qreal rad;
};
#endif // ARCGRAPHICSITEM_H
arcgraphicsitem.cpp
#include "arcgraphicsitem.h"
#include "qmath.h"
#include <QPen>
#include <QDebug>
#include <QPainterPath>
ArcGraphicsItem::ArcGraphicsItem(int i,
QPointF point1,
QPointF point2,
QPointF point3)
: m_id(i), p1(point1), p2(point2), p3(point3)
{
init();
}
ArcGraphicsItem::ArcGraphicsItem()
{
p1 = QPointF(0,0);
p2 = QPointF(0,1);
p3 = QPointF(1,1);
m_id = -1;
init();
}
ArcGraphicsItem::~ArcGraphicsItem()
{
}
void ArcGraphicsItem::init()
{
lineBC = QLineF(p2, p3);
lineAC = QLineF(p1, p3);
lineBA = QLineF(p2, p1);
rad = qAbs(lineBC.length()/(2*qSin(qDegreesToRadians(lineAC.angleTo(lineBA)))));
bisectorBC = QLineF(lineBC.pointAt(0.5), lineBC.p2());
bisectorBC.setAngle(lineBC.normalVector().angle());
bisectorBA = QLineF(lineBA.pointAt(0.5), lineBA.p2());
bisectorBA.setAngle(lineBA.normalVector().angle());
bisectorBA.intersect(bisectorBC, ¢er);
circle = QRectF(center.x() - rad, center.y() - rad, rad*2, rad*2);
lineOA = QLineF(center, p1);
lineOB = QLineF(center, p2);
lineOC = QLineF(center, p3);
startAngle = lineOA.angle();
spanAngle = lineOA.angleTo(lineOC);
// Make sure that the span angle covers all three points with the second point in the middle
if(qAbs(spanAngle) < qAbs(lineOA.angleTo(lineOB)) || qAbs(spanAngle) < qAbs(lineOB.angleTo(lineOC)))
{
// swap the end point and invert the spanAngle
startAngle = lineOC.angle();
spanAngle = 360 - spanAngle;
}
int w = 10;
boundingRectTemp = circle.adjusted(-w, -w, w, w);
}
QRectF ArcGraphicsItem::boundingRect() const
{
// outer most edges
return boundingRectTemp;
}
void ArcGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QPen paintpen;
painter->setRenderHint(QPainter::Antialiasing);
paintpen.setWidth(1);
// Draw arc
if(isSelected())
{
painter->setBrush(Qt::SolidPattern);
paintpen.setColor(Qt::black);
painter->setPen(paintpen);
}
else
{
paintpen.setStyle(Qt::DashLine);
paintpen.setColor(Qt::black);
painter->setPen(paintpen);
}
QPainterPath path;
path.arcMoveTo(circle,startAngle);
path.arcTo(circle, startAngle, spanAngle);
// Draw points
if (isSelected())
{
// sets brush for end points
painter->setBrush(Qt::SolidPattern);
paintpen.setColor(Qt::red);
painter->setPen(paintpen);
qreal ptRad = 10;
painter->drawEllipse(p1, ptRad, ptRad);
painter->drawEllipse(p2, ptRad, ptRad);
painter->drawEllipse(p3, ptRad, ptRad);
}
painter->setBrush(Qt::NoBrush);
painter->drawPath(path);
}
QPainterPath ArcGraphicsItem::shape() const
{
QPainterPath path;
path.arcMoveTo(circle,startAngle);
path.arcTo(circle, startAngle, spanAngle);
return path;
}
Hope that helps
I have different subclasses for my entities. The entities are drawn in GraphicsView, How can apply the Clipboard operations for Cut, Copy and Paste.
gEntity.h
#ifndef GENTITY_H
#define GENTITY_H
class gEntity :public QObject
{
public:
gEntity(QObject* parent=0) : QObject(parent) {}
virtual ~gEntity() {}
virtual gEntity* my_clone() { return 0; }
};
#endif // GENTITY_H
circle.h
#ifndef CIRCLE_H
#define CIRCLE_H
#include <QPainter>
#include <QGraphicsItem>
#include <gentity.h>
#include "qmath.h"
class Circle : public QObject, public QGraphicsItem, public gEntity
{
Q_OBJECT
public:
Circle(QObject* parent=0) : gEntity(parent){
}
Circle(int, QPointF, QPointF);
Circle(int, QPointF, qreal);
QRectF boundingRect() const;
virtual void paint(QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget);
enum { Type = UserType + 3 };
int type() const;
int id;
QPointF center_p, end_p, move_p;
qreal radius;
void setRadius((const qreal &radius);
private:
QVector<QPointF> stuff;
};
#endif // CIRCLE_H
circle.cpp
#include "circle.h"
#include "gentity.h"
Circle::Circle(int i, QPointF p1, QPointF p2)
{
// assigns id
id = i;
/* set values of center point, end point
and calculate radius of circle */
center_p = p1;
end_p = p2;
radius = qSqrt(qPow((end_p.x()-center_p.x()), 2)
+ qPow((end_p.y()-center_p.y()), 2));
}
Circle::Circle(int i, QPointF p1, qreal rad)
{
// assigns id
id = i;
/* set values of center point
and radius of circle */
center_p = p1;
radius = rad;
}
int Circle::type() const
{
// Enable the use of qgraphicsitem_cast with circle item.
return Type;
}
QRectF Circle::boundingRect() const
{
// bounding rectangle for circle
return QRectF((center_p.x()-radius), (center_p.y()-radius),
(2*radius), (2*radius));
}
void Circle::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
// draws/paints the path of circle
QPen paintpen(Qt::black);
paintpen.setWidth(1);
painter->setRenderHint(QPainter::Antialiasing);
if (isSelected())
{
// sets brush for center point
painter->setBrush(Qt::SolidPattern);
paintpen.setColor(Qt::red);
painter->setPen(paintpen);
painter->drawEllipse(center_p, 2, 2);
// sets pen for circumference
paintpen.setStyle(Qt::DashLine);
paintpen.setColor(Qt::black);
painter->setBrush(Qt::NoBrush);
painter->setPen(paintpen);
painter->drawEllipse(center_p, radius, radius);
}
else
{ painter->save();
painter->setBrush(Qt::SolidPattern);
paintpen.setColor(Qt::black);
painter->setPen(paintpen);
painter->drawEllipse(center_p, 2, 2);
painter->setBrush(Qt::NoBrush);
painter->drawEllipse(center_p, radius, radius);
painter->restore();
}
}
gEntity* my_clone(){
Circle *c = new Circle();
c->setRadius(radius);
return c;
}
Clipboard.h
#include<QStack>
#include<QClipboard>
class MyClipBoard
{
public:
static MyClipBoard* instance()
{
if(!inst)
inst = new MyClipBoard;
return inst;
}
void push(gEntity* g) {
clips.push(g);
}
gEntity* last()
{
if(clips.count() == 0) return 0;
return clips.last();
}
gEntity* pop()
{
if(clips.count() == 0) return 0;
return clips.pop();
}
bool isempty() const { return clips.empty(); }
private:
QStack<gEntity*> clips;
static MyClipBoard* inst;
};
CadgraphicsScene.cpp
MyClipBoard* MyClipBoard::inst = 0;
CadGraphicsScene::CadGraphicsScene(QObject *parent, QUndoStack *undoStack)
: QGraphicsScene(parent)
{
setFlags();
id = 0;
mUndoStack = undoStack;
m_context = new QMenu;
m_context->addAction("Insert Circle");
a_cut = m_context->addAction("cut");
a_copy = m_context->addAction("copy");
a_paste = m_context->addAction("paste");
context_item = 0;
connect(m_context, SIGNAL(triggered(QAction*)), this, SLOT(contmenu(QAction*)));
}
void CadGraphicsScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event){
m_context->exec(event->screenPos());
}
void CadGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
// mousePressEvent in the graphicsScene
if(mouseEvent->button() == Qt::LeftButton)
{
switch (entityMode)
{
case NoMode:
qDebug() << "No Mode";
break;
case PointMode:
pointItem = new Point(++id);
pointItem->setPos(mouseEvent->scenePos());
itemList.append(pointItem);
mUndoStack->push(new CadCommandAdd(this, pointItem));
break;
case LineMode:
if (mFirstClick)
{
start_p = mouseEvent->scenePos();
mFirstClick = false;
mSecondClick = true;
}
else if (!mFirstClick && mSecondClick)
{
end_p = mouseEvent->scenePos();
mPaintFlag = true;
mSecondClick = false;
}
if (mPaintFlag)
{
lineItem = new Line(++id, start_p, end_p);
lineItem->setLine(start_p.x(), start_p.y(), end_p.x(), end_p.y());
itemList.append(lineItem);
mUndoStack->push(new CadCommandAdd(this, lineItem));
setFlags();
}
break;
case CircleMode:
if (mFirstClick)
{
start_p = mouseEvent->scenePos();
mFirstClick = false;
mSecondClick = true;
}
else if (!mFirstClick && mSecondClick)
{
end_p = mouseEvent->scenePos();
mPaintFlag = true;
mSecondClick = false;
}
if (mPaintFlag)
{
circleItem = new Circle(++id, start_p, end_p);
itemList.append(circleItem);
mUndoStack->push(new CadCommandAdd(this, circleItem));
setFlags();
}
break;
case EllipseMode:
if (mFirstClick)
{
start_p = mouseEvent->scenePos();
mFirstClick = false;
mSecondClick = true;
}
else if (!mFirstClick && mSecondClick)
{
mid_p = mouseEvent->scenePos();
mFirstClick = false;
mSecondClick = false;
mThirdClick = true;
}
else if (!mSecondClick && mThirdClick)
{
end_p = mouseEvent->scenePos();
mThirdClick = false;
mPaintFlag = true;
}
if (mPaintFlag)
{
ellipseItem = new Ellipse(++id, start_p, mid_p, end_p);
itemList.append(ellipseItem);
mUndoStack->push(new CadCommandAdd(this, ellipseItem));
setFlags();
}
break;
case TextMode:
textItem = new mText(++id);
textItem->setPos(mouseEvent->scenePos());
itemList.append(textItem);
textItem->setTextInteractionFlags(Qt::TextEditorInteraction);
mUndoStack->push(new CadCommandAdd(this, textItem));
connect(textItem, SIGNAL(lostFocus(mText*)),
this, SLOT(editorLostFocus(mText*)));
connect(textItem, SIGNAL(selectedChange(QGraphicsItem*)),
this, SIGNAL(itemSelected(QGraphicsItem*)));
setFlags();
default:
;
}
}else if(event->button() & Qt::RightButton)
{
context_item = itemAt(event->scenePos().toPoint(), QTransform());//base operand not a pointer
cpos = event->scenePos();//says invalid use of member function
if(!context_item)//Here it says all variables out of scope
{
a_cut->setEnabled(false);
a_copy->setEnabled(false);
if(MyClipBoard::instance()->isempty())
a_paste->setEnabled(false);
else a_paste->setEnabled(true);
}
else
{
a_cut->setEnabled(true);
a_copy->setEnabled(true);
a_paste->setEnabled(false);
}
}
QGraphicsScene::mousePressEvent(mouseEvent);
}
cadgraphicsscene.h
private:
QMenu* m_context;
QAction* a_cut;
QAction* a_copy;
QAction* a_paste;
QGraphicsItem* context_item;
QPointF cpos;
what are you mean of cutting a grahicsitem? (change its position or move to another view)
for copy and paste search for cloning a object in c++ it's not hard, and in paste just change position of new object and add that to your graphicsview or scene
update:
#ifndef MYSCENE_H
#define MYSCENE_H
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsItem>
#include <QGraphicsSceneContextMenuEvent>
#include <QMenu>
#include <QStack>
class gEntity : public QObject, public QGraphicsItem
{
public:
gEntity(QObject* parent=0) : QObject(parent) {}
virtual ~gEntity() {}
virtual gEntity* my_clone() { return 0; }
};
class Circle : public gEntity
{
public:
Circle(QObject* parent=0) : gEntity(parent) {
m_radius = 10;
}
qreal radius() const;
void setRadius(const qreal &radius);
gEntity* my_clone()
{
Circle* c = new Circle;
c->setRadius(m_radius);
return c;
}
QRectF boundingRect() const
{
return QRectF(-m_radius, -m_radius, 2 * m_radius, 2 * m_radius);
}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
painter->save();
painter->setBrush(Qt::yellow);
painter->drawEllipse(QPointF(0,0), m_radius, m_radius);
painter->restore();
}
private:
qreal m_radius;
};
class MyClipBoard
{
public:
static MyClipBoard* instance()
{
if(!inst)
inst = new MyClipBoard;
return inst;
}
void push(gEntity* g) {
clips.push(g);
}
gEntity* last()
{
if(clips.count() == 0) return 0;
return clips.last();
}
gEntity* pop()
{
if(clips.count() == 0) return 0;
return clips.pop();
}
bool isempty() const { return clips.empty(); }
private:
QStack<gEntity*> clips;
static MyClipBoard* inst;
};
class MyScene : public QGraphicsScene
{
Q_OBJECT
public:
MyScene(QObject* parent=0);
virtual ~MyScene()
{
delete m_context;
}
protected:
void contextMenuEvent(QGraphicsSceneContextMenuEvent* event);
void mousePressEvent(QGraphicsSceneMouseEvent* event);
public slots:
void insertCircle(const QPointF& pos)
{
Circle* mcircle = new Circle;
addItem(mcircle);
mcircle->setPos(pos);
}
void cut(gEntity* obj)
{
removeItem(obj);
MyClipBoard::instance()->push(obj);
}
void copy(gEntity* obj)
{
MyClipBoard::instance()->push(obj->my_clone());
}
void paste(const QPointF& pos)
{
gEntity* last = MyClipBoard::instance()->pop();
if(last)
{
addItem(last);
last->setPos(pos);
}
}
void contmenu(QAction* a)
{
if(a->text() == "Insert Circle")
{
insertCircle(cpos);
}
else if(a == a_cut)
{
cut(static_cast<gEntity*>(context_item));
}
else if(a == a_copy)
{
copy(static_cast<gEntity*>(context_item));
}
else if(a == a_paste)
{
paste(cpos);
}
}
private:
QMenu* m_context;
QAction* a_cut;
QAction* a_copy;
QAction* a_paste;
QGraphicsItem* context_item;
QPointF cpos;
};
#endif // MYSCENE_H
scene.cpp
#include "myscene.h"
#include <QGraphicsSceneMouseEvent>
MyClipBoard* MyClipBoard::inst = 0;
qreal Circle::radius() const
{
return m_radius;
}
void Circle::setRadius(const qreal &radius)
{
m_radius = radius;
}
MyScene::MyScene(QObject *parent) : QGraphicsScene(parent)
{
m_context = new QMenu;
m_context->addAction("Insert Circle");
a_cut = m_context->addAction("cut");
a_copy = m_context->addAction("copy");
a_paste = m_context->addAction("paste");
context_item = 0;
connect(m_context, SIGNAL(triggered(QAction*)), this, SLOT(contmenu(QAction*)));
}
void MyScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event){
m_context->exec(event->screenPos());
}
void MyScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
QGraphicsScene::mousePressEvent(event);
if(event->button() & Qt::RightButton)
{
context_item = itemAt(event->scenePos().toPoint(), QTransform());
cpos = event->scenePos();
if(!context_item)
{
a_cut->setEnabled(false);
a_copy->setEnabled(false);
if(MyClipBoard::instance()->isempty())
a_paste->setEnabled(false);
else a_paste->setEnabled(true);
}
else
{
a_cut->setEnabled(true);
a_copy->setEnabled(true);
a_paste->setEnabled(false);
}
}
}
main.cpp
#include <QApplication>
#include "myscene.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsView w(new MyScene);
w.setSceneRect(0,0,500,500);
w.show();
return a.exec();
}