I am trying to allow users to add new "widgets" (images, text, perhaps other custom data too. Image is good enough for now) to a kind of design area. And then I would like them to be able to resize/move those conveniently. The best way for the moving part seems to be to use QGraphicsView. Can be done nicely with 4 lines of code:
auto const scene = new QGraphicsScene{this};
auto const item = scene->addPixmap(QPixmap{":/img/example.png"});
item->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
ui->graphicsView->setScene(scene);
This results in something like this:
It's nice, but cannot be resized with the mouse. I've seen (on this site) multiple ways to make this, sort of, resizable with a mouse, but they are all kind of hacky.
I've seen this example on Qt website which takes a different approach of creating a custom container for a moveable-and-resizeable container. It seems like it can be made work with a bit more tweaking, but it's again, not a nice solution. The widget doesn't really look like it's resizable. When selected, the borders don't have the nice the clue that it's a dynamically placed/sized thing.
Having ms-paint like moveable widgets must be a common use case, so I reckon there has got to be a nice way to get this happen. Is this the case? QGraphicsScene seems like a good candidate honestly. Perhaps I am missing something?
Ok, so I had this problem and my solution was to link the creation of the handlers with the selection of the item:
mainwindow.h
#pragma once
#include <QMainWindow>
#include <QGraphicsItem>
#include <QPainter>
class Handler: public QGraphicsItem
{
public:
enum Mode
{
Top = 0x1,
Bottom = 0x2,
Left = 0x4,
TopLeft = Top | Left,
BottomLeft = Bottom | Left,
Right = 0x8,
TopRight = Top | Right,
BottomRight = Bottom | Right,
Rotate = 0x10
};
Handler(QGraphicsItem *parent, Mode mode);
~Handler(){}
void updatePosition();
QRectF boundingRect() const override;
protected:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
QPointF iniPos;
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
private:
Mode mode;
bool isMoving = false;
};
class ObjectResizerGrip: public QGraphicsItem
{
public:
ObjectResizerGrip(QGraphicsItem *parent): QGraphicsItem(parent)
{
setFlag(QGraphicsItem::ItemHasNoContents, true);
setFlag(QGraphicsItem::ItemIsSelectable, false);
setFlag(QGraphicsItem::ItemIsFocusable, false);
}
void updateHandlerPositions();
virtual QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override{Q_UNUSED(painter) Q_UNUSED(option) Q_UNUSED(widget)}
protected:
QList<Handler*> handlers;
};
class Object4SidesResizerGrip: public ObjectResizerGrip
{
public:
Object4SidesResizerGrip(QGraphicsItem *parent);
};
class Item:public QGraphicsItem
{
public:
Item(QGraphicsItem *parent=nullptr): QGraphicsItem(parent)
{
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
setAcceptHoverEvents(true);
}
QRectF boundingRect() const override
{
return boundingBox;
}
void setWidth(qreal value)
{
auto width = boundingBox.width();
if(width == value) return;
width = qMax(value, 100.0);
setDimensions(width, boundingBox.height());
}
void setHeight(qreal value)
{
auto height = boundingBox.height();
if(height == value) return;
height = qMax(value, 100.0);
setDimensions(boundingBox.width(), height);
}
void setDimensions(qreal w, qreal h)
{
prepareGeometryChange();
boundingBox = QRectF(-w/2.0, -h/2.0, w, h);
if(resizerGrip) resizerGrip->updateHandlerPositions();
update();
}
private:
ObjectResizerGrip* resizerGrip = nullptr;
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override
{
if(change == ItemSelectedHasChanged && scene())
{
if(value.toBool())
{
if(!resizerGrip)
resizerGrip = newSelectionGrip();
}
else
{
if(resizerGrip)
{
delete resizerGrip;
resizerGrip = nullptr;
}
}
}
return QGraphicsItem::itemChange(change, value);
}
QRectF boundingBox;
virtual ObjectResizerGrip *newSelectionGrip() =0;
};
class CrossItem:public Item
{
public:
CrossItem(QGraphicsItem *parent=nullptr): Item(parent){};
private:
virtual ObjectResizerGrip *newSelectionGrip() override
{
return new Object4SidesResizerGrip(this);
}
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
{
painter->drawLine(boundingRect().topLeft(), boundingRect().bottomRight());
painter->drawLine(boundingRect().topRight(), boundingRect().bottomLeft());
}
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
};
mainwindow.cpp
#include "mainwindow.h"
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QApplication>
#include <QGraphicsSceneMouseEvent>
#include <QHBoxLayout>
// Return nearest point along the line to a given point
// http://stackoverflow.com/questions/1459368/snap-point-to-a-line
QPointF getClosestPoint(const QPointF &vertexA, const QPointF &vertexB, const QPointF &point, const bool segmentClamp)
{
QPointF AP = point - vertexA;
QPointF AB = vertexB - vertexA;
qreal ab2 = AB.x()*AB.x() + AB.y()*AB.y();
if(ab2 == 0) // Line lenth == 0
return vertexA;
qreal ap_ab = AP.x()*AB.x() + AP.y()*AB.y();
qreal t = ap_ab / ab2;
if (segmentClamp)
{
if (t < 0.0f) t = 0.0f;
else if (t > 1.0f) t = 1.0f;
}
return vertexA + AB * t;
}
Object4SidesResizerGrip::Object4SidesResizerGrip(QGraphicsItem* parent) : ObjectResizerGrip(parent)
{
handlers.append(new Handler(this, Handler::Left));
handlers.append(new Handler(this, Handler::BottomLeft));
handlers.append(new Handler(this, Handler::Bottom));
handlers.append(new Handler(this, Handler::BottomRight));
handlers.append(new Handler(this, Handler::Right));
handlers.append(new Handler(this, Handler::TopRight));
handlers.append(new Handler(this, Handler::Top));
handlers.append(new Handler(this, Handler::TopLeft));
handlers.append(new Handler(this, Handler::Rotate));
updateHandlerPositions();
}
QRectF ObjectResizerGrip::boundingRect() const
{
return QRectF();
}
void ObjectResizerGrip::updateHandlerPositions()
{
foreach (Handler* item, handlers)
item->updatePosition();
}
Handler::Handler(QGraphicsItem *parent, Mode mode): QGraphicsItem(parent), mode(mode)
{
QPen pen(Qt::white);
pen.setWidth(0);
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsSelectable, false);
setAcceptHoverEvents(true);
setZValue(100);
setCursor(Qt::UpArrowCursor);
updatePosition();
}
void Handler::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QPen pen(isMoving ? QColor(250,214,36) : QColor(100,100,100));
pen.setWidth(0);
pen.setBrush(pen.color());
painter->setPen(pen);
painter->setBrush(QColor(100,100,100,150));
if(mode & Rotate)
{
auto rect_ = ((Item*) parentItem()->parentItem())->boundingRect();
auto topPos = QPointF(rect_.left() + rect_.width() / 2 - 1, rect_.top());
painter->drawLine(mapFromParent(topPos), mapFromParent(topPos - QPointF(0, 175)));
painter->drawEllipse(boundingRect());
}
else
painter->drawRect(boundingRect());
}
QRectF Handler::boundingRect() const
{
return QRectF(-25, -25, 50, 50);
}
void Handler::updatePosition()
{
auto rect_ = ((Item*) parentItem()->parentItem())->boundingRect();
switch (mode)
{
case TopLeft:
setPos(rect_.topLeft());
break;
case Top:
setPos(rect_.left() + rect_.width() / 2 - 1,rect_.top());
break;
case TopRight:
setPos(rect_.topRight());
break;
case Right:
setPos(rect_.right(),rect_.top() + rect_.height() / 2 - 1);
break;
case BottomRight:
setPos(rect_.bottomRight());
break;
case Bottom:
setPos(rect_.left() + rect_.width() / 2 - 1,rect_.bottom());
break;
case BottomLeft:
setPos(rect_.bottomLeft());
break;
case Left:
setPos(rect_.left(), rect_.top() + rect_.height() / 2 - 1);
break;
case Rotate:
setPos(0, rect_.top() - 200);
break;
}
}
void Handler::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(mode & Rotate)
{
Item* item = (Item*) parentItem()->parentItem();
auto angle = QLineF(item->mapToScene(QPoint()), event->scenePos()).angle();
if(!(QApplication::keyboardModifiers() & Qt::AltModifier)) // snap to 45deg
{
auto modAngle = fmod(angle+180, 45);
if(modAngle < 10 || modAngle > 35)
angle = round(angle/45)*45;
}
item->setRotation(0);
angle = QLineF(item->mapFromScene(QPoint()), item->mapFromScene(QLineF::fromPolar(10, angle).p2())).angle();
item->setRotation(90 - angle);
item->update();
}
else
{
Item* item = (Item*) parentItem()->parentItem();
auto diff = mapToItem(item, event->pos()) - mapToItem(item, event->lastPos());
auto bRect = item->boundingRect();
if(mode == TopLeft || mode == BottomRight)
diff = getClosestPoint(bRect.topLeft(), QPoint(0,0), diff, false);
else if(mode == TopRight || mode == BottomLeft)
diff = getClosestPoint(bRect.bottomLeft(), QPoint(0,0), diff, false);
if(mode & Left || mode & Right)
{
item->setPos(item->mapToScene(QPointF(diff.x()/2.0, 0)));
if(mode & Left)
item->setWidth(item->boundingRect().width() - diff.x());
else
item->setWidth(item->boundingRect().width() + diff.x());
}
if(mode & Top || mode & Bottom)
{
item->setPos(item->mapToScene(QPointF(0, diff.y()/2.0)));
if(mode & Top)
item->setHeight(item->boundingRect().height() - diff.y());
else
item->setHeight(item->boundingRect().height() + diff.y());
}
item->update();
}
((ObjectResizerGrip*) parentItem())->updateHandlerPositions();
}
void Handler::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
Q_UNUSED(event);
isMoving = true;
}
void Handler::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
Q_UNUSED(event);
isMoving = false;
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
auto const graphicsView = new QGraphicsView(this);
graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
auto const scene = new QGraphicsScene(this);
auto const item = new CrossItem();
item->setWidth(100);
item->setHeight(100);
scene->addItem(item);
item->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
graphicsView->setScene(scene);
graphicsView-> fitInView(scene->sceneRect(), Qt::KeepAspectRatio);
setCentralWidget(graphicsView);
}
MainWindow::~MainWindow()
{
}
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
This solution is far from perfect but it works and can be a good start for improvements.
Known issues:
rotation grip requires FullViewportUpdate because I was too lazy to implement it in a separate child item and it is drawing outside the bounding box.
there are probably better architectures like using proxies or signals/event.
When it comes to using mouse, keyboard and in general capturing operating system events, you have to rely on the event system. The base class is QEvent, which in your specific case allows you to "QResizeEvent, QMouseEvent, QScrollEvent, ..." and many more fun things.
Related
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);
}
}
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:
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.
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.
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.
In most examples, customizing the Qt slider is done like this (with a stylesheet):
mySlider = new QSlider(centralWidget);
mySlider->setObjectName(QStringLiteral("mySlider"));
mySlider->setGeometry(QRect(645, 678, 110, 21));
mySlider->setOrientation(Qt::Horizontal);
mySlider->setStyleSheet("QSlider::groove:horizontal {background-image:url(:/main/graphics/mySliderBackround.png);}"
"QSlider::handle:horizontal {background-image:url(:/main/graphics/mySliderHandle.png); height:21px; width: 21px;}");
This works fine for me as well.
I have a situation where I need to programmatically set the background using a dyamically created pixmap. Using the code below, this is how I accomplished it. The problem is that when I am on Fedora Linux, this slider works fine. When I'm on OSX or Windows, the slider handle goes off the goove.
Here's what it looks like on OSX. Notice how the handle is off the groove. The left side is customized with a stylesheet, the right is customized with the Style object below.
Create the slider and assign the style:
mySlider = new QSlider(centralWidget);
mySlider->setObjectName(QStringLiteral("mySlider"));
mySlider->setGeometry(QRect(645, 678, 110, 21));
mySlider->setOrientation(Qt::Horizontal);
mySlider->setStyle(new MySliderStyle(mySlider->style()));
The custom slider style code:
Header
#ifndef MYSTYLE_H
#define MYSTYLE_H
#include <QObject>
#include <QWidget>
#include <QProxyStyle>
#include <QPainter>
#include <QStyleOption>
#include <QtWidgets/QCommonStyle>
class MySliderStyle : public QProxyStyle
{
private:
QPixmap groovePixmap;
QPixmap handlePixmap;
public:
LightingSliderStyle(QStyle *style)
: QProxyStyle(style)
{
setColor(QColor::fromRgba(0));
this->handlePixmap = <snip initialize the pixmap>;
this->grooveMaskPixmap = <snip initialize the pixmap>;
}
void drawComplexControl(QStyle::ComplexControl control, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const;
QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, SubControl sc, const QWidget *widget) const;
void setColor(QColor color);
};
#endif // MYSTYLE_H
Implementation*
#include "MySliderStyle.h"
QRect MySliderStyle::subControlRect(ComplexControl control,
const QStyleOptionComplex *option,
SubControl subControl,
const QWidget *widget) const
{
QRect rect;
rect = QCommonStyle::subControlRect(control, option, subControl, widget);
if (control == CC_Slider && subControl == SC_SliderHandle)
{
// this is the exact pixel dimensions of the handle png files
rect.setWidth(21);
rect.setHeight(21);
}
else if (control == CC_Slider && subControl == SC_SliderGroove)
{
// this is the exact pixel dimensions of the slider png files
rect.setWidth(widget->width());
rect.setHeight(widget->height());
}
return rect;
}
void MySliderStyle::drawComplexControl(QStyle::ComplexControl control,
const QStyleOptionComplex *option,
QPainter *painter,
const QWidget *widget) const
{
if (control == CC_Slider)
{
if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(option))
{
QRect groove = subControlRect(CC_Slider, slider, SC_SliderGroove, widget);
QRect handle = subControlRect(CC_Slider, slider, SC_SliderHandle, widget);
if ((slider->subControls & SC_SliderGroove) && groove.isValid())
{
Qt::BGMode oldMode = painter->backgroundMode();
painter->setBackgroundMode(Qt::TransparentMode);
painter->drawPixmap(groove, groovePixmap);
painter->setBackgroundMode(oldMode);
}
if ((slider->subControls & SC_SliderHandle) && handle.isValid())
{
Qt::BGMode oldMode = painter->backgroundMode();
painter->setBackgroundMode(Qt::TransparentMode);
painter->drawPixmap(handle, handlePixmap);
painter->setBackgroundMode(oldMode);
}
}
}
else
{
QProxyStyle::drawComplexControl(control, option, painter, widget);
}
}
void MySliderStyle::setColor(QColor color)
{
QImage myGrooveImage;
// <snip>
// Code to create the custom pixmap
// <snip>
groovePixmap = QPixmap::fromImage(myGrooveImage);
}
UPDATE
The code for this project is open source and available here
A call to QCommonStyle::subControlRect and adjusting the width/height is not enough. You must also recalculate the x/y position.
You can therfore use the QCommonStyle::subControlRect function as reference to calculate the proper rectangle:
QRect LightingSliderStyle::subControlRect(ComplexControl control,
const QStyleOptionComplex *option,
SubControl subControl,
const QWidget *widget) const
{
if (control == CC_Slider)
{
if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) {
QRect ret;
int tickOffset = 0;
int thickness = 21; // height
int len = 21; // width
switch (subControl) {
case SC_SliderHandle: {
int sliderPos = 0;
bool horizontal = slider->orientation == Qt::Horizontal;
sliderPos = sliderPositionFromValue(slider->minimum, slider->maximum+1,
slider->sliderPosition,
(horizontal ? slider->rect.width()
: slider->rect.height()) - len,
slider->upsideDown);
if (horizontal)
ret.setRect(slider->rect.x() + sliderPos, slider->rect.y() + tickOffset, len, thickness);
else
ret.setRect(slider->rect.x() + tickOffset, slider->rect.y() + sliderPos, thickness, len);
break; }
case SC_SliderGroove:
if (slider->orientation == Qt::Horizontal)
ret.setRect(slider->rect.x(), slider->rect.y() + tickOffset,
slider->rect.width(), thickness);
else
ret.setRect(slider->rect.x() + tickOffset, slider->rect.y(),
thickness, slider->rect.height());
break;
default:
break;
}
return visualRect(slider->direction, slider->rect, ret);
}
}
return QCommonStyle::subControlRect(control, option, subControl, widget);
}
in QGraphicview,
if we set it with : ui->graphicsView->setDragMode(QGraphicsView::ScrollHandDrag);
this code make graphicsview can scroll items with mouse pressed and drag.
How can we make QListView or QTableView as the QGraphicsView?
You will need to subclass these widgets and reimplement QWidget::mousePressEvent, QWidget::mousMoveEvent and QWidget::mouseReleaseEvent. However you will have to be careful because you may be interfering with actions that are mapped to these by default implementations (e.g. selecting) so it would need to be tweaked a bit. For example (assumed subclass of QListView):
void MyListView::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::RightButton) //lets map scrolling to right button
m_ScrollStart = event->pos(); //QPoint member, indicates the start of the scroll
else
QListView::mousePressEvent(event);
}
and then
void MyListView::mouseMoveEvent(QMouseEvent *event)
{
if(!m_ScrollStart.isNull()) //if the scroll was started
{
bool direction = (m_ScrollStart.y() < event->pos().y()); //determine direction, true is up (start is below current), false is down (start is above current)
int singleStep = (direction ? 10 : -10); //fill in the desired value
verticalScrollBar()->setValue(verticalScrollBar()->value() + singleStep);
//scroll by the certain amount in determined direction,
//you decide how much will be a single step... test and see what you like
}
QListView::mouseMoveEvent(event);
}
and finally
void MyListView::mouseReleaseEvent(QMouseEvent *event)
{
m_ScrollStart = QPoint(); //resets the scroll drag
QListView::mouseReleaseEvent(event);
}
like Resurrection did mention
You will need to subclass these widgets and reimplement QWidget::mousePressEvent, QWidget::mousMoveEvent and QWidget::mouseReleaseEvent
but below code is more preferred by us:
class MyListView : public QListView
{
typedef QListView super;
public:
explicit MyListView(QWidget *parent = 0);
protected:
// QWidget interface
void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE;
void mouseReleaseEvent(QMouseEvent *) Q_DECL_OVERRIDE;
void mouseMoveEvent(QMouseEvent *) Q_DECL_OVERRIDE;
private:
enum DragState {
DragStopped,
DragStarted,
Dragged
};
quint8 m_dragState;
int m_dragStartPos;
};
MyListView::MyListView(QWidget *parent)
: super(parent)
, m_dragState(DragStopped)
, m_dragStartPos(-1)
{
}
void MyListView::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton) {
m_dragState = DragStarted;
m_dragStartPos = event->pos().y();
} else
super::mousePressEvent(event);
}
void MyListView::mouseReleaseEvent(QMouseEvent *event)
{
if(m_dragState) {
m_dragState = DragStopped;
m_dragStartPos = -1;
return;
}
super::mouseReleaseEvent(event);
}
void MyListView::mouseMoveEvent(QMouseEvent *event)
{
if(m_dragState != DragStopped) {
const int itemSize = sizeHintForRow(0) / 2;
const int distance = qAbs(m_dragStartPos - event->pos().y());
if(distance > 10)
m_dragState = Dragged;
if(distance > itemSize) {
QScrollBar *scrollBar = this->verticalScrollBar();
int stepCount = (distance/itemSize);
if(m_dragStartPos < event->pos().y())
stepCount = -stepCount; //scrolling up
scrollBar->setValue(scrollBar->value() + (stepCount * scrollBar->singleStep()));
m_dragStartPos = event->y();
}
return;
}
super::mouseMoveEvent(event);
}
I am building a map widget (something like the google map) using Qt, basically I used a QGraphicsScene to display the map tile.
Now I want to add a scale ruler to the widget just like the one in google map.
Any suggestions about how could I realize this?
Take a look at this example:
Structure your code base as following:
Write a class inheriting descendants class of QAbstractScrollArea (As example QGraphicsView, QMdiArea, QPlainTextEdit, QScrollArea, QTextEdit, QColumnView, QHeaderView, QListView, QTableView, QTreeView etc.)
In the constructor of your class call setViewportMargins and set the margins of left/top/right/bottom areas length.
Create a QGridLayout and adds your custom Ruler/Scale in the layout.
Set this layout calling setLayout
Example:
setViewportMargins(RULER_BREADTH,RULER_BREADTH,0,0);
QGridLayout* gridLayout = new QGridLayout();
gridLayout->setSpacing(0);
gridLayout->setMargin(0);
mHorzRuler = new QDRuler(QDRuler::Horizontal);
mVertRuler = new QDRuler(QDRuler::Vertical);
QWidget* fake = new QWidget();
fake->setBackgroundRole(QPalette::Window);
fake->setFixedSize(RULER_BREADTH,RULER_BREADTH);
gridLayout->addWidget(fake,0,0);
gridLayout->addWidget(mHorzRuler,0,1);
gridLayout->addWidget(mVertRuler,1,0);
gridLayout->addWidget(this->viewport(),1,1);
this->setLayout(gridLayout);
QDRuler: The ruler class
#define RULER_BREADTH 20
class QDRuler : public QWidget
{
Q_OBJECT
Q_ENUMS(RulerType)
Q_PROPERTY(qreal origin READ origin WRITE setOrigin)
Q_PROPERTY(qreal rulerUnit READ rulerUnit WRITE setRulerUnit)
Q_PROPERTY(qreal rulerZoom READ rulerZoom WRITE setRulerZoom)
public:
enum RulerType { Horizontal, Vertical };
QDRuler(QDRuler::RulerType rulerType, QWidget* parent)
: QWidget(parent), mRulerType(rulerType), mOrigin(0.), mRulerUnit(1.),
mRulerZoom(1.), mMouseTracking(false), mDrawText(false)
{
setMouseTracking(true);
QFont txtFont("Goudy Old Style", 5,20);
txtFont.setStyleHint(QFont::TypeWriter,QFont::PreferOutline);
setFont(txtFont);
}
QSize minimumSizeHint() const
{
return QSize(RULER_BREADTH,RULER_BREADTH);
}
QDRuler::RulerType rulerType() const
{
return mRulerType;
}
qreal origin() const
{
return mOrigin;
}
qreal rulerUnit() const
{
return mRulerUnit;
}
qreal rulerZoom() const
{
return mRulerZoom;
}
public slots:
void setOrigin(const qreal origin)
{
if (mOrigin != origin)
{
mOrigin = origin;
update();
}
}
void setRulerUnit(const qreal rulerUnit)
{
if (mRulerUnit != rulerUnit)
{
mRulerUnit = rulerUnit;
update();
}
}
void setRulerZoom(const qreal rulerZoom)
{
if (mRulerZoom != rulerZoom)
{
mRulerZoom = rulerZoom;
update();
}
}
void setCursorPos(const QPoint cursorPos)
{
mCursorPos = this->mapFromGlobal(cursorPos);
mCursorPos += QPoint(RULER_BREADTH,RULER_BREADTH);
update();
}
void setMouseTrack(const bool track)
{
if (mMouseTracking != track)
{
mMouseTracking = track;
update();
}
}
protected:
void mouseMoveEvent(QMouseEvent* event)
{
mCursorPos = event->pos();
update();
QWidget::mouseMoveEvent(event);
}
void paintEvent(QPaintEvent* event)
{
QPainter painter(this);
painter.setRenderHints(QPainter::TextAntialiasing | QPainter::HighQualityAntialiasing);
QPen pen(Qt::black,0); // zero width pen is cosmetic pen
//pen.setCosmetic(true);
painter.setPen(pen);
// We want to work with floating point, so we are considering
// the rect as QRectF
QRectF rulerRect = this->rect();
// at first fill the rect
//painter.fillRect(rulerRect,QColor(220,200,180));
painter.fillRect(rulerRect,QColor(236,233,216));
// drawing a scale of 25
drawAScaleMeter(&painter,rulerRect,25,(Horizontal == mRulerType ? rulerRect.height()
: rulerRect.width())/2);
// drawing a scale of 50
drawAScaleMeter(&painter,rulerRect,50,(Horizontal == mRulerType ? rulerRect.height()
: rulerRect.width())/4);
// drawing a scale of 100
mDrawText = true;
drawAScaleMeter(&painter,rulerRect,100,0);
mDrawText = false;
// drawing the current mouse position indicator
painter.setOpacity(0.4);
drawMousePosTick(&painter);
painter.setOpacity(1.0);
// drawing no man's land between the ruler & view
QPointF starPt = Horizontal == mRulerType ? rulerRect.bottomLeft()
: rulerRect.topRight();
QPointF endPt = Horizontal == mRulerType ? rulerRect.bottomRight()
: rulerRect.bottomRight();
painter.setPen(QPen(Qt::black,2));
painter.drawLine(starPt,endPt);
}
private:
void drawAScaleMeter(QPainter* painter, QRectF rulerRect, qreal scaleMeter, qreal startPositoin)
{
// Flagging whether we are horizontal or vertical only to reduce
// to cheching many times
bool isHorzRuler = Horizontal == mRulerType;
scaleMeter = scaleMeter * mRulerUnit * mRulerZoom;
// Ruler rectangle starting mark
qreal rulerStartMark = isHorzRuler ? rulerRect.left() : rulerRect.top();
// Ruler rectangle ending mark
qreal rulerEndMark = isHorzRuler ? rulerRect.right() : rulerRect.bottom();
// Condition A # If origin point is between the start & end mard,
//we have to draw both from origin to left mark & origin to right mark.
// Condition B # If origin point is left of the start mark, we have to draw
// from origin to end mark.
// Condition C # If origin point is right of the end mark, we have to draw
// from origin to start mark.
if (mOrigin >= rulerStartMark && mOrigin <= rulerEndMark)
{
drawFromOriginTo(painter, rulerRect, mOrigin, rulerEndMark, 0, scaleMeter, startPositoin);
drawFromOriginTo(painter, rulerRect, mOrigin, rulerStartMark, 0, -scaleMeter, startPositoin);
}
else if (mOrigin < rulerStartMark)
{
int tickNo = int((rulerStartMark - mOrigin) / scaleMeter);
drawFromOriginTo(painter, rulerRect, mOrigin + scaleMeter * tickNo,
rulerEndMark, tickNo, scaleMeter, startPositoin);
}
else if (mOrigin > rulerEndMark)
{
int tickNo = int((mOrigin - rulerEndMark) / scaleMeter);
drawFromOriginTo(painter, rulerRect, mOrigin - scaleMeter * tickNo,
rulerStartMark, tickNo, -scaleMeter, startPositoin);
}
}
void drawFromOriginTo(QPainter* painter, QRectF rulerRect, qreal startMark, qreal endMark, int startTickNo, qreal step, qreal startPosition)
{
bool isHorzRuler = Horizontal == mRulerType;
int iterate = 0;
for (qreal current = startMark;
(step < 0 ? current >= endMark : current <= endMark); current += step)
{
qreal x1 = isHorzRuler ? current : rulerRect.left() + startPosition;
qreal y1 = isHorzRuler ? rulerRect.top() + startPosition : current;
qreal x2 = isHorzRuler ? current : rulerRect.right();
qreal y2 = isHorzRuler ? rulerRect.bottom() : current;
painter->drawLine(QLineF(x1,y1,x2,y2));
if (mDrawText)
{
QPainterPath txtPath;
txtPath.addText(x1 + 1,y1 + (isHorzRuler ? 7 : -2),this->font(),QString::number(qAbs(int(step) * startTickNo++)));
painter->drawPath(txtPath);
iterate++;
}
}
}
void drawMousePosTick(QPainter* painter)
{
if (mMouseTracking)
{
QPoint starPt = mCursorPos;
QPoint endPt;
if (Horizontal == mRulerType)
{
starPt.setY(this->rect().top());
endPt.setX(starPt.x());
endPt.setY(this->rect().bottom());
}
else
{
starPt.setX(this->rect().left());
endPt.setX(this->rect().right());
endPt.setY(starPt.y());
}
painter->drawLine(starPt,endPt);
}
}
private:
RulerType mRulerType;
qreal mOrigin;
qreal mRulerUnit;
qreal mRulerZoom;
QPoint mCursorPos;
bool mMouseTracking;
bool mDrawText;
};
I developing some kind of builder for our project. I want to use both drag and drop support and context menu in my application. Currently I use drag and drop support but no luck with context menu.
Left side my gui is toolbox. I am draging and droping widgets to the right side(QGraphicsScene) and I also want to use context menu inside QGraphicsScene. I use context menu inside graphics scene before. But before I did not use drap & drop operations. I write proper code but it does not work. What is missing?(Is it related drag & drop). Below is my code files.
//#dragwidget.cpp - Toolbox widgets
#include "dragwidget.h"
DragWidget::DragWidget(void)
{
}
DragWidget::~DragWidget(void)
{
}
void DragWidget::mousePressEvent(QMouseEvent *ev)
{
if(ev->button() == Qt::LeftButton)
{
QMimeData *data = new QMimeData;
data->setProperty("type",property("type"));
QDrag *drag = new QDrag(this);
drag->setMimeData(data);
drag->start();
}
}
//#view.cpp - Wrapper class for QGraphicsView
#include "view.h"
View::View(Scene *scene,QWidget *parent)
:QGraphicsView(scene,parent)
{
}
View::~View()
{
}
//#scene.cpp - Wrapper class for QGraphicsScene which catch drop events
#include "scene.h"
Scene::Scene(QObject *parent)
:QGraphicsScene(parent)
{
}
Scene::~Scene(void)
{
}
void Scene::setSceneRect(qreal x, qreal y, qreal w, qreal h)
{
QGraphicsScene::setSceneRect(x,y,w,h);
}
void Scene::dragEnterEvent(QGraphicsSceneDragDropEvent *e)
{
if(e->mimeData()->hasFormat("text/plain"));
e->acceptProposedAction();
}
void Scene::dragMoveEvent(QGraphicsSceneDragDropEvent *e)
{
}
void Scene::dropEvent(QGraphicsSceneDragDropEvent *e)
{
e->acceptProposedAction();
int itemType = e->mimeData()->property("type").toInt();
switch(itemType)
{
case BlockSegment:
drawStandartBlockSegment(e->scenePos());
break;
case Switch:
drawStandartSwitch(e->scenePos());
break;
case Signal:
drawStandartSignal(e->scenePos());
break;
}
}
void Scene::drawStandartBlockSegment(QPointF p)
{
BlockSegmentItem *blockSegment = new BlockSegmentItem(p.x(),p.y(),p.x()+100,p.y());
addItem(blockSegment);
}
void Scene::drawStandartSwitch(QPointF p)
{
SwitchItem *switchItem = new SwitchItem(":app/SS.svg");
switchItem->setPos(p);
addItem(switchItem);
}
void Scene::drawStandartSignal(QPointF p)
{
SignalItem *signalItem = new SignalItem(":app/sgs3lr.svg");
signalItem->setPos(p);
addItem(signalItem);
}
//#item.cpp - Base class for my custom graphics scene items
#include "item.h"
Item::Item(ItemType itemType, QGraphicsItem *parent)
:QGraphicsWidget(parent),m_type(itemType)
{
}
Item::~Item()
{
}
int Item::type()
{
return m_type;
}
QRectF Item::boundingRect() const
{
return QRectF(0,0,0,0);
}
QPainterPath Item::shape() const
{
QPainterPath path;
return path;
}
void Item::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(painter);
Q_UNUSED(option);
Q_UNUSED(widget);
}
void Item::deleteItem()
{
}
void Item::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
Q_UNUSED(event);
}
//#switchitem.cpp - my custom switch item.
#include "switchitem.h"
SwitchItem::SwitchItem(const QString& fileName,QGraphicsItem *parent /* = 0 */)
:Item(Switch,parent)
{
svg = new QGraphicsSvgItem(fileName,this);
createActions();
createContextMenu();
}
SwitchItem::~SwitchItem(void)
{
}
QRectF SwitchItem::boundingRect() const
{
return QRectF(pos().x(),pos().y(),svg->boundingRect().width(),
svg->boundingRect().height());
}
QPainterPath SwitchItem::shape() const
{
QPainterPath path;
path.addRect(boundingRect());
return path;
}
void SwitchItem::createActions()
{
deleteAct = new QAction("Sil",this);
deleteAct->setIcon(QIcon(":app/delete.png"));
connect(deleteAct,SIGNAL(triggered()),this,SLOT(deleteItem()));
}
void SwitchItem::createContextMenu()
{
contextMenu = new QMenu("SwitchContextMenu");
contextMenu->addAction(deleteAct);
}
void SwitchItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *e)
{
contextMenu->exec(e->screenPos());
}
//#app.cpp - Application class
#include "app.h"
#include "dragwidget.h"
App::App(QWidget *parent, Qt::WFlags flags)
: QMainWindow(parent, flags)
{
Q_INIT_RESOURCE(app);
ui.setupUi(this);
canvasScene = new Scene(this);
canvasScene->setSceneRect(0,0,5000,5000);
canvasView = new View(canvasScene);
canvasView->setBackgroundBrush(Qt::black);
canvasView->ensureVisible(0,0,canvasView->geometry().width(),canvasView->geometry().height());
QHBoxLayout *layout = new QHBoxLayout;
toolBox = new QFrame(this);
layout->addWidget(toolBox,1);
layout->addWidget(canvasView,7);
ui.centralWidget->setLayout(layout);
createToolBox();
}
App::~App()
{
}
void App::createToolBox()
{
QVBoxLayout *layout = new QVBoxLayout;
QLabel *blockSegmentLabel = new QLabel("Block Segment");
DragWidget *blockSegmentWidget = new DragWidget;
blockSegmentWidget->setProperty("type",BlockSegment);
blockSegmentWidget->setPixmap(QPixmap(":app/blocksegment.png"));
QLabel *switchLabel = new QLabel("Switch");
DragWidget *switchWidget = new DragWidget;
switchWidget->setProperty("type",Switch);
switchWidget->setPixmap(QPixmap(":app/SS.png"));
QLabel *signalLabel = new QLabel("Signal");
DragWidget *signalWidget = new DragWidget;
signalWidget->setProperty("type",Signal);
signalWidget->setPixmap(QPixmap(":app/sgs3lr.png"));
layout->addWidget(blockSegmentLabel);
layout->addWidget(blockSegmentWidget);
layout->addWidget(switchLabel);
layout->addWidget(switchWidget);
layout->addWidget(signalLabel);
layout->addWidget(signalWidget);
toolBox->setLayout(layout);
}
void DragWidget::mousePressEvent(QMouseEvent *ev)
{
if(ev->button() == Qt::LeftButton)
{
QMimeData *data = new QMimeData;
data->setProperty("type",property("type"));
QDrag *drag = new QDrag(this);
drag->setMimeData(data);
drag->start();
}
}
Problem is here. You've "eaten" right button :) Try fall back into base implementation of mousePressEvent