I have a custom QGraphicsView and QGraphicsScene. Inside QGraphicsScene I have overriden void drawBackground(QPainter *painter, const QRectF &rect) and based on a boolean flag I want to toggle a grid on and off. I tried calling clear() or calling the painter's eraseRect(sceneRect()) inside my function but it didn't work. So after doing some reading I guess it wasn't supposed to work since after changing the scene you need to refresh the view. That's why I'm emitting a signal called signalUpdateViewport()
void Scene::drawBackground(QPainter *painter, const QRectF &rect) {
if(this->gridEnabled) {
// Draw grid
}
else {
// Erase using the painter
painter->eraseRect(sceneRect());
// or by calling
//clear();
}
// Trigger refresh of view
emit signalUpdateViewport();
QGraphicsScene::drawBackground(painter, rect);
}
which is then captured by my view:
void View::slotUpdateViewport() {
this->viewport()->update();
}
Needless to say this didn't work. With doesn't work I mean that the results (be it a refresh from inside the scene or inside the view) are made visible only when changing the widget for example triggering a resize event.
How do I properly refresh the view to my scene to display the changed that I have made in the scene's background?
The code:
scene.h
#ifndef SCENE_HPP
#define SCENE_HPP
#include <QGraphicsScene>
class View;
class Scene : public QGraphicsScene
{
Q_OBJECT
Q_ENUMS(Mode)
Q_ENUMS(ItemType)
public:
enum Mode { Default, Insert, Select };
enum ItemType { None, ConnectionCurve, ConnectionLine, Node };
Scene(QObject* parent = Q_NULLPTR);
~Scene();
void setMode(Mode mode, ItemType itemType);
signals:
void signalCursorCoords(int x, int y);
void signalSceneModeChanged(Scene::Mode sceneMode);
void signalSceneItemTypeChanged(Scene::ItemType sceneItemType);
void signalGridDisplayChanged(bool gridEnabled);
void signalUpdateViewport();
public slots:
void slotSetSceneMode(Scene::Mode sceneMode);
void slotSetSceneItemType(Scene::ItemType sceneItemType);
void slotSetGridStep(int gridStep);
void slotToggleGrid(bool flag);
private:
Mode sceneMode;
ItemType sceneItemType;
bool gridEnabled;
int gridStep;
void makeItemsControllable(bool areControllable);
double round(double val);
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event);
void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
void keyPressEvent(QKeyEvent *event);
void drawBackground(QPainter *painter, const QRectF &rect);
};
Q_DECLARE_METATYPE(Scene::Mode)
Q_DECLARE_METATYPE(Scene::ItemType)
#endif // SCENE_HPP
scene.cpp
#include <QGraphicsItem>
#include <QGraphicsView>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
#include <QRectF>
#include <QKeyEvent>
#include <QtDebug>
#include "scene.h"
Scene::Scene(QObject* parent)
: QGraphicsScene (parent),
sceneMode(Default),
sceneItemType(None),
gridEnabled(true),
gridStep(30)
{
}
Scene::~Scene()
{
}
void Scene::setMode(Mode mode, ItemType itemType)
{
this->sceneMode = mode;
this->sceneItemType = itemType;
QGraphicsView::DragMode vMode = QGraphicsView::NoDrag;
switch(mode) {
case Insert:
{
makeItemsControllable(false);
vMode = QGraphicsView::NoDrag;
break;
}
case Select:
{
makeItemsControllable(true);
vMode = QGraphicsView::RubberBandDrag;
break;
}
case Default:
{
makeItemsControllable(false);
vMode = QGraphicsView::NoDrag;
break;
}
}
QGraphicsView* mView = views().at(0);
if(mView) {
mView->setDragMode(vMode);
}
}
void Scene::slotSetSceneMode(Scene::Mode sceneMode)
{
this->sceneMode = sceneMode;
qDebug() << "SM" << (int)this->sceneMode;
emit signalSceneModeChanged(this->sceneMode);
}
void Scene::slotSetSceneItemType(Scene::ItemType sceneItemType)
{
this->sceneItemType = sceneItemType;
qDebug() << "SIT:" << (int)this->sceneItemType;
emit signalSceneItemTypeChanged(this->sceneItemType);
}
void Scene::slotSetGridStep(int gridStep)
{
this->gridStep = gridStep;
}
void Scene::slotToggleGrid(bool flag)
{
this->gridEnabled = flag;
invalidate(sceneRect(), BackgroundLayer);
qDebug() << "Grid " << (this->gridEnabled ? "enabled" : "disabled");
update(sceneRect());
emit signalGridDisplayChanged(this->gridEnabled);
}
void Scene::makeItemsControllable(bool areControllable)
{
foreach(QGraphicsItem* item, items()) {
if(/*item->type() == QCVN_Node_Top::Type
||*/ item->type() == QGraphicsLineItem::Type
|| item->type() == QGraphicsPathItem::Type) {
item->setFlag(QGraphicsItem::ItemIsMovable, areControllable);
item->setFlag(QGraphicsItem::ItemIsSelectable, areControllable);
}
}
}
double Scene::round(double val)
{
int tmp = int(val) + this->gridStep/2;
tmp -= tmp % this->gridStep;
return double(tmp);
}
void Scene::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
QGraphicsScene::mousePressEvent(event);
}
void Scene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
emit signalCursorCoords(int(event->scenePos().x()), int(event->scenePos().y()));
QGraphicsScene::mouseMoveEvent(event);
}
void Scene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
QGraphicsScene::mouseReleaseEvent(event);
}
void Scene::keyPressEvent(QKeyEvent* event)
{
if(event->key() == Qt::Key_M) {
slotSetSceneMode(static_cast<Mode>((int(this->sceneMode) + 1) % 3));
}
else if(event->key() == Qt::Key_G) {
slotToggleGrid(!this->gridEnabled);
}
QGraphicsScene::keyPressEvent(event);
}
void Scene::drawBackground(QPainter *painter, const QRectF &rect)
{
// FIXME Clearing and drawing the grid happens only when scene size or something else changes
if(this->gridEnabled) {
painter->setPen(QPen(QColor(200, 200, 255, 125)));
// draw horizontal grid
double start = round(rect.top());
if (start > rect.top()) {
start -= this->gridStep;
}
for (double y = start - this->gridStep; y < rect.bottom(); ) {
y += this->gridStep;
painter->drawLine(int(rect.left()), int(y), int(rect.right()), int(y));
}
// now draw vertical grid
start = round(rect.left());
if (start > rect.left()) {
start -= this->gridStep;
}
for (double x = start - this->gridStep; x < rect.right(); x += this->gridStep) {
painter->drawLine(int(x), int(rect.top()), int(x), int(rect.bottom()));
}
}
else {
// Erase whole scene's background
painter->eraseRect(sceneRect());
}
slotToggleGrid(this->gridEnabled);
QGraphicsScene::drawBackground(painter, rect);
}
The View currently doesn't contain anything - all is default. The setting of my Scene and View instance inside my QMainWindow is as follows:
void App::initViewer()
{
this->scene = new Scene(this);
this->view = new View(this->scene, this);
this->viewport = new QGLWidget(QGLFormat(QGL::SampleBuffers), this->view);
this->view->setRenderHints(QPainter::Antialiasing);
this->view->setViewport(this->viewport);
this->view->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
this->view->setCacheMode(QGraphicsView::CacheBackground);
setCentralWidget(this->view);
}
EDIT: I tried calling invalidate(sceneRect(), BackgroundLayer) before the update(), clear() or whatever I tried to trigger a refresh.
I also tried QGraphicsScene::update() from within the scene but it didn't work Passing both no argument to the function call and then passing sceneRect() didn't result in anything different then what I've described above.
Found the issue - I forgot to set the size of the scene's rectangle:
this->scene->setSceneRect(QRectF(QPointF(-1000, 1000), QSizeF(2000, 2000)));
I actually found the problem by deciding to print the size of the QRectF returned by the sceneRect() call and when I looked at the output I saw 0, 0 so basically I was indeed triggering the update but on a scene with the area of 0 which (obviously) would result in nothing.
Another thing that I tested and worked was to remove the background caching of my view.
When you change your grid settings, whether it's on or off (or color, etc.), you need to call QGraphicsScene::update. That's also a slot, so you can connect a signal to it if you want. You can also specify the exposed area; if you don't, then it uses a default of everything. For a grid, that's probably what you want.
You don't need to clear the grid. The update call ensures that the updated area gets cleared, and then you either paint on it if you want the grid, or don't paint on it if the grid shouldn't be there.
Sometimes when the view/scene refuse to run update() directly, processing the app events before the update() fixes it, like so:
qApp->processEvents();
update();
Related
I'm working on a TreeViewNode class derived from QObject and QGraphicsItem, responsible for creating the individual nodes of the tree view (which displays a family tree) and adding them to the scene. I made sure to include the setFlag(QGraphicsItem::ItemIsSelectable); method in the class constructor and I overloaded mousePressEvent like so:
void TreeViewNode::mousePressEvent(QGraphicsSceneMouseEvent *event) {
if (event->button() == Qt::LeftButton) {
qDebug() << "Mouse pressed on node: " << m_node->getPatient()->get_Name().c_str();
emit clicked(this);
}
QGraphicsItem::mousePressEvent(event);
}
The clicked signal is defined in the header file as void clicked(TreeViewNode* node);
Then I have a updateSelectedPatient slot in MainWindow
void MainWindow::updateSelectedPatient(TreeViewNode* node) {
selected->setSelectedPatient(node->getNode()->getPatient());
}
and the corresponding connect statement in the MainWindow constructor
connect(treeView, &TreeViewNode::clicked, this, &MainWindow::updateSelectedPatient);
When I execute I see the nodes that have been added to the scene in the view but when I click on them nothing happens (I know through debugging and also because the setSelectedPatient() method updates a widget that shows the patient information of the currently selected patient); it seems like mousePressEvent() is not being called at all.. any help would be really appreciated, thank you for your time!
edit:
//TreeViewNode.hpp
class TreeViewNode : public QObject, public QGraphicsItem {
Q_OBJECT
Q_INTERFACES(QGraphicsItem)
private:
node* m_node;
Family_tree* m_family;
QGraphicsScene* m_scene;
static std::set<node*> addedNodes;
protected:
void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
public:
TreeViewNode(node* node, Family_tree* family, QGraphicsScene* scene, QGraphicsItem* parent = nullptr);
TreeViewNode* getTreeViewNode(node* n);
node* getNode() const;
static void clearAddedNodes();
void updateNode(Patient& patient);
QRectF boundingRect() const override;
void drawBranches(QPainter* painter);
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
signals:
void clicked(TreeViewNode* node);
};
//TreeViewNode.cpp
TreeViewNode::TreeViewNode(node* node, Family_tree* family, QGraphicsScene* scene, QGraphicsItem* parent) : QGraphicsItem(parent), m_node(node), m_family(family), m_scene(scene) {
if (m_node == m_family->get_root()) {m_scene->addItem(this);}
addedNodes.insert(m_node);
setFlag(QGraphicsItem::ItemIsSelectable);
qDebug() << "Scene item count: " << m_scene->items().count();
if (m_node->getFather()) {
auto father = m_node->getFather();
if (addedNodes.count(father) > 0) {return;}
TreeViewNode* fatherNode = new TreeViewNode(m_node->getFather(), family, m_scene, this);
fatherNode->setPos(-50, -100);
}
if (m_node->getMother()) {
auto mother = m_node->getMother();
if (addedNodes.count(mother) > 0) {return;}
TreeViewNode* motherNode = new TreeViewNode(m_node->getMother(), family, m_scene, this);
motherNode->setPos(50, -100);
}
if (m_node->getSpouse()) {
auto spouse = m_node->getSpouse();
if (addedNodes.count(spouse) > 0) {return;}
TreeViewNode* spouseNode = new TreeViewNode(m_node->getSpouse(), family, m_scene, this);
spouseNode->setPos(100, 0);
}
int childCount = 0;
for (auto child : m_node->getChildren()) {
if (addedNodes.count(child) > 0) {return;}
TreeViewNode* childNode = new TreeViewNode(child, family, m_scene, this);
childCount++;
double xPos, yPos;
xPos = pos().x() + 50 + 100 * (childCount - (m_node->getChildren().size() + 1) / 2.0);
yPos = pos().y() + 100;
childNode->setPos(xPos, yPos);
}
}
You could use a normal event filter and check if is it a mouse press, release or move event then cast it to a mouse event and then just do whatever you want with it.
Example:
bool YourWidgetClass::eventFilter(QObject* object, QEvent* event)
{
if (object == YourInstance)
{
switch (event->type())
{
case QEvent::GraphicsSceneMousePress:
{
QMouseEvent * mouseEvent = static_cast<QMouseEvent*>(event);
switch (mouseEvent->button())
{
case Qt::LeftButton:
// Handle left button pressed here
return true;
case Qt::RightButton:
// Handle right button pressed here
return true;
default:
break;
}
break;
}
}
}
Don't forget to install the event filter on your class
YourClassCtor->installEventFilter(this)
I don't see from your question that you need it for anything special. the signal &QGraphicsScene::selectionChanged is not enough for you? Is the item set to interactive?
So simply put:
declare a slot:
public slots:
void selectionChanged();
define the slot:
void MainWindow::selectionChanged()
{
qDebug() << ...... ;
}
and connect:
connect(scene,&QGraphicsScene::selectionChanged,this,&MainWindow::selectionChanged);
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'm learning how to use cocos2d-x by following the gamesfromscratch tutorial. I got to this part when I noticed this problem with the positioning:
This basic app basically draws on screen the label "You Touched Here" at the position where you click. Whenever I clicked though, the label would appear well above where I clicked.
In the screenshot above, I clicked at the origin. In the output log, you can see that the touch point (specifically: touch->getLocation(), unconverted) is recorded as (0, 166), where it should be (0, 0).
I tried using other position functions, as well as converting the touch coordinates to other coordinate types, but the problem still persisted.
Below is the code for this simple app:
AppDelegate.h
#pragma once
#include "cocos2d.h"
class AppDelegate : private cocos2d::Application {
public:
AppDelegate();
virtual ~AppDelegate();
virtual bool applicationDidFinishLaunching();
virtual void applicationDidEnterBackground();
virtual void applicationWillEnterForeground();
};
AppDelegate.cpp
#include "AppDelegate.h"
// These header files are not used currently
//#include "HelloWorldScene.h"
//#include "GraphicsScene.h"
//#include "TouchScene.h"
#include "TouchScene2.h"
USING_NS_CC;
AppDelegate::AppDelegate() {
}
AppDelegate::~AppDelegate() {
}
bool AppDelegate::applicationDidFinishLaunching() {
auto director = Director::getInstance();
auto glView = director->getOpenGLView();
if (!glView) {
glView = GLViewImpl::create("Hello World");
glView->setFrameSize(640, 480);
director->setOpenGLView(glView);
}
auto scene = TouchScene2::createScene();
director->runWithScene(scene);
return true;
}
void AppDelegate::applicationDidEnterBackground() {
}
void AppDelegate::applicationWillEnterForeground() {
}
TouchScene2.h
#pragma once
#include "cocos2d.h"
class TouchScene2 : public cocos2d::Layer
{
public:
static cocos2d::Scene* createScene();
virtual bool init();
virtual bool onTouchBegan(cocos2d::Touch*, cocos2d::Event*);
virtual void onTouchEnded(cocos2d::Touch*, cocos2d::Event*);
virtual void onTouchMoved(cocos2d::Touch*, cocos2d::Event*);
virtual void onTouchCancelled(cocos2d::Touch*, cocos2d::Event*);
CREATE_FUNC(TouchScene2);
private:
cocos2d::Label* labelTouchInfo;
};
TouchScene2.cpp
#include "TouchScene2.h"
USING_NS_CC;
Scene* TouchScene2::createScene()
{
auto scene = Scene::create();
auto layer = TouchScene2::create();
scene->addChild(layer);
return scene;
}
bool TouchScene2::init()
{
if (!Layer::init())
{
return false;
}
labelTouchInfo = Label::createWithSystemFont("Touch or clicksomewhere to begin", "Arial", 30);
labelTouchInfo->setPosition(Vec2(
Director::getInstance()->getVisibleSize().width / 2,
Director::getInstance()->getVisibleSize().height / 2));
auto touchListener = EventListenerTouchOneByOne::create();
touchListener->onTouchBegan = CC_CALLBACK_2(TouchScene2::onTouchBegan, this);
touchListener->onTouchEnded = CC_CALLBACK_2(TouchScene2::onTouchEnded, this);
touchListener->onTouchMoved = CC_CALLBACK_2(TouchScene2::onTouchMoved, this);
touchListener->onTouchCancelled = CC_CALLBACK_2(TouchScene2::onTouchCancelled, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, this);
this->addChild(labelTouchInfo);
return true;
}
bool TouchScene2::onTouchBegan(Touch* touch, Event* event)
{
std::stringstream output;
output << "Touch Pos: (" << touch->getLocation().x << ", " << touch- >getLocation().y << ")" << std::endl;
log(output.str().c_str());
labelTouchInfo->setPosition(touch->getLocation());
labelTouchInfo->setString("You Touched Here");
return true;
}
void TouchScene2::onTouchEnded(Touch* touch, Event* event)
{
cocos2d::log("touch ended");
}
void TouchScene2::onTouchMoved(Touch* touch, Event* event)
{
cocos2d::log("touch moved");
}
void TouchScene2::onTouchCancelled(Touch* touch, Event* event)
{
cocos2d::log("touch cancelled");
}
One thing to point out is that the tutorial I'm following is several years old (written in 2015 I believe). The author is using version 3.3 beta, while I'm using the latest version 3.17.1. Could this be part of the problem?
And, regardless, how do I fix this issue so that the origin is (0, 0) as it should be?
your TouchScene2.cpp and TouchScene2.hpp seems fine
Problem is in your AppDelegate.cpp where you set
glView->setFrameSize(640, 480);
director->setOpenGLView(glView);
Instead of these try the following code.
You have fixed the FrameSize and hasn't set ContentScaleFactor. Set it as in the Cocos2d-x sample project
director->setOpenGLView(glview);
// Set the design resolution
glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::NO_BORDER);
Size frameSize = glview->getFrameSize();
vector<string> searchPath;
if (frameSize.height > mediumResource.size.height)
{
searchPath.push_back(largeResource.directory);
director->setContentScaleFactor(MIN(largeResource.size.height/designResolutionSize.height, largeResource.size.width/designResolutionSize.width));
}
// If the frame's height is larger than the height of small resource size, select medium resource.
else if (frameSize.height > smallResource.size.height)
{
searchPath.push_back(mediumResource.directory);
director->setContentScaleFactor(MIN(mediumResource.size.height/designResolutionSize.height, mediumResource.size.width/designResolutionSize.width));
}
// If the frame's height is smaller than the height of medium resource size, select small resource.
else
{
searchPath.push_back(smallResource.directory);
director->setContentScaleFactor(MIN(smallResource.size.height/designResolutionSize.height, smallResource.size.width/designResolutionSize.width));
}
I have 5 entities that can be added in graphics view on mouse events and button clicks. Each entity has been assigned the unique id. I need to add the operations cut, copy and paste on
these entities. How to proceed with that. I didn't get any example for cut, copy paste operations in graphics View in Qt. How can I do that?
I have different classes for all entities my class line, circle and ellipse are inherited from QGraphicsItem and line and text from QgraphicsLineItem and QgraphicsEllipse Item. Please tell me how can I work with them too.
line.cpp
#include "line.h"
Line::Line(int i, QPointF p1, QPointF p2)
{
// assigns id
id = i;
// set values of start point and end point of line
startP = p1;
endP = p2;
}
int Line::type() const
{
// Enable the use of qgraphicsitem_cast with line item.
return Type;
}
QRectF Line::boundingRect() const
{
qreal extra = 1.0;
// bounding rectangle for line
return QRectF(line().p1(), QSizeF(line().p2().x() - line().p1().x(),
line().p2().y() - line().p1().y()))
.normalized()
.adjusted(-extra, -extra, extra, extra);
}
void Line::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
// draws/paints the path of line
QPen paintpen;
painter->setRenderHint(QPainter::Antialiasing);
paintpen.setWidth(1);
if (isSelected())
{
// sets brush for end points
painter->setBrush(Qt::SolidPattern);
paintpen.setColor(Qt::red);
painter->setPen(paintpen);
painter->drawEllipse(startP, 2, 2);
painter->drawEllipse(endP, 2, 2);
// sets pen for line path
paintpen.setStyle(Qt::DashLine);
paintpen.setColor(Qt::black);
painter->setPen(paintpen);
painter->drawLine(startP, endP);
}
else
{
painter->setBrush(Qt::SolidPattern);
paintpen.setColor(Qt::black);
painter->setPen(paintpen);
painter->drawEllipse(startP, 2, 2);
painter->drawEllipse(endP, 2, 2);
painter->drawLine(startP, endP);
}
}
How can I work with this QGraphicsLineItem?
I think you should use custom graphics scene. Create QGraphicsScene subclass. Reimplement keyPressEvent:
if (e->key() == Qt::Key_C && e->modifiers() & Qt::ControlModifier)
{
listCopiedItems = this->selectedItems();
}
if (e->key() == Qt::Key_V && e->modifiers() & Qt::ControlModifier)
{
for(int i=0; i< listCopiedItems.count(); i++)
{
//create new objects, set position and properties
}
}
You can get all needed properties from old objects such as color, size etc and set to new. For cut do same thing but delete old objects from scene and when all work will be done, delete this objects from memory. Also you can create shortcuts with QShortcut class.
Edit. I want to say that it is very complicate task, so I can't get you code for all cases, for all types. I give just example, but this example works(I tested it). I post here absolutely full code.
Header:
#ifndef GRAPHICSSCENE_H
#define GRAPHICSSCENE_H
#include <QGraphicsScene>
#include <QStack>
#include <QPoint>
#include <QMouseEvent>
class GraphicsScene : public QGraphicsScene
{
Q_OBJECT
public:
explicit GraphicsScene(QObject *parent = 0);
signals:
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event);
void keyPressEvent(QKeyEvent *event);
public slots:
private:
QList<QGraphicsItem *> lst; QPoint last;
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
QVector<QGraphicsEllipseItem * > vec;
};
#endif // GRAPHICSSCENE_H
Cpp:
#include "graphicsscene.h"
#include <QDebug>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsItem>
GraphicsScene::GraphicsScene(QObject *parent) :
QGraphicsScene(parent)
{
//add something
addPixmap(QPixmap("G:/2/qt.jpg"));
vec.push_back(addEllipse(0,0,50,50,QPen(Qt::red),QBrush(Qt::blue)));
vec.push_back(addEllipse(0+100,0+100,50,50,QPen(Qt::red),QBrush(Qt::blue)));
vec.push_back(addEllipse(0+150,0+150,50,50,QPen(Qt::red),QBrush(Qt::blue)));
}
void GraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
//qDebug() << "in";
if (mouseEvent->button() == Qt::LeftButton)
{
last = mouseEvent->scenePos().toPoint();//remember this point, we need it for copying
QGraphicsItem *item = itemAt(mouseEvent->scenePos(), QTransform());
item->setFlags(QGraphicsItem::ItemIsSelectable);
item->setSelected(!item->isSelected());
}
QGraphicsScene::mousePressEvent(mouseEvent);
}
void GraphicsScene::keyPressEvent(QKeyEvent *e)
{
if (e->key() == Qt::Key_C && e->modifiers() & Qt::ControlModifier)
{
lst = this->selectedItems();
}
if (e->key() == Qt::Key_V && e->modifiers() & Qt::ControlModifier)
{
for(int i=0; i< lst.count(); i++)
{
//if it is ellipse
QGraphicsEllipseItem *ell = qgraphicsitem_cast<QGraphicsEllipseItem *>(lst.at(i));
if(ell)
{//then add ellipse to scene with ell properties and new position
addEllipse(QRect(last,ell->rect().size().toSize()),ell->pen(),ell->brush());
qDebug() << "good";
}
}
}
QGraphicsScene::keyPressEvent(e);
}
It is so complicate because you have no any clone() method, so you can't clone object with all all properties and move it to the new position. If you have your specific items that you should provide something specific too. That's why it is complex and I can't get code for all cases.
EDIT
You can't show scene, you should use this instead:
QGraphicsView vview;
GraphicsScene ss;
vview.setScene(&ss);
vview.show();