c++qtqt5qgraphicsitemqgraphicspixmapitem

Draggable pixmaps inside a QGraphicsScene of graphic items


I have a scene with a 12*4 grid with blocks of QGraphicsItems ,when i right click on the blocks I have a contexmenu that can add icons inside the blocks my proplem is that I can't fingure out how can I make those icons draggable to the other blocks inside the graphic scene ,I know there is the "Draggable Icons Example" but how can I implement that code to a graphic scene.

this is the mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QGraphicsPathItem>
class QGraphicsSceneMouseEvent;
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT

public:
explicit MainWindow(QWidget *parent = 0);
bool eventFilter(QObject *, QEvent *);
~MainWindow();

private slots:
void showContextMenu(const QPoint&);
void addPixBlock();

private:
Ui::MainWindow *ui;
QGraphicsScene *scene;
QGraphicsItem *itemAt(const QPointF&);

int x;
int y;

QMenu *Menu;
QMenu *Submenu;
QAction *Picture;
QGraphicsPixmapItem* pix;
};
#endif // MAINWINDOW_H

the mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "block.h"
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QDebug>
#include <QPainter>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
scene = new QGraphicsScene(this) ;
for(int row=-4;row<8;++row)
  for(int column=0;column<4;++column)
{
  Block *b = new Block;
  scene->addItem(b);
  b->setPos(row* 95,column*85);
}
ui->graphicsView->setScene(scene);
scene->installEventFilter(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
QGraphicsItem* MainWindow::itemAt(const QPointF &pos)
{
QList<QGraphicsItem*> items = scene->items(QRectF(pos - QPointF(1,1), 
QSize(3,3)));

foreach(QGraphicsItem *item, items)
     if (item->type() > QGraphicsItem::UserType)
     return item;
 return 0;
  }
 bool MainWindow::eventFilter(QObject *o, QEvent *e)
  {
 QGraphicsSceneMouseEvent *me = (QGraphicsSceneMouseEvent*) e;

 switch ((int) e->type()){

 case QEvent::GraphicsSceneMousePress:{

 switch ((int) me->button()){

  case Qt::RightButton:{

     QGraphicsItem *item = itemAt(me->scenePos());

     if (item && item->type() == Block::Type){
         x=item->scenePos().x();
         y=item->scenePos().y();
        showContextMenu(item->scenePos().toPoint());
      }
   break;
  }
  }
  break;
  }
 }
 return QObject::eventFilter(o, e);
 }
void MainWindow::showContextMenu(const QPoint &pos)
{
Menu= new QMenu("Menu");
Submenu=Menu->addMenu(QIcon(":/img/pix.png"),"Pix");
Picture =Submenu->addAction(QIcon(":/img/pix.png"),"Pix");
connect(Picture, SIGNAL(triggered()), this, SLOT(addPixBlock()));
Menu->exec(QCursor::pos());
}
void MainWindow::addPixBlock()
{
QPixmap pixmap(":/img/pix.png");
pix = scene->addPixmap(pixmap.scaled(70,50));
pix->setPos(x,y);
}

the block.h

#ifndef BLOCK_H
#define BLOCK_H
#include <QGraphicsPathItem>
class QGraphicsSceneMouseEvent;
class Block : public QGraphicsPathItem
{
public:
enum { Type = QGraphicsItem::UserType + 3 };
int type() const { return Type; }
Block(QGraphicsItem *parent = 0);
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget 
*widget);
bool eventFilter(QObject *, QEvent *);
};
#endif // BLOCK_H

the Block.cpp

#include "block.h"
#include <QPainter>
#include <QtWidgets>
class QGraphicsSceneMouseEvent;
Block::Block(QGraphicsItem *parent)
         : QGraphicsPathItem(parent)
{
QPainterPath p;
//<->,|,<->,|,roundness
p.addRoundedRect(0,0,80,50, 5, 5);
setPath(p);
setAcceptDrops(true);
setAcceptedMouseButtons(Qt::LeftButton);
}
void Block::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, 
QWidget *widget)
{
Q_UNUSED(option)
Q_UNUSED(widget)
painter->setPen(QPen(QColor(67, 141, 220)));
painter->setBrush(QColor(67, 141, 220,100));
painter->drawPath(path());
}

Solution

  • First of all if you want to place a QGraphicsPixmapItem on top of another item, a better option is to set it as your parentItem.

    On the other hand we can use an event filter but a better option in this case is to implement a custom QGraphicsScene, and when pressing with the left key it allows to drag the item, for that we use QDrag and we pass the data of the item, then we overwrite the event dropEvent where we will obtain the item and establish a new parent.

    graphicsscene.h

    #ifndef GRAPHICSSCENE_H
    #define GRAPHICSSCENE_H
    
    #include <QGraphicsScene>
    
    class QMenu;
    class QAction;
    
    class GraphicsScene : public QGraphicsScene
    {
    public:
        using QGraphicsScene::QGraphicsScene;
    
    protected:
        void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
        void dropEvent(QGraphicsSceneDragDropEvent *event) override;
    
    private:
        QGraphicsPixmapItem *findPixmapItem(QGraphicsItem *item);
        void createDrag(const QPointF &pos, QWidget *widget, QGraphicsItem *item);
        void showContextMenu(const QPointF &pos);
        void addPixBlock(QGraphicsItem *item);
    
        QMenu *menu;
        QMenu *submenu;
        QAction *picture;
        QGraphicsPixmapItem *pix;
    };
    
    #endif // GRAPHICSSCENE_H
    

    graphicsscene.cpp

    #include "graphicsscene.h"
    
    #include <QDrag>
    #include <QGraphicsItem>
    #include <QGraphicsSceneMouseEvent>
    #include <QMenu>
    #include <QMimeData>
    #include <QWidget>
    
    void GraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
    {
        auto its =  items(QRectF(event->scenePos() - QPointF(1,1), QSize(3,3)));
        auto val = std::find_if(its.constBegin(), its.constEnd(), [](auto const& it){
            return it->type() > QGraphicsItem::UserType;
        });
        if(val == its.constEnd())
            return;
        if(event->button() == Qt::RightButton){
            showContextMenu(event->scenePos());
        }
        else{
            createDrag(event->scenePos(), event->widget(), *val);
        }
    
    }
    
    void GraphicsScene::dropEvent(QGraphicsSceneDragDropEvent *event)
    {
        QByteArray byteArray = event->mimeData()->data("Item");
        QGraphicsPixmapItem * item = *reinterpret_cast<QGraphicsPixmapItem**>(byteArray.data());
        QGraphicsItem *item_parent = itemAt(event->scenePos(), QTransform());
        item->setParentItem(item_parent);
    }
    
    QGraphicsPixmapItem *GraphicsScene::findPixmapItem(QGraphicsItem *item){
        auto chs = item->childItems();
        auto  val = std::find_if(chs.constBegin(), chs.constEnd(), [](auto const& it){
                return static_cast<QGraphicsPixmapItem *>(it) != Q_NULLPTR;
    });
        return val == chs.constEnd() ? Q_NULLPTR : static_cast<QGraphicsPixmapItem *>(*val);
    }
    
    void GraphicsScene::createDrag(const QPointF &pos, QWidget *widget, QGraphicsItem *item){
        QGraphicsPixmapItem *pix = findPixmapItem(item);
        if(pix == Q_NULLPTR)
            return;
        QByteArray byteArray(reinterpret_cast<char*>(&pix),sizeof(QGraphicsPixmapItem*));
        QDrag *drag = new QDrag(widget);
        QMimeData * mimeData = new QMimeData;
        mimeData->setData("Item",byteArray);
        drag->setMimeData(mimeData);
        drag->setHotSpot(pos.toPoint()-pix->scenePos().toPoint());
        drag->setPixmap(pix->pixmap());
        drag->start();
    
    }
    
    void GraphicsScene::showContextMenu(const QPointF &pos)
    {
        QGraphicsItem *item = itemAt(pos, QTransform());
        menu= new QMenu("Menu");
        submenu = menu->addMenu(QIcon(":/img/pix.png"),"Pix");
        picture = submenu->addAction(QIcon(":/img/pix.png"),"Pix");
        connect(picture, &QAction::triggered, [item, this](){
            addPixBlock(item);
        });
        menu->exec(QCursor::pos());
    }
    
    void GraphicsScene::addPixBlock(QGraphicsItem *item)
    {
        if(findPixmapItem(item))
            return;
        QPixmap pixmap(":/img/pix.png");
        pix = addPixmap(pixmap.scaled(70,50));
        if(pix->parentItem() != item)
            pix->setParentItem(item);
    }
    

    Then we establish that new scene and add the Blocks.

    The complete example can be found in the following link