Max
Max

Reputation: 880

QGraphicsItemGroup and childs resize

I made an algorithm for resizing a picture(inherited from QGraphicsItem) using vector mathematics (I added points to the corners and using the mouse, the picture is resized while maintaining the aspect ratio QGraphicsItem resize with mouse position and keeping aspect ratio , gif: https://gph.is/g/Zr0WdxJ).

Next, I created a group(inherited from QGraphicsItemGroup) add border dots(inherited from QGraphicsRectItem) and added pictures to the group (via addToGroup).

Is it possible to generalize this algorithm to a group? So that all pictures in the group are resized with border dot position.

this what I want: https://gph.is/g/EJxpeVQ (PureRef app) and this is what I got: https://gph.is/g/aQnpq5x

here the project if anybody wants to run application: https://github.com/try-hard-factory/familiar/tree/feature/itemgroup-resize

can't resize childs... here the code of some classes:

borderdot.h :

#ifndef BORDERDOT_H
#define BORDERDOT_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
    };

    enum { Type = UserType + 1 };

    int type() const override
    {
        return Type;
    }
    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) override;
    void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;

public slots:

private:
    unsigned int m_flags;
    QPointF m_previousPosition;
};

borderdot.cpp:

#include "borderdot.h"


#include <QBrush>
#include <QColor>
#include <QGraphicsSceneHoverEvent>
#include <QGraphicsSceneMouseEvent>

DotSignal::DotSignal(QGraphicsItem *parentItem, QObject *parent) :
    QObject(parent)
{
    setZValue(999999999);
//    setFlags(ItemIsMovable);
    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)
{
    setZValue(999999999);
//    setFlags(ItemIsMovable);
    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) {
        qDebug()<<"DotSignal::mouseMoveEvent";
        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 {
        qDebug()<<"else DotSignal::mouseMoveEvent";
        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)
{
    qDebug()<<"DotSignal::hoverEnterEvent";
    Q_UNUSED(event)
    setBrush(QBrush(Qt::red));
}

void DotSignal::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
    qDebug()<<"DotSignal::hoverLeaveEvent";
    Q_UNUSED(event)
    setBrush(QBrush(Qt::black));
}

itemgroup.h:

#ifndef ITEMGROUP_H
#define ITEMGROUP_H

#include <QObject>
#include <QGraphicsItemGroup>


class DotSignal;
class QGraphicsSceneMouseEvent;

class ItemGroup : public QObject, public QGraphicsItemGroup
{
    Q_OBJECT
    Q_INTERFACES(QGraphicsItem)
    Q_PROPERTY(QPointF previousPosition READ previousPosition WRITE setPreviousPosition NOTIFY previousPositionChanged)
public:
    enum EItemsType {
        eBorderDot = QGraphicsItem::UserType + 1,
    };
    ItemGroup(uint64_t& zc, QGraphicsItemGroup *parent = nullptr);
    ~ItemGroup();
    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
    };

public:
    void addItem(QGraphicsItem* item);
    void printChilds();
    QPointF previousPosition() const;
    void setPreviousPosition(const QPointF previousPosition);


signals:
    void rectChanged(ItemGroup *rect);
    void previousPositionChanged();
    void clicked(ItemGroup *rect);
    void signalMove(QGraphicsItemGroup *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;
public:
    void clearItemGroup();
    bool isContain(const QGraphicsItem* item) const;
    bool isEmpty() const;
    void incZ();
protected:
    QRectF boundingRect() const override;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;



private:
    QPointF shiftMouseCoords_;
    uint64_t& zCounter_;
    QRectF m_tmpRect;

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 // ITEMGROUP_H

itemgroup.cpp:

#include <QPainter>
#include <QDebug>
#include <QCursor>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsRectItem>
#include <math.h>
#include "borderdot.h"

static const double Pi = 3.14159265358979323846264338327950288419717;
static double TwoPi = 2.0 * Pi;


ItemGroup::~ItemGroup()
{
    for(int i = 0; i < 8; i++){
        delete cornerGrabber[i];
    }
}

QPointF ItemGroup::previousPosition() const
{
    return m_previousPosition;
}


void ItemGroup::setPreviousPosition(const QPointF previousPosition)
{
    if (m_previousPosition == previousPosition)
        return;

    m_previousPosition = previousPosition;
    emit previousPositionChanged();
}


ItemGroup::ItemGroup(uint64_t& zc, QGraphicsItemGroup *parent) :
    QGraphicsItemGroup(parent),
    zCounter_(zc),
    m_cornerFlags(0),
    m_actionFlags(ResizeState)
{
    setAcceptHoverEvents(true);
    setFlags(ItemIsSelectable|ItemSendsGeometryChanges);
    for(int i = 0; i < 8; i++){
        cornerGrabber[i] = new DotSignal(this);
    }
    setPositionGrabbers();
}

void ItemGroup::addItem(QGraphicsItem* item)
{
    addToGroup(item);
    auto childs = childItems();
    auto tmp = childs.first()->sceneBoundingRect();
    for (auto& it : childs) {
        if (it->type() == eBorderDot) continue;
        tmp = tmp.united(it->sceneBoundingRect());
    }
    m_tmpRect = tmp;
}

void ItemGroup::printChilds()
{
    auto childs = childItems();
    for (auto& it : childs) {
        LOG_DEBUG(logger, "CHILDREN: ", it);
    }
}

QRectF ItemGroup::boundingRect() const
{
    return m_tmpRect;
}

void ItemGroup::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 {
            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);
            }
    }
    QGraphicsItemGroup::mouseMoveEvent(event);
}

void ItemGroup::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    setZValue(++zCounter_);
    shiftMouseCoords_ = (this->pos() - mapToScene(event->pos()))/scale();
    if (event->button() & Qt::LeftButton) {
        m_leftMouseButtonPressed = true;
        setPreviousPosition(event->scenePos());
        emit clicked(this);
    }
    QGraphicsItemGroup::mousePressEvent(event);
    LOG_DEBUG(logger, "EventPos: (", event->pos().x(),";",event->pos().y(), "), Pos: (", pos().x(),";",pos().y(),")");
}

void ItemGroup::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    if (event->button() & Qt::LeftButton) {
        m_leftMouseButtonPressed = false;
    }
    QGraphicsItemGroup::mouseReleaseEvent(event);
}

void ItemGroup::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
    qDebug()<<"ItemGroup::hoverEnterEvent";
    setPositionGrabbers();
    setVisibilityGrabbers();
    QGraphicsItem::hoverEnterEvent(event);
}

void ItemGroup::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
    qDebug()<<"ItemGroup::hoverLeaveEvent";
    m_cornerFlags = 0;
    hideGrabbers();
    setCursor(Qt::CrossCursor);
    QGraphicsItem::hoverLeaveEvent( event );
}

void ItemGroup::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
{
    QPointF pt = event->pos();              // The current position of the mouse
    qreal drx = pt.x() - boundingRect().right();    // Distance between the mouse and the right
    qreal dlx = pt.x() - boundingRect().left();     // Distance between the mouse and the left

    qreal dby = pt.y() - boundingRect().top();      // Distance between the mouse and the top
    qreal dty = pt.y() - boundingRect().bottom();   // Distance between the mouse and the bottom


    m_cornerFlags = 0;
    if( dby < 10 && dby > -10 ) m_cornerFlags |= Top;       // Top side
    if( dty < 10 && dty > -10 ) m_cornerFlags |= Bottom;    // Bottom side
    if( drx < 10 && drx > -10 ) m_cornerFlags |= Right;     // Right side
    if( dlx < 10 && dlx > -10 ) m_cornerFlags |= Left;      // Left side

    switch (m_cornerFlags) {
    case TopLeft:
    case TopRight:
    case BottomLeft:
    case BottomRight: {
        setCursor(Qt::BusyCursor);
        break;
    }
    default:
        setCursor(Qt::CrossCursor);
        break;
    }
}


QVariant ItemGroup::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
{
    switch (change) {
    case QGraphicsItemGroup::ItemSelectedChange:
        m_actionFlags = ResizeState;
        break;
    default:
        break;
    }
    return QGraphicsItemGroup::itemChange(change, value);
}


void ItemGroup::resizeRight(const QPointF &pt)
{
    QRectF tmpRect = boundingRect();
    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();
    m_tmpRect = tmpRect;
    update();
    setPositionGrabbers();
}

void ItemGroup::resizeTop(const QPointF &pt)
{
    QRectF tmpRect = boundingRect();
    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 , boundingRect().height() - tmpRect.height() );
    prepareGeometryChange();
    m_tmpRect = tmpRect;
    update();
    setPositionGrabbers();
}

void ItemGroup::setPositionGrabbers()
{
    QRectF tmpRect = boundingRect();
    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 ItemGroup::setVisibilityGrabbers()
{
    cornerGrabber[GrabberTopLeft]->setVisible(true);
    cornerGrabber[GrabberTopRight]->setVisible(true);
    cornerGrabber[GrabberBottomLeft]->setVisible(true);
    cornerGrabber[GrabberBottomRight]->setVisible(true);
    cornerGrabber[GrabberTop]->setVisible(true);
    cornerGrabber[GrabberBottom]->setVisible(true);
    cornerGrabber[GrabberLeft]->setVisible(true);
    cornerGrabber[GrabberRight]->setVisible(true);
}

void ItemGroup::hideGrabbers()
{
    for(int i = 0; i < 8; i++){
        cornerGrabber[i]->setVisible(false);
    }
}

Upvotes: 0

Views: 351

Answers (1)

goug
goug

Reputation: 2444

I didn't dig through your code, but yes, it can absolutely be done. The product I work in has a "group" concept, and I had to support this exact thing. I can't spell out code for you, but here's the general concept. The same concept works for resizing a related set of selected items that you want to resize all together.

I created a Resizer class that manages the handles and responds to those movements. The Resizer is instantiated when one or more objects is selected. All of the graphics item classes are my own, derived from QGraphicsItem; if you're using the built-in classes, you may need to subclass them and add your own functions.

Whenever the user moves a handle, the Resizer determines the new geometry of the selected items and updates their sizes. For rectangles and ellipses, this involves calls to setRect; for paths, you'll have to write a scaling algorithm that moves the points in the path to their new locations.

For a grouped object, the concept is identical. You look at the proportions of the member objects' areas to the group's encompassing rectangle, recalculate them, and then update them.

What I found to be the key is to save the starting encompassing rectangle, and then create a resize method that accepts the original encompassing rectangle and the new encompassing rectangle (defined by the handle movement), and then scale from the original to the new rectangle. You'll also need the original rectangle of the individual objects.

Off the top of my head, here's the general idea, and this is assuming you want the items to redraw themselves as you move the handles:

  1. When user initiates first handle movement, grab the current geometry of all of the objects being resized, and the encompassing geometry for the overall group.

  2. As the handle moves, call a new "resize" method with the original encompassing rectangle, and the new one defined by the handle positions, and then have each object resize itself by mapping its original area onto the new area, using the group's before/after encompassing rectangles to define the scaling.

  3. When the movement stops, notify each object being resized that the operation is finished, and this is their new size. (This may not be necessary for you, but it was for me because the final object attributes have to be reflected back to a database.)

This takes some effort, but it's absolutely doable. The main things to think about is to abstract the operations and create your own QGraphicsitem-derived classes with methods to support it. If you're using the built-in classes, you can extend them with an interface class that ensures they all have the required methods your resizing code needs.

Upvotes: 1

Related Questions