Problem with resizing when object is rotated
-
Hi!
I have a text-box/rectangle with corner-grabbers to enable to change width & height (draggable corners). But after a rotation/transform the re-size does not works as expected. When dragging the corners I want the oppsite corner to be stucked/not moving as it does when the text-box is not rotated. Please can some have a look at it and give me some advices.Best Regards
OlleThis function is used to rotate the textbox:
void StateBox::rotate(qreal angle) { setTransformOriginPoint(boundingRect().center()); setRotation(45); }
statebox.cpp file:
#include "statebox.h" #include <QBrush> #include <QLinearGradient> #include <QDebug> #include "math.h" StateBox::StateBox(): _text(), _outterborderColor(Qt::black), _outterborderPen(), _location(0,0), _dragStart(0,0), _gridSpace(10), _width(250), _height(200), _cornerDragStart(0,0), _XcornerGrabBuffer(20), _YcornerGrabBuffer(20), _drawingWidth( _width - _XcornerGrabBuffer), _drawingHeight( _height - _YcornerGrabBuffer), _drawingOrigenX( _XcornerGrabBuffer), _drawingOrigenY( _YcornerGrabBuffer) { _outterborderPen.setWidth(2); _outterborderPen.setColor(_outterborderColor); _text.setPos(35,35); _text.setPlainText("text goes here"); _text.setParentItem(this); this->setAcceptHoverEvents(true); } void StateBox::adjustSize(int x, int y) { _width += x; _height += y; _drawingWidth = _width - _XcornerGrabBuffer; _drawingHeight= _height - _YcornerGrabBuffer; } bool StateBox::sceneEventFilter ( QGraphicsItem * watched, QEvent * event ) { qDebug() << " QEvent == " + QString::number(event->type()); CornerGrabber * corner = dynamic_cast<CornerGrabber *>(watched); if ( corner == NULL) return false; // not expected to get here QGraphicsSceneMouseEvent * mevent = dynamic_cast<QGraphicsSceneMouseEvent*>(event); if ( mevent == NULL) { // this is not one of the mouse events we are interrested in return false; } switch (event->type() ) { // if the mouse went down, record the x,y coords of the press, record it inside the corner object case QEvent::GraphicsSceneMousePress: { corner->setMouseState(CornerGrabber::kMouseDown); corner->mouseDownX = mevent->pos().x(); corner->mouseDownY = mevent->pos().y(); } break; case QEvent::GraphicsSceneMouseRelease: { corner->setMouseState(CornerGrabber::kMouseReleased); } break; case QEvent::GraphicsSceneMouseMove: { corner->setMouseState(CornerGrabber::kMouseMoving ); } break; default: // we dont care about the rest of the events return false; break; } if ( corner->getMouseState() == CornerGrabber::kMouseMoving ) { qreal x = mevent->pos().x(), y = mevent->pos().y(); // depending on which corner has been grabbed, we want to move the position // of the item as it grows/shrinks accordingly. so we need to eitehr add // or subtract the offsets based on which corner this is. int XaxisSign = 0; int YaxisSign = 0; switch( corner->getCorner() ) { case 0: { XaxisSign = +1; YaxisSign = +1; } break; case 1: { XaxisSign = -1; YaxisSign = +1; } break; case 2: { XaxisSign = -1; YaxisSign = -1; } break; case 3: { XaxisSign = +1; YaxisSign = -1; } break; } // if the mouse is being dragged, calculate a new size and also re-position // the box to give the appearance of dragging the corner out/in to resize the box int xMoved = corner->mouseDownX - x; int yMoved = corner->mouseDownY - y; int newWidth = _width + ( XaxisSign * xMoved); if ( newWidth < 40 ) newWidth = 40; int newHeight = _height + (YaxisSign * yMoved) ; if ( newHeight < 40 ) newHeight = 40; int deltaWidth = newWidth - _width ; int deltaHeight = newHeight - _height ; adjustSize( deltaWidth , deltaHeight); deltaWidth *= (-1); deltaHeight *= (-1); if ( corner->getCorner() == 0 ) { int newXpos = this->pos().x() + deltaWidth; int newYpos = this->pos().y() + deltaHeight; this->setPos(newXpos, newYpos); } else if ( corner->getCorner() == 1 ) { int newYpos = this->pos().y() + deltaHeight; this->setPos(this->pos().x(), newYpos); } else if ( corner->getCorner() == 3 ) { int newXpos = this->pos().x() + deltaWidth; this->setPos(newXpos,this->pos().y()); } setCornerPositions(); this->update(); } return true;// true => do not send event to watched - we are finished with this event } // for supporting moving the box across the scene void StateBox::mouseReleaseEvent ( QGraphicsSceneMouseEvent * event ) { event->setAccepted(true); _location.setX( ( static_cast<int>(_location.x()) / _gridSpace) * _gridSpace ); _location.setY( ( static_cast<int>(_location.y()) / _gridSpace) * _gridSpace ); this->setPos(_location); } // for supporting moving the box across the scene void StateBox::mousePressEvent ( QGraphicsSceneMouseEvent * event ) { event->setAccepted(true); _dragStart = event->pos(); } // for supporting moving the box across the scene void StateBox::mouseMoveEvent ( QGraphicsSceneMouseEvent * event ) { QPointF newPos = event->pos() ; _location += (newPos - _dragStart); this->setPos(_location); } // remove the corner grabbers void StateBox::hoverLeaveEvent ( QGraphicsSceneHoverEvent * ) { _outterborderColor = Qt::black; _corners[0]->setParentItem(NULL); _corners[1]->setParentItem(NULL); _corners[2]->setParentItem(NULL); _corners[3]->setParentItem(NULL); delete _corners[0]; delete _corners[1]; delete _corners[2]; delete _corners[3]; } // create the corner grabbers void StateBox::hoverEnterEvent ( QGraphicsSceneHoverEvent * ) { _outterborderColor = Qt::red; _corners[0] = new CornerGrabber(this,0); _corners[1] = new CornerGrabber(this,1); _corners[2] = new CornerGrabber(this,2); _corners[3] = new CornerGrabber(this,3); _corners[0]->installSceneEventFilter(this); _corners[1]->installSceneEventFilter(this); _corners[2]->installSceneEventFilter(this); _corners[3]->installSceneEventFilter(this); setCornerPositions(); } void StateBox::setCornerPositions() { _corners[0]->setPos(_drawingOrigenX, _drawingOrigenY); _corners[1]->setPos(_drawingWidth, _drawingOrigenY); _corners[2]->setPos(_drawingWidth , _drawingHeight); _corners[3]->setPos(_drawingOrigenX, _drawingHeight); } QRectF StateBox::boundingRect() const { return QRectF(0,0,_width,_height); } void StateBox::paint (QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) { int shadowThickness = 3; QLinearGradient gradient; gradient.setStart(_drawingOrigenX,_drawingOrigenY); gradient.setFinalStop( _drawingWidth ,_drawingOrigenY); QColor grey1(150,150,150,125);// starting color of the gradient - can play with the starting color and ,point since its not visible anyway // grey2 is ending color of the gradient - this is what will show up as the shadow. the last parameter is the alpha blend, its set // to 125 allowing a mix of th color and and the background, making more realistic shadow effect. QColor grey2(225,225,225,125); gradient.setColorAt((qreal)0, grey1 ); gradient.setColorAt((qreal)1, grey2 ); QBrush brush(gradient); painter->setBrush( brush); // for the desired effect, no border will be drawn, and because a brush was set, the drawRoundRect will fill the box with the gradient brush. _outterborderPen.setStyle(Qt::NoPen); painter->setPen(_outterborderPen); QPointF topLeft (_drawingOrigenX,_drawingOrigenX); QPointF bottomRight ( _drawingWidth , _drawingHeight); QRectF rect (topLeft, bottomRight); painter->drawRoundRect(rect,25,25); // corner radius of 25 pixels // draw the top box, the visible one QBrush brush2(QColor(243,255,216,255),Qt::SolidPattern); painter->setBrush( brush2); QPointF topLeft2 (_drawingOrigenX, _drawingOrigenY); QPointF bottomRight2 ( _drawingWidth-shadowThickness, _drawingHeight-shadowThickness); QRectF rect2 (topLeft2, bottomRight2); painter->drawRoundRect(rect2,25,25); } void StateBox::mouseMoveEvent(QGraphicsSceneDragDropEvent *event) { event->setAccepted(false); } void StateBox::mousePressEvent(QGraphicsSceneDragDropEvent *event) { event->setAccepted(false); } void StateBox::rotate(qreal angle) { setTransformOriginPoint(boundingRect().center()); setRotation(45); }
statebox.h
#ifndef STATEBOX_H #define STATEBOX_H #include <QGraphicsItem> #include <QGraphicsRectItem> #include <QGraphicsTextItem> #include <QGraphicsSceneHoverEvent> #include <QGraphicsSceneMouseEvent> #include <QColor> #include <QPainter> #include <QPen> #include <QPointF> #include "cornergrabber.h" class StateBox : public QGraphicsItem { public: StateBox(); QGraphicsTextItem _text; ///< sample text to go in the title area. void setGridSpace(int space); void rotate(qreal angle); private: virtual QRectF boundingRect() const; ///< must be re-implemented in this class to provide the diminsions of the box to the QGraphicsView virtual void paint (QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); ///< must be re-implemented here to pain the box on the paint-event virtual void hoverEnterEvent ( QGraphicsSceneHoverEvent * event ); ///< must be re-implemented to handle mouse hover enter events virtual void hoverLeaveEvent ( QGraphicsSceneHoverEvent * event ); ///< must be re-implemented to handle mouse hover leave events virtual void mouseMoveEvent ( QGraphicsSceneMouseEvent * event );///< allows the main object to be moved in the scene by capturing the mouse move events virtual void mousePressEvent (QGraphicsSceneMouseEvent * event ); virtual void mouseReleaseEvent (QGraphicsSceneMouseEvent * event ); virtual void mouseMoveEvent(QGraphicsSceneDragDropEvent *event); virtual void mousePressEvent(QGraphicsSceneDragDropEvent *event); virtual bool sceneEventFilter ( QGraphicsItem * watched, QEvent * event ) ; void setCornerPositions(); void adjustSize(int x, int y); QColor _outterborderColor; ///< the hover event handlers will toggle this between red and black QPen _outterborderPen; ///< the pen is used to paint the red/black border QPointF _location; QPointF _dragStart; int _gridSpace; qreal _width; qreal _height; QPointF _cornerDragStart; int _XcornerGrabBuffer; int _YcornerGrabBuffer; qreal _drawingWidth; qreal _drawingHeight; qreal _drawingOrigenX; qreal _drawingOrigenY; CornerGrabber* _corners[4];// 0,1,2,3 - starting at x=0,y=0 and moving clockwise around the box }; #endif // STATEBOX_H
cornergrabber.cpp
#include "cornergrabber.h" CornerGrabber::CornerGrabber(QGraphicsItem *parent, int corner) : QGraphicsItem(parent), mouseDownX(0), mouseDownY(0), _outterborderColor(Qt::black), _outterborderPen(), _width(6), _height(6), _corner(corner), _mouseButtonState(kMouseReleased) { setParentItem(parent); _outterborderPen.setWidth(2); _outterborderPen.setColor(_outterborderColor); this->setAcceptHoverEvents(true); } void CornerGrabber::setMouseState(int s) { _mouseButtonState = s; } int CornerGrabber::getMouseState() { return _mouseButtonState; } int CornerGrabber::getCorner() { return _corner; } void CornerGrabber::mouseMoveEvent(QGraphicsSceneDragDropEvent *event) { event->setAccepted(false); } void CornerGrabber::mousePressEvent(QGraphicsSceneDragDropEvent *event) { event->setAccepted(false); } void CornerGrabber::mouseReleaseEvent ( QGraphicsSceneMouseEvent * event ) { event->setAccepted(true); } void CornerGrabber::mousePressEvent ( QGraphicsSceneMouseEvent * event ) { event->setAccepted(false); } void CornerGrabber::mouseMoveEvent ( QGraphicsSceneMouseEvent * event ) { event->setAccepted(false); } void CornerGrabber::hoverLeaveEvent ( QGraphicsSceneHoverEvent * ) { _outterborderColor = Qt::black; this->update(0,0,_width,_height); } void CornerGrabber::hoverEnterEvent ( QGraphicsSceneHoverEvent * ) { _outterborderColor = Qt::red; this->update(0,0,_width,_height); } QRectF CornerGrabber::boundingRect() const { return QRectF(0,0,_width,_height); } void CornerGrabber::paint (QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) { _outterborderPen.setCapStyle(Qt::SquareCap); _outterborderPen.setStyle(Qt::SolidLine); painter->setPen(_outterborderPen); QPointF topLeft (0, 0); QPointF bottomRight ( _width, _height); QRectF rect (topLeft, bottomRight); QBrush brush (Qt::SolidPattern); brush.setColor (_outterborderColor); painter->fillRect(rect,brush); }
cornergrabber.h
#ifndef CORNERGRABBER_H #define CORNERGRABBER_H #include <QObject> #include <QGraphicsItem> #include <QGraphicsRectItem> #include <QGraphicsTextItem> #include <QGraphicsSceneHoverEvent> #include <QGraphicsSceneMouseEvent> #include <QColor> #include <QPainter> #include <QPen> #include <QPointF> class CornerGrabber : public QGraphicsItem { public: explicit CornerGrabber(QGraphicsItem *parent = 0, int corner=0); int getCorner(); ///< allows the owner to find out which coner this is void setMouseState(int); ///< allows the owner to record the current mouse state int getMouseState(); ///< allows the owner to get the current mouse state qreal mouseDownX; qreal mouseDownY; enum {kMouseReleased=0, kMouseDown, kMouseMoving}; ///< define the mouse states private: virtual QRectF boundingRect() const; ///< must be re-implemented in this class to provide the diminsions of the box to the QGraphicsView virtual void paint (QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); ///< must be re-implemented here to pain the box on the paint-event virtual void hoverEnterEvent ( QGraphicsSceneHoverEvent * event ); ///< must be re-implemented to handle mouse hover enter events virtual void hoverLeaveEvent ( QGraphicsSceneHoverEvent * event ); ///< must be re-implemented to handle mouse hover leave events virtual void mouseMoveEvent ( QGraphicsSceneMouseEvent * event ); virtual void mouseMoveEvent(QGraphicsSceneDragDropEvent *event); virtual void mousePressEvent (QGraphicsSceneMouseEvent * event ); virtual void mousePressEvent(QGraphicsSceneDragDropEvent *event); virtual void mouseReleaseEvent (QGraphicsSceneMouseEvent * event ); QColor _outterborderColor; ///< the hover event handlers will toggle this between red and black QPen _outterborderPen; ///< the pen is used to paint the red/black border qreal _width; qreal _height; int _corner;// 0,1,2,3 - starting at x=0,y=0 and moving clockwise around the box int _mouseButtonState; }; #endif // CORNERGRABBER_H
mainwindow.cpp
#include "mainwindow.h" #include "ui_mainwindow.h" #include <QDebug> #include <QSizePolicy> #include <QGraphicsLineItem> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow()), scene( new QGraphicsScene()), gview( new QGraphicsView ()), stateBox( new StateBox()) { QColor c (242,251,235); QBrush brush (c, Qt::SolidPattern); scene->setBackgroundBrush(brush); ui->setupUi(this); ui->horizontalLayout->addWidget( gview); stateBox->setPos(200,200); stateBox->rotate(45); scene->addItem( stateBox); gview->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); gview->setScene( scene); gview->setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate); gview->show(); } void MainWindow::resizeEvent(QResizeEvent * ) { } MainWindow::~MainWindow() { delete gview; delete stateBox; delete scene; delete ui; }
mainwindow.h
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QGraphicsItem> #include <QGraphicsScene> #include <QGraphicsView> #include <QGraphicsEllipseItem> #include <QBrush> #include <QScrollBar> #include <QResizeEvent> #include "statebox.h" namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private: Ui::MainWindow *ui; ///< QtCreator made this instance. QGraphicsScene * scene ; ///< the scene will contain the graphics item 'StateBox' QGraphicsView * gview; ///< the graphics view will contain and display the scene. StateBox * stateBox; ///< this is my custom QGraphicsItem virtual void resizeEvent(QResizeEvent *);///< re-implementing this event handler of the QMainWindow class allows my to capture re-size window values - not used in this project }; #endif // MAINWINDOW_H
main.cpp
#include <QtGui/QApplication> #include "mainwindow.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
pro file:
QT += core gui TARGET = GraphicsViewFrameWork_lesson_3 TEMPLATE = app SOURCES += main.cpp\ mainwindow.cpp \ statebox.cpp \ cornergrabber.cpp HEADERS += mainwindow.h \ statebox.h \ cornergrabber.h FORMS += mainwindow.ui
-
@ollax452 To fix this, the idea is to make sure the old center of rotation remains in place. I did that with code like
// How much did the old center of rotation move because of the change of center of rotation? // Move back by that much, so it stays where it used to be const auto oldCenterInNewItemCoords = oldBoundingBox.center() - newBoundingBox.center(); QTransform rotationTransform; rotationTransform.rotate(rotation()); const auto newPosOfOldCenter = rotationTransform.map(oldCenterInNewItemCoords); const auto unwantedMovement = newPosOfOldCenter - oldCenterInNewItemCoords; newBoundingBox.translate(-unwantedMovement);
(this could obviously be simplified by merging some lines, but I find it more readable this way)
And of course you need to call
setTransformOriginPoint(boundingRect().center());
when the bounding rect changes (so, not inrotate()
, but rather insetGeometry()
or whichever method changes the variables used by boundingRect)