I defined my custom QQuickPaintedItem in such a way:
class NiceItem : public QQuickPaintedItem
{
...
public:
...
void keyPressEvent(QKeyEvent * event);
void paint(QPainter *painter);
...
};
Here you have keyPressEventcode:
void NiceItem::keyPressEvent(QKeyEvent * event)
{
if(event->key() == Qt::Key_Left)
playerX--;
else if(event->key() == Qt::Key_Right)
playerX++;
else if(event->key() == Qt::Key_Up)
playerY--;
else if(event->key() == Qt::Key_Down)
playerY++;
}
Here's paint code:
void NiceItem::paint(QPainter *painter)
{
QPen pen(m_color, 2);
painter->setPen(pen);
QRectF rectangle(playerX, playerY, 80.0, 60.0);
painter->drawRect(rectangle);
update();
}
As you can see code is really simple. Rectangle is successfully drawn on the screen, but pressing arrows does nothing. Is this a wrong method? If so, how can I process the keyboard input?
For an Item to receive the mouse event, it must meet the following:
Have the flag QQuickItem::ItemIsFocusScope enabled.
Having focus, this can be done in C++ with setFocus(true) or in QML: focus: true.
On the other hand, if your objective is to draw a rectangle on the window, then you have an error in the concept of what coordinates are used for the painting, the painting uses the local coordinates and not the window or screen. So instead of creating new attributes you should only use position(), x(), y() (and their setters) to modify the geometry since these elements are with respect to the parent's coordinates.
#ifndef NICEITEM_H
#define NICEITEM_H
#include <QQuickPaintedItem>
class NiceItem : public QQuickPaintedItem
{
Q_OBJECT
public:
NiceItem(QQuickItem *parent=nullptr);
void paint(QPainter *painter);
protected:
void keyPressEvent(QKeyEvent *event);
private:
QColor m_color;
};
#endif // NICEITEM_H
#include "niceitem.h"
#include <QPainter>
#include <QPen>
NiceItem::NiceItem(QQuickItem *parent):
QQuickPaintedItem(parent), m_color(QColor("red"))
{
setFlag(QQuickItem::ItemIsFocusScope, true);
setFocus(true);
setSize(QSizeF(80.0, 60.0));
}
void NiceItem::paint(QPainter *painter)
{
QPen pen(m_color, 2);
painter->setPen(pen);
painter->drawRect(boundingRect());
}
void NiceItem::keyPressEvent(QKeyEvent *event)
{
QPointF delta;
if(event->key() == Qt::Key_Left)
delta = QPointF(-1, 0);
else if(event->key() == Qt::Key_Right)
delta = QPointF(+1, 0);
else if(event->key() == Qt::Key_Up)
delta = QPointF(0, -1);
else if(event->key() == Qt::Key_Down)
delta = QPointF(0, +1);
setPosition(position() + delta);
}
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.
I have label inside custom frame.
I try to movement of MainWindow (all aplication) on mouse event:
void settingslogolabel::mouseMoveEvent(QMouseEvent *ev)
{
if ((ev->buttons() & Qt::LeftButton) && firstCIsNotNull){
window()->move( mapToGlobal(ev->pos() - m_dragPosition - this->geometry().topLeft()));
}
}
BUT! this->geometry() returns local geometry.
So, how can I get global geometry of child?
What I try to make:
When you press mouse and move - all application should move as your cursor move, until you up mouse button. I want to make this interactive for label.
Full code:
HPP:
#ifndef SETTINGSLOGOLABEL_H
#define SETTINGSLOGOLABEL_H
#include <QLabel>
#include <QWidget>
#include <QMouseEvent>
class settingslogolabel : public QLabel
{
Q_OBJECT
public:
explicit settingslogolabel(QWidget *parent = 0);
void mouseMoveEvent(QMouseEvent *ev);
void mousePressEvent(QMouseEvent *ev);
void mouseReleaseEvent(QMouseEvent *ev);
private:
QPoint m_dragPosition;
bool firstCIsNotNull = true;
private:
};
#endif // SETTINGSLOGOLABEL_H
CPP:
#include "settingslogolabel.hpp"
settingslogolabel::settingslogolabel(QWidget *parent) :
QLabel(parent)
{
}
void settingslogolabel::mouseMoveEvent(QMouseEvent *ev)
{
if ((ev->buttons() & Qt::LeftButton) && firstCIsNotNull){
window()->move( mapToGlobal(ev->pos() - m_dragPosition - this->geometry().topLeft()));
}
}
void settingslogolabel::mousePressEvent(QMouseEvent *ev)
{
if (ev->button() == Qt::LeftButton) {
m_dragPosition = ev->pos();
firstCIsNotNull = true;
}
}
void settingslogolabel::mouseReleaseEvent(QMouseEvent *ev)
{
if (ev->button() == Qt::LeftButton) {
firstCIsNotNull = false;
}
}
Not sure I fully understand the problem but, the global coordinates of the top left corner of a widget can be found -- from within that widget's member functions -- using...
mapToGlobal(QPoint(0, 0));
Similarly, the global geometry would be...
rect().translated(mapToGlobal(QPoint(0, 0)));
Edit:
If the aim is to allow dragging of the top level window then your mouseMoveEvent implementation should be something like (untested)...
void settingslogolabel::mouseMoveEvent (QMouseEvent *ev)
{
if ((ev->buttons() & Qt::LeftButton) && firstCIsNotNull) {
auto delta = ev->pos() - m_dragPosition;
window()->move(window()->pos() + delta);
}
}
I am trying to make a simple extension of QGraphicsRectItem that allows me to resize the rectangle and move it around with the mouse. I model the handles with elliptical arches on the corners I want to enable for dragging, which I implement as QGraphicsEllipseItems:
class QGraphicsBoxWithHandlesItem : public QObject, public QGraphicsRectItem
{
Q_OBJECT
typedef enum {
None,
BottomLeft,
TopRight
} ActiveAnchor;
private:
QGraphicsEllipseItem m_anchorBottomLeft;
QGraphicsEllipseItem m_anchorTopRight;
float m_anchorRadius;
ActiveAnchor m_activeAnchor;
public:
QGraphicsBoxWithHandlesItem(QRectF r, float handlesRadius = 20.0, QGraphicsItem *parent = nullptr);
void setAnchorRadius(float radius);
float getAnchorRadius();
QPainterPath shape() const;
protected:
void mousePressEvent(QGraphicsSceneMouseEvent * event);
void mouseMoveEvent(QGraphicsSceneMouseEvent * event);
void mouseReleaseEvent(QGraphicsSceneMouseEvent * event);
};
I want to be able to detect clicks both on the rectangle and on the handle items (this is necessary because if the rectangle gets too small the handles are the only easily clickable area), so I thought I'd extend QGraphicsRectItem::shape() to add to the returned QPainterPath the paths of the sub-items (adjusting coordinates to make them relative to the parent item):
QPainterPath QGraphicsBoxWithHandlesItem::shape() const
{
auto curShape = QGraphicsRectItem::shape();
curShape.addPath( mapFromItem(&m_anchorBottomLeft, m_anchorBottomLeft.shape()) );
curShape.addPath( mapFromItem(&m_anchorTopRight, m_anchorTopRight.shape()) );
return curShape;
}
What I get, however, is that now clicks inside the handles areas are completely ignored and only clicks in the central area of the rectangle are handled.
What is the correct way to extend the clickable area of an item when it has a non-trivial shape?
Update: I tried to set the ItemIsSelectable flag on a handle and now, if I click on it, I see that it gets selected. I still don't get any mousePressEvent in the parent, however. What am I doing wrong?
Edit:
This is the constructor implementation:
QGraphicsBoxWithHandlesItem::QGraphicsBoxWithHandlesItem( QRectF r, float handlesRadius, QGraphicsItem * parent) :
QGraphicsRectItem(parent),
m_anchorRadius(handlesRadius),
m_activeAnchor(None)
{
setFlag(QGraphicsItem::ItemIsMovable);
setFlag(QGraphicsItem::ItemIsSelectable);
setRect(r);
m_anchorBottomLeft.setRect(-m_anchorRadius, -m_anchorRadius, m_anchorRadius*2, m_anchorRadius*2);
m_anchorBottomLeft.setPos(rect().bottomLeft());
m_anchorBottomLeft.setSpanAngle(90 * 16); // angle is in 16ths of degree
m_anchorBottomLeft.setParentItem(this);
m_anchorTopRight.setRect(-m_anchorRadius, -m_anchorRadius, m_anchorRadius*2, m_anchorRadius*2);
m_anchorTopRight.setPos(rect().topRight());
m_anchorTopRight.setStartAngle(180 * 16); // angle is in 16ths of degree
m_anchorTopRight.setSpanAngle(90 * 16); // angle is in 16ths of degree
m_anchorTopRight.setParentItem(this);
}
Your QGraphicsEllipseItem are on top of the base item so the mouse events will never come to you.
What you have to do is use a sceneEventFilter, but since the QGraphicsEllipseItem are children of the main item they will never move, so they should not have a parent but you should add them directly to the scene.
The full functionality is implemented in the following code:
*.h
#ifndef QGRAPHICSBOXWITHHANDLESITEM_H
#define QGRAPHICSBOXWITHHANDLESITEM_H
#include <QGraphicsRectItem>
#include <QObject>
class QGraphicsBoxWithHandlesItem : public QObject, public QGraphicsRectItem
{
Q_OBJECT
enum ActiveAnchor{
None,
BottomLeft,
TopRight
};
public:
QGraphicsBoxWithHandlesItem(QRectF r, float handlesRadius = 20.0, QGraphicsItem *parent = nullptr);
protected:
QVariant itemChange(GraphicsItemChange change, const QVariant &value);
bool sceneEventFilter(QGraphicsItem *watched, QEvent *event);
private:
QGraphicsEllipseItem m_anchorBottomLeft;
QGraphicsEllipseItem m_anchorTopRight;
float m_anchorRadius;
ActiveAnchor m_activeAnchor;
};
#endif // QGRAPHICSBOXWITHHANDLESITEM_H
*.cpp
#include "qgraphicsboxwithhandlesitem.h"
#include <QEvent>
#include <QGraphicsScene>
#include <QDebug>
QGraphicsBoxWithHandlesItem::QGraphicsBoxWithHandlesItem( QRectF r, float handlesRadius, QGraphicsItem * parent) :
QGraphicsRectItem(parent),
m_anchorRadius(handlesRadius),
m_activeAnchor(None)
{
setFlag(QGraphicsItem::ItemIsMovable);
setFlag(QGraphicsItem::ItemIsSelectable);
setFlag(QGraphicsItem::ItemSendsGeometryChanges);
setRect(r);
m_anchorBottomLeft.setRect(-m_anchorRadius, -m_anchorRadius, m_anchorRadius*2, m_anchorRadius*2);
m_anchorBottomLeft.setPos(rect().bottomLeft());
m_anchorBottomLeft.setSpanAngle(90 * 16); // angle is in 16ths of degree
//m_anchorBottomLeft.setParentItem(this);
m_anchorBottomLeft.setFlag(QGraphicsItem::ItemIsMovable);
m_anchorBottomLeft.setFlag(QGraphicsItem::ItemIsSelectable);
m_anchorTopRight.setRect(-m_anchorRadius, -m_anchorRadius, m_anchorRadius*2, m_anchorRadius*2);
m_anchorTopRight.setPos(rect().topRight());
m_anchorTopRight.setStartAngle(180 * 16); // angle is in 16ths of degree
m_anchorTopRight.setSpanAngle(90 * 16); // angle is in 16ths of degree
//m_anchorTopRight.setParentItem(this);
m_anchorTopRight.setFlag(QGraphicsItem::ItemIsMovable);
m_anchorTopRight.setFlag(QGraphicsItem::ItemIsSelectable);
}
QVariant QGraphicsBoxWithHandlesItem::itemChange(GraphicsItemChange change, const QVariant & value){
if(change == QGraphicsItem::ItemSceneHasChanged){
if(scene()){
scene()->addItem(&m_anchorBottomLeft);
scene()->addItem(&m_anchorTopRight);
m_anchorBottomLeft.installSceneEventFilter(this);
m_anchorTopRight.installSceneEventFilter(this);
}
}
else if (change == QGraphicsItem::ItemPositionHasChanged) {
m_anchorBottomLeft.setPos(mapToScene(rect().bottomLeft()));
m_anchorTopRight.setPos(mapToScene(rect().topRight()));
}
return QGraphicsRectItem::itemChange(change, value);
}
bool QGraphicsBoxWithHandlesItem::sceneEventFilter(QGraphicsItem *watched, QEvent *event)
{
if(watched == &m_anchorTopRight){
switch (event->type()) {
case QEvent::GraphicsSceneMousePress:{
//mousePressEvent
qDebug()<<"mousePressEvent m_anchorTopRight";
break;
}
case QEvent::GraphicsSceneMouseMove:{
// mouseMoveEvent
QRectF r = rect();
auto p = m_anchorTopRight.mapToScene(m_anchorTopRight.rect().center());
r.setTopRight(mapFromScene(p));
setRect(r);
qDebug()<<"mouseMoveEvent m_anchorTopRight";
break;
}
case QEvent::GraphicsSceneMouseRelease :{
//mouseReleaseEvent
qDebug()<<"mouseReleaseEvent m_anchorTopRight";
break;
}
}
}
if(watched == &m_anchorBottomLeft){
switch (event->type()) {
case QEvent::GraphicsSceneMousePress:{
//mousePressEvent
qDebug()<<"mousePressEvent m_anchorBottomLeft";
break;
}
case QEvent::GraphicsSceneMouseMove:{
// mouseMoveEvent
QRectF r = rect();
auto p = m_anchorBottomLeft.mapToScene(m_anchorBottomLeft.rect().center());
r.setBottomLeft(mapFromScene(p));
setRect(r);
qDebug()<<"mouseMoveEvent m_anchorBottomLeft";
break;
}
case QEvent::GraphicsSceneMouseRelease :{
//mouseReleaseEvent
qDebug()<<"mouseReleaseEvent m_anchorBottomLeft";
break;
}
}
}
return QGraphicsRectItem::sceneEventFilter(watched, event);
}
I did sub-classing to include mouse click function. Here, a rectangle can be chosen by mousePressEvent, mouseMoveEvent and mouseReleaseEvent. When I am trying to chose another rectangle, my previous rectangle is not being removed. It is still displaying with my previous drawn rectangle, which I don't want to display. I want to chose and display only one rectangle. I meant when I press again to chose another rectange, the previous one should be removed.
I included here my subclass named mouse_crop
mouse_crop.h is as follows
#ifndef MOUSE_CROP_H
#define MOUSE_CROP_H
#include <QMainWindow>
#include <QObject>
#include <QWidget>
#include <QMouseEvent>
#include <QLabel>
#include <QRubberBand>
class mouse_crop : public QLabel
{
Q_OBJECT
public:
mouse_crop(QWidget *parent=0);
QRubberBand *rubberBand;
QPoint origin, ending;
protected:
void mousePressEvent(QMouseEvent *ev);
void mouseMoveEvent(QMouseEvent *ev);
void mouseReleaseEvent(QMouseEvent *ev);
signals:
void sendMousePosition(QPoint&);
void sendMouseEnding(QPoint&);
};
#endif // MOUSE_CROP_H`
And mouse_crop.cpp is as follows
#include "mouse_crop.h"
mouse_crop::mouse_crop(QWidget *parent):QLabel (parent)
{
}
void mouse_crop::mousePressEvent(QMouseEvent *ev)
{
origin = ev->pos();
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
if(ev->button()== Qt::LeftButton || ev->button()== Qt::RightButton)
{
rubberBand->show();
emit sendMousePosition(origin);
}
}
void mouse_crop::mouseMoveEvent(QMouseEvent *ev)
{
rubberBand->setGeometry(QRect(origin, ev->pos()).normalized());
}
void mouse_crop::mouseReleaseEvent(QMouseEvent *ev)
{
ending = ev->globalPos();
if(ev->button()== Qt::LeftButton || ev->button()== Qt::RightButton)
{
emit sendMouseEnding(ending);
}
}
Can any one tell me how to solve this? Thanks in advance.
The problem is caused because every time you press the mouse you are creating a new QRubberBand, what you must do is create only a QRubberBand, hide it and show it when necessary.
mouse_crop::mouse_crop(QWidget *parent)
: QLabel(parent)
{
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
rubberBand->hide();
}
void mouse_crop::mousePressEvent(QMouseEvent *ev)
{
origin = ev->pos();
rubberBand->setGeometry(QRect(origin, origin));
if(ev->button()== Qt::LeftButton || ev->button()== Qt::RightButton)
{
rubberBand->show();
emit sendMousePosition(origin);
}
}
void mouse_crop::mouseMoveEvent(QMouseEvent *ev)
{
rubberBand->setGeometry(QRect(origin, ev->pos()).normalized());
}
void mouse_crop::mouseReleaseEvent(QMouseEvent *ev)
{
ending = ev->globalPos();
if(ev->button()== Qt::LeftButton || ev->button()== Qt::RightButton)
{
emit sendMouseEnding(ending);
}
}
I occasionally develop scientific simulation software and want to visualize results on a 2D view.
Is there any ready made (preferably open source) Win32 class which creates a drawing canvas with
zoom,
snapshot and
possibly video capture capability?
I need limited API which includes fast pixel drawing, draw bmp from memory buffer and possibly texts.
Look at Qt together with OpenGl. Here is some code I frequently use, that might be of help for you
//piview.h
#include <QGraphicsView>
#include <QGraphicsScene>
class PiView : public QGraphicsView{
Q_OBJECT
public:
explicit PiView(QGraphicsScene * =0, QWidget *parent = 0);
void enableSmoothRendering();
void enableOpenGl();
void fit();
void renderToFile(QString const& path)const;
private:
//void mouseMoveEvent(QMouseEvent *event);
virtual void mousePressEvent(QMouseEvent *event);
virtual void wheelEvent(QWheelEvent *event);
virtual void resizeEvent(QResizeEvent * event );
signals:
void pointRightClicked(QPointF) const;
void pointLeftClicked(QPointF) const;
};
PiView* createMyPiView(QGraphicsScene* pScene);
//piview.cpp
#include "piview.h"
#ifdef OPENGL
#include <QtOpenGL>
#include <QGLWidget>
#endif
#include <QMouseEvent>
#include <QWheelEvent>
#include <qdebug.h>
PiView::PiView(QGraphicsScene* scene, QWidget *parent) : QGraphicsView(scene , parent){
this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
this->setDragMode(QGraphicsView::ScrollHandDrag);
}
void PiView::enableSmoothRendering(){
this->setRenderHints(QPainter::Antialiasing|QPainter::TextAntialiasing|QPainter::SmoothPixmapTransform);
}
#ifdef OPENGL
void PiView::enableOpenGl(){
QGLFormat fmt = QGLFormat::defaultFormat();
fmt.setSampleBuffers(true);
fmt.setDoubleBuffer(true);
//fmt.setSamples(256);
fmt.setDirectRendering(true);
//qDebug() << "SampleBuffers:" << fmt.sampleBuffers() << fmt.samples();
this->setViewport(new QGLWidget(fmt));
this->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
this->setCacheMode(QGraphicsView::CacheBackground);
//this->scene()->setItemIndexMethod(QGraphicsScene::NoIndex);
}
#else
void PiView::enableOpenGl(){
qDebug() << "Opengl was not enabled, enable it using opengl as qmake parameter";
}
#endif
void PiView::mousePressEvent(QMouseEvent *event){
if (event->button()==Qt::RightButton) {
emit pointRightClicked(mapToScene(event->pos()));
}
else if (event->button()==Qt::LeftButton) {
emit pointLeftClicked(mapToScene(event->pos()));
}
QGraphicsView::mousePressEvent(event);
}
//Zooming using the mousewheel
void PiView::wheelEvent(QWheelEvent *event){
double scaleFactor = 1.15; //How fast we zoom
if (event->orientation()==Qt::Vertical){
if(event->delta() > 0) {
//Zoom in
scale(scaleFactor, scaleFactor);
} else {
//Zooming out
scale(1.0 / scaleFactor, 1.0 / scaleFactor);
}
}
else if (event->orientation()==Qt::Horizontal) {
if(event->delta() > 0) {
scroll(10,0);
}
else {
scroll(-10,0);
}
}
}
void PiView::resizeEvent(QResizeEvent *event){
this->fit();
QGraphicsView::resizeEvent(event);
}
void PiView::fit(){
this->fitInView(this->sceneRect(),Qt::KeepAspectRatio);
}
PiView* createMyPiView(QGraphicsScene* pScene){
PiView* view = new PiView(pScene);
view->enableOpenGl();
view->enableSmoothRendering();
return view;
}
For Snapshots take a look at the following:
void SceneInteractor::saveSelection(){
if (mpScene->selectedItems().isEmpty()) return;
QList<QGraphicsItem*> items(mpScene->selectedItems());
QRect sourceRect;
boostForeach(QGraphicsItem* item, items) {
sourceRect = sourceRect.united(item->sceneBoundingRect().toRect());
}
QImage img(sourceRect.size(),QImage::Format_ARGB32_Premultiplied);
img.fill(Qt::transparent);
QPainter p(&img);
p.setRenderHint(QPainter::Antialiasing);
QGraphicsView::CacheMode cache = mpView->cacheMode();
mpView->setCacheMode(QGraphicsView::CacheNone);
mpView->scene()->clearSelection();
mpView->scene()->render(&p, QRectF(), sourceRect);
p.end();
QString path = QFileDialog::getSaveFileName(0,tr("Choose Image-File Destination"));
img.save(path,"PNG",100);
mpView->setCacheMode(cache);
}