Rotate a collection of QGraphicsItems around the center of their mutual bounding rectangle
This article explains how to implement a rotation of multiple QGraphicsItems around the center of their mutual bounding rectangle. The situation is illustrated in the figure below. Naturally, the approach also works for a single item.
Warning: This approach utilizes QGraphicsItemGroup to deal with transformation of multiple items. The act of creation of QGraphicsItemGroup changes the stacking order of items. For this reason, use this approach only with z-values assigned to your items (or use it if you do not care about the stacking order of your items).
MainWindow implementation
The rotation is accomplished with the help of the slot MainWindow::rotate90(), this can be seen in the header file of the MainWindow:
class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = 0); public slots: void rotate90(); private: QGraphicsView* view; QGraphicsScene* scene; QToolBar* editToolBar; QAction* rotateAction; void createSceneAndView(); void createToolBarAndAction(); void createGraphicsItems(); };
In the MainWindow constructor we create the actions, view, scene and populate it with items. The details of the implementation can be found in the individual files above. The important thing is to connect the triggered() signal of the rotateAction to the rotate90() slot of the MainWindow.
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { resize(250,250); createSceneAndView(); createGraphicsItems(); createToolBarAndAction(); QObject::connect(rotateAction, SIGNAL(triggered()), this, SLOT(rotate90())); }
Notice that I assigned z-values to my items to avoid stacking order problems:
void MainWindow::createGraphicsItems(){ QGraphicsEllipseItem* ellipseItem1 = new QGraphicsEllipseItem(QRectF(0,0,80,120)); ellipseItem1->setPos(60,40); ellipseItem1->setBrush(Qt::darkBlue); ellipseItem1->setZValue(1.0); QGraphicsRectItem* rectItem = new QGraphicsRectItem(QRectF(0,0,50,80)); rectItem->setPos(120,60); rectItem->setBrush(Qt::cyan); rectItem->setZValue(2.0); QGraphicsEllipseItem* ellipseItem2 = new QGraphicsEllipseItem(QRectF(0,0,70,70)); ellipseItem2->setPos(80,90); ellipseItem2->setBrush(Qt::red); ellipseItem2->setZValue(3.0); scene->addItem(ellipseItem1); scene->addItem(rectItem); scene->addItem(ellipseItem2); foreach(QGraphicsItem* item, scene->items()){ item->setFlag(QGraphicsItem::ItemIsSelectable); item->setFlag(QGraphicsItem::ItemIsMovable); } }
Implementation of the slot rotate90()
Note: When rotating an object around an arbitrary point P(px,py) we firstly translate the object in such a manner, that the point P coincides with the origin (T(-px,-py)). After that, we perform the rotation around the origin (R(θ)). Finally, we translate the object back (T(px,py)). The resulting transformation is:
T(px,py)*R(θ)*T(-px,-py)
Below you can see the details of the implementation of the rotate90() slot. We create a group from the selected items and create a new transform. Then we perform the steps leading to the desired transformation according to the formula above. In the end, we assign the new transform to our group and disassemble the group.
void MainWindow::rotate90(){ QGraphicsItemGroup* gr = scene->createItemGroup( scene->selectedItems()); QPointF offset = gr->sceneBoundingRect().center(); QTransform transform; transform.translate(offset.x(),offset.y()); transform.rotate(90); transform.translate(-offset.x(),-offset.y()); gr->setTransform(transform); scene->destroyItemGroup(gr); scene->update(); }
That's it. To try out the example you need to select one or multiple items and click on the Rotate icon.