QGraphicsItem setShear ? (applicable for single or multiple items)

290 views Asked by At

To apply rotation on a QGraphicsItem, I can either call rotate() or setRotation(). What I see happening:

item.rotate(angle);

results in rotation, as I expect; yet if I copy the item (using a cop constructor in a subclass), no rotation will be copied (even if I copy all transformations).

item.setRotation(angle);

results in the item having a rotation() property which I can copy - but requires an update.

So the second version is what I need.

I would like to be able to apply shear to my items as well.

item.shear(shx, shy);

looks good on initial item - but I cannot find a way to copy this shear. Nor can I find a similar property as for rotation: there is no setShear() that I can find.

Even more, if I try to play with transformations, to achieve a group shear (like this question's rotation), I get very weird results...

How can I create a similar stored property for shear ?

Edit:

Trying

QTransform t;
t.shear(shx, shy);
item->setTransform(t, true);

also gives me a giant scaling and some rotation... I have tried to divide shx and shy by 100 and it seems reasonable (not sure if correct ?).

Note - will move the code snippet in answer, since it seems it will work.

1

There are 1 answers

0
Thalia On BEST ANSWER

It seems that I have to divide shear factors by 100 (this doesn't seem true always).

void shearItem(qreal shx, qreal shy)
{
    QRectF rect;
    foreach(QGraphicsItem* item, scene()->selectedItems())
        rect |= item->mapToScene(item->boundingRect()).boundingRect();    
    QPointF center = rect.center();

    QTransform t;

    t.translate(center.x(), center.y());
    // seems shear increases item to gigantic proportions so I tried
    t.shear(shx/100, shy/100);
    t.translate(-center.x(), -center.y());

    foreach(QGraphicsItem* item, scene()->selectedItems())
    {
        item->setPos(t.map(item->pos()));
        item->setTransform(t, true);
    }
}

I think the transformations are combined on item, so I don't need to do any action to combine any rotation or scaling or shear... I can just use

item->setTransform(t, true);

for all transforms so there is no need to separately set rotation or scale.

There does not seem to exist any conflict though between translate(), rotate(), scale() and shear() methods and setting the transform matrix. So combining these functions, and then copying the transform seems fine. (http://doc.qt.io/qt-4.8/qtransform.html#setMatrix).

There does not seem to be any documented reason for having to scale down the shear factors... (http://doc.qt.io/qt-4.8/qtransform.html#shear).
If I set shear in item constructor, it works without having to divide by 100.
Yet if I call a shear as in code above, without scaling the factors down I get a resize as well. I find this very confusing... But as long as it works....

Full sample code: included rotation change and shear change

myview.h

#ifndef MYVIEW_H
#define MYVIEW_H

#include <QGraphicsView>
#include <QAction>
#include <QContextMenuEvent>
#include <QGraphicsItem>

class MyView : public QGraphicsView
{
    Q_OBJECT

public:
    MyView(QWidget *parent = 0);

protected:
    virtual void contextMenuEvent(QContextMenuEvent *event);

private slots:
    void rotateItem(qreal deg = 10);
    void shearItem(qreal shx = 10, qreal shy = 10);

private:
    void createActions();
    QAction *rotateAct;
    QAction *shearAct;
};

#endif // MYVIEW_H

myview.cpp

#include "myview.h"
#include <QMenu>

MyView::MyView(QWidget *parent) : QGraphicsView(parent)
{
    createActions();
    setScene(new QGraphicsScene(-500, -150, 1000, 600, this));
    setDragMode(RubberBandDrag);
}

void MyView::contextMenuEvent(QContextMenuEvent *event)
{
    QGraphicsItem* item = itemAt(event->pos());
    if(item != NULL)
        item->setSelected(true);

    if(scene()->selectedItems().isEmpty())  return;

    QMenu menu(this);
    menu.addAction(rotateAct);
    menu.addAction(shearAct);
    menu.exec(event->globalPos());
}

void MyView::rotateItem(qreal deg)
{
    QRectF rect;
    foreach(QGraphicsItem* item, scene()->selectedItems())
        rect |= item->mapToScene(item->boundingRect()).boundingRect();    
    QPointF center = rect.center();

    QTransform t;
    t.translate(center.x(), center.y());
    t.rotate(deg);
    t.translate(-center.x(), -center.y());

    foreach(QGraphicsItem* item, scene()->selectedItems())
    {
        item->setPos(t.map(item->pos()));
        item->setRotation(item->rotation() + deg);
    }
}

void MyView::shearItem(qreal shx, qreal shy)
{
    QRectF rect;
    foreach(QGraphicsItem* item, scene()->selectedItems())
        rect |= item->mapToScene(item->boundingRect()).boundingRect();    
    QPointF center = rect.center();

    QTransform t;

    t.translate(center.x(), center.y());
    t.shear(shx, shy);
    // seems shear increases item to gigantic proportions so I tried
    //t.shear(shx/100, shy/100);
    t.translate(-center.x(), -center.y());

    foreach(QGraphicsItem* item, scene()->selectedItems())
    {
        item->setPos(t.map(item->pos()));
        item->setTransform(t, true);
    }
}

void MyView::createActions()
{
    rotateAct = new QAction(tr("Rotate 10 degrees"), this);
    connect(rotateAct, SIGNAL(triggered()), this, SLOT(rotateItem()));
    shearAct = new QAction(tr("Shear 10, 10"), this);
    connect(shearAct, SIGNAL(triggered()), this, SLOT(shearItem()));
}

main.cpp:

#include <QtGui>    
#include "myview.h"    

int main( int argc, char **argv )
{
    QApplication app(argc, argv);
    MyView* view = new MyView();

    QGraphicsRectItem* rItem = view->scene()->addRect(30, 40, 100, 100, Qt::NoPen, Qt::blue);
    QGraphicsRectItem* rItem1 = view->scene()->addRect(-30, -40, 100, 100, Qt::NoPen, Qt::red);
    QGraphicsEllipseItem* eItem = view->scene()->addEllipse(70, -50, 100, 100, Qt::NoPen, Qt::blue);

    foreach(QGraphicsItem* item, view->scene()->items())
    {
        item->setFlag(QGraphicsItem::ItemIsMovable);
        item->setFlag(QGraphicsItem::ItemIsFocusable);
        item->setFlag(QGraphicsItem::ItemIsSelectable);
    }

    view->show();
    return app.exec();
}