I want to change my QGraphicsItem position by resize event over graphicsview.
I scaled position by newSize/oldSize but my items stay at the same position.
I don't know what wrong with my code or a better way to change my items position.
bool cameraItems::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::Resize) {
QResizeEvent *e = static_cast<QResizeEvent*>(event);
if(obj == ui->graphicsView) {
setFixedSizeForGraphicsView(e->size(), e->oldSize());
ui->graphicsView->scene()->setSceneRect(0, 0, e->size().width(), e->size().height());
ui->graphicsView->setSceneRect(0, 0, e->size().width(), e-->size().height());
}
}
QWidget::eventFilter(obj, event);
}
void cameraItems::setFixedSizeForGraphicsView(QSize size, QSize oldSize)
{
foreach (singleCamera *cam, m_cameras) {
prevImageSize = cam->imageSize();
QPointF ppos = cam->pos();
QPointF newPos = QPointF((ppos.x()/prevSize.width())*size.width(), (ppos.y()/prevSize.height())*size.height());
cam->setPos(newPos);
}
}
You can make your custom class which inherits from QGraphicsView and then reimplement resizeEvent( QResizeEvent *event ) like:
void MyView::resizeEvent(QResizeEvent *event)
{
QRectF rect = this->scene()->itemsBoundingRect();
fitInView(rect, ,Qt::KeepAspectRatio);
QGraphicsView::resizeEvent(event);
}
This way the view will always display the whole scene. I.e. if the window size is changed and the graphicsView is resized, The scene gets scaled and you can see everything appropriately.
Related
I am having QGraphicsView which contains different QGraphicsItem's like rectangle,polyline,text etc. I have installed event filter through which I am managing mouse click and release event and creating rubber band rectangle. Whichever area comes under rubber band rectangle, it gets zoom in.
I am storing rubber band rectangle points in local variables like rubberBandOrigin ( mouse click position ) and rubberBandEnd ( mouse release position)
When I click on any item's, its paint() method gets called. I want to use rubberBandOrigin and rubberBandEnd variables in paint() method.
The way I was told that, you will get these variables ( means mouse click position and release position ) through paint() itself.
paint() has 3rd parameter QWidget *widget. Through this, you will get mouse click and release position (i.e. rubber band rectangle position)
Here is my code:
bool guiSchematicViewUtil::eventFilter(QObject *watched, QEvent *event)
{
bool filterEvent = false;
switch(event->type())
{
case QEvent::MouseButtonPress:
{
QMouseEvent * mouseEvent = static_cast<QMouseEvent *>(event);
rubberBandOrigin = mouseEvent->pos();
rubberBandActive = true;
break;
}
case QEvent::MouseButtonRelease:
{
if (rubberBandActive)
{
QMouseEvent * mouseEvent = static_cast<QMouseEvent *>(event);
rubberBandEnd = mouseEvent->pos();
QGraphicsView * view = static_cast<QGraphicsView *>(watched->parent());
QRectF zoomRectInScene = QRectF(view->mapToScene(rubberBandOrigin),
view->mapToScene(rubberBandEnd));
view->fitInView(zoomRectInScene, Qt::KeepAspectRatio);
rubberBandActive = false;
}
break;
}
default:
break;
}
return filterEvent;
}
paint method:
void guiSchematicRect::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
// Here I want rubberBandOrigin and rubberBandEnd co-ordinates using QWidget* widget parameter
}
I tried with api like parent(), parentWidget() of QWidget but did not get it.
Can any one help me ?
I am building a Qt5 application that allows a user to draw a rubberband with his mouse over an image, to select certain area of the image for further processing.
I got my code to only allow the user to start drawing the rubberband, by subclassing a QLabel to a custom class (frame_displayer) which mousePressEvent() is overridden and thus be only invoked when the mouse press happens within the custom classed widget.
The problem is that when the initial click is inside the frame_displayer, mouseMoveEvent(), the function that I use to change the rubberband size accordingly, keeps getting called even when the mouse cursor has been dragged outside of the frame_displayer.
I have tried using leaveEvent() and enterEvent() to control a class boolean flag that codes inside mouseMoveEvent could rely on to know whether the cursor is still within the widget. However, both leaveEvent() and enterEvent() are only called while a mouse button is not being held, thus rendering them no use for constraining the rubberband.
Also, the underMouse() always return true, for a reason unknown to me.
Segment of frame_displayer.cpp
frame_displayer::frame_displayer(QWidget * parent) : QLabel(parent)
{
_rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
}
void frame_displayer::mousePressEvent(QMouseEvent *event)
{
_lastClickedBtn = event->button();
if (_lastClickedBtn == Qt::LeftButton)
{
_mouseOriginClickPoint = event->pos();
_rubberBand->setGeometry(QRect(_mouseOriginClickPoint, _mouseClickPoint));
_rubberBand->show();
}
}
void frame_displayer::mouseMoveEvent(QMouseEvent *event)
{
if(_rubberBand != nullptr)
{
if (this->underMouse())
{
if (_lastClickedBtn == Qt::LeftButton)
{
QPoint mouseCurrentPoint = event->pos();
_rubberBand->setGeometry(QRect(_mouseOriginClickPoint, mouseCurrentPoint).normalized());
}
}
}
}
void frame_displayer::mouseReleaseEvent(QMouseEvent *event)
{
_mouseOriginClickPoint = QPoint();
_lastClickedBtn = Qt::MidButton;
if(_rubberBand != nullptr)
{
_rubberBand->hide();
_rubberBand->clearMask();
}
}
void frame_displayer::leaveEvent(QEvent *event)
{
qDebug() << "Leaving";
}
void frame_displayer::enterEvent(QEvent *event)
{
qDebug() << "Entering";
}
Thanks in advance!
I think that's the expected behaviour. If you want to limit the extents of the rubber band then simply clamp them in the mouseMoveEvent override...
void frame_displayer::mouseMoveEvent(QMouseEvent *event)
{
if(_rubberBand != nullptr)
{
if (this->underMouse())
{
if (_lastClickedBtn == Qt::LeftButton)
{
QPoint mouseCurrentPoint = event->pos();
/*
* Clamp mouseCurrentPoint to the QRect of this widget.
*/
auto clamp_rect = rect();
mouseCurrentPoint.rx() = std::min(clamp_rect.right(), std::max(clamp_rect.left(), mouseCurrentPoint.x()));
mouseCurrentPoint.ry() = std::min(clamp_rect.bottom(), std::max(clamp_rect.top(), mouseCurrentPoint.y()));
_rubberBand->setGeometry(QRect(_mouseOriginClickPoint, mouseCurrentPoint).normalized());
}
}
}
}
I have a GraphicsBoxItem that holds a list of GraphicsImageItem (some kind of floating buttons around the box). I create them on the fly in focusInEvent() and destroy them in focusOutEvent().
Some of the GraphicsImageItem should move with the parent when clicked, some of them should stay at the same spot when clicked, until the box gets a focusOut by clicking outside of any graphics items.
Is there a way to prevent a child QGraphicsItem from being moved with the parent?
class GraphicsBoxItem : public QObject, public QGraphicsItem
{
Q_OBJECT
private:
QColor mColor;
QVector<GraphicsImageItem*> mItemList;
public:
GraphicsBoxItem(QGraphicsItem *parent = NULL)
:QGraphicsItem(parent)
{
mColor = Qt::lightGray;
setFlag(QGraphicsItem::ItemIsSelectable);
setFlag(QGraphicsItem::ItemIsFocusable);
}
QRectF boundingRect() const { return QRectF(50, 20, 100, 60); }
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
painter->setBrush(mColor);
painter->drawRect(boundingRect());
}
virtual void focusOutEvent(QFocusEvent * event)
{
foreach(GraphicsImageItem *item1, mItemList)
{
item1->deleteLater();
item1 = NULL;
}
mItemList.clear();
mColor = Qt::lightGray;
QGraphicsItem::focusOutEvent(event);
}
virtual void focusInEvent(QFocusEvent * event)
{
GraphicsImageItem *movableItem = new GraphicsImageItem(this);
movableItem->setPos(150, 20);
mItemList.push_back(movableItem);
movableItem->installSceneEventFilter(this);
connect(movableItem, SIGNAL(SignalClicked()), this, SLOT(SlotButtonClicked()));
GraphicsImageItem *nonMovableItem = new GraphicsImageItem(this);
nonMovableItem->setPos(20, 20);
mItemList.push_back(nonMovableItem);
nonMovableItem->installSceneEventFilter(this);
connect(nonMovableItem, SIGNAL(SignalClicked()), this, SLOT(SlotButtonClicked()));
mColor = Qt::blue;
QGraphicsItem::focusInEvent(event);
}
bool sceneEventFilter(QGraphicsItem* target, QEvent* event)
{
if(event->type() == QEvent::GraphicsSceneMousePress || event->type() == QEvent::GraphicsSceneMouseDoubleClick)
{
GraphicsImageItem* item = dynamic_cast<GraphicsImageItem*>(target);
if(item)
{
item->SignalClicked();
qDebug() << "image button was clicked: Need to set the focus back to the box";
setFocus();
return true;
}
}
return QGraphicsItem::sceneEventFilter(target, event);
}
public slots:
void SlotButtonClicked()
{
setPos(pos().x() + 10, pos().y()+10);
}
};
SignalClicked() moves the GraphicsBoxItem by 10 pixels. Some of the GraphicsImageItem stay put, some move with GraphicsBoxItem:
class GraphicsImageItem : public QGraphicsSvgItem
{
Q_OBJECT
public:
GraphicsImageItem::GraphicsImageItem(QGraphicsItem *parent = NULL)
: QGraphicsSvgItem(QString(":/images/icon.svg"), parent)
{
setFlag(QGraphicsItem::ItemIsSelectable);
}
QRectF boundingRect() const { return QRectF(0, 0, 30, 30); }
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
painter->setBrush(Qt::lightGray);
painter->drawRect(boundingRect());
renderer()->render(painter, boundingRect().adjusted(3, 3, -3, -3));
}
Q_SIGNALS:
void SignalClicked();
};
A child's position is always relative to its parent, if the parent moves, then the child's absolute position changes too.
If you want to create the illusion that this doesn't happen, you will have to manually move the child in the opposite direction, so it can remain in the same absolute position.
The other option would be to not have the items as children in the first place.
Maybe you should create an invisible item, then parent the movable item as well as the non-movable children to it, and the movable children - under the movable item. Then you simply only move the movable item - its children move, its siblings stay in the same position, as their parent doesn't move.
For the child items you don't want to move, try set this flag:
setFlag(QGraphicsItem::ItemIgnoresTransformations);
If you can subclass your child items, then you can override the itemChange() method and watch for:
ItemScenePositionHasChanged
The item's scene position has changed. This notification is sent if
the ItemSendsScenePositionChanges flag is enabled, and after the
item's scene position has changed (i.e., the position or
transformation of the item itself or the position or transformation of
any ancestor has changed). The value argument is the new scene
position (the same as scenePos()), and QGraphicsItem ignores the
return value for this notification (i.e., a read-only notification).
Then then return the child item's last scenePos() which you'll have to store as a member and update each scene position change.
Remember to set the flag:
ItemSendsGeometryChanges
in your child subclass's ctor.
I'm currently working with Qts Chart plotting tool. I have now a plot where I can zoom in and out by using the chartview class provieded by this example (with small adjustments).
I would like to see the ability to not only zoom, but also move my view a pressed middle mouse button (which is used a lot in other applications and therefore is very intuitive).
How can I do this in Qt? How can I check if a middle mouse button is pressed and released and change my view at the plot if the mouse moves during a pressed middle mouse button...
I'm sure someone has coded this before and would really appreciate a small example/help.
you need to derive a class from QChartView and overload the mouse events:
class ChartView: public QChartView
{
Q_OBJECT
public:
ChartView(Chart* chart, QWidget *parent = 0);
protected:
virtual void mousePressEvent(QMouseEvent *event) override;
virtual void mouseMoveEvent(QMouseEvent *event) override;
private:
QPointF m_lastMousePos;
};
ChartView::ChartView(Chart* chart, QWidget *parent)
: QChartView(chart, parent)
{
setDragMode(QGraphicsView::NoDrag);
this->setMouseTracking(true);
}
void ChartView::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::MiddleButton)
{
QApplication::setOverrideCursor(QCursor(Qt::SizeAllCursor));
m_lastMousePos = event->pos();
event->accept();
}
QChartView::mousePressEvent(event);
}
void ChartView::mouseMoveEvent(QMouseEvent *event)
{
// pan the chart with a middle mouse drag
if (event->buttons() & Qt::MiddleButton)
{
QRectF bounds = QRectF(0,0,0,0);
for(auto series : this->chart()->series())
bounds.united(series->bounds())
auto dPos = this->chart()->mapToValue(event->pos()) - this->chart()->mapToValue(m_lastMousePos);
if (this->rubberBand() == QChartView::RectangleRubberBand)
this->chart()->zoom(bounds.translated(-dPos.x(), -dPos.y()));
else if (this->rubberBand() == QChartView::HorizontalRubberBand)
this->chart()->zoom(bounds.translated(-dPos.x(), 0));
else if (this->rubberBand() == QChartView::VerticalRubberBand)
this->chart()->zoom(bounds.translated(0, -dPos.y()));
m_lastMousePos = event->pos();
event->accept();
}
QChartView::mouseMoveEvent(event);
}
I'd like to offer a simpler version of Nicolas's mouseMoveEvent():
void ChartView::mouseMoveEvent(QMouseEvent *event)
{
// pan the chart with a middle mouse drag
if (event->buttons() & Qt::MiddleButton)
{
auto dPos = event->pos() - lastMousePos_;
chart()->scroll(-dPos.x(), dPos.y());
lastMousePos_ = event->pos();
event->accept();
QApplication::restoreOverrideCursor();
}
QChartView::mouseMoveEvent(event);
}
Also, be sure to include QApplication::restoreOverrideCursor() so the cursor returns to usual after the move is done.
I'm trying to move a QGraphicsRectItem after I add it to the scene. It moves, but appears with a certain offset from the mouse pointer. I think it is simply adding the mouse pointer position to its original position. I am at a loss as to how to work around this.
Here is my code:
class ucFilter : public QGraphicsItem {
std::shared_ptr<QGraphicsRectItem> m_rect;
std::shared_ptr<QGraphicsTextItem> m_text;
std::shared_ptr<QString> m_name;
std::shared_ptr<QPointF> m_pos;
QGraphicsItem* selectedItem;
bool m_mouseGrabbed;
public:
static const int default_x = 80, default_y=40;
ucFilter::ucFilter(QString &name, QPointF &pos){
m_name = shared_ptr<QString>(new QString(name));
m_pos = shared_ptr<QPointF>(new QPointF(pos));
m_rect = shared_ptr<QGraphicsRectItem>( new QGraphicsRectItem(pos.x()-default_x, pos.y()-default_y, 2*default_x, 2*default_y ));
m_text = shared_ptr<QGraphicsTextItem>( new QGraphicsTextItem(name));
m_text->setPos(pos.x() - m_text->boundingRect().width()/2, pos.y()- 30);
selectedItem = NULL;
m_mouseGrabbed = false;
}
QGraphicsRectItem* getRect() { return m_rect.get(); }
QGraphicsTextItem* getText() { return m_text.get(); }
QString* getName() { return m_name.get(); }
QPointF* getPos() { return m_pos.get(); }
void setPos(QPointF newPos) { m_pos->setX(newPos.x()); m_pos->setY(newPos.y()); }
QRectF ucFilter::boundingRect() const
{
return m_rect->boundingRect();
}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(widget);
//QBrush brush(
if (!m_mouseGrabbed){ grabMouse(); m_mouseGrabbed = true; }
}
void mousePressEvent(QGraphicsSceneMouseEvent *event){
selectedItem = this;
QGraphicsItem::mousePressEvent(event);
}
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event){
selectedItem = NULL;
QGraphicsItem::mouseReleaseEvent(event);
}
void mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(NULL != selectedItem){
m_text->setPos(event->pos());
m_rect->setPos(event->pos());
}
QGraphicsItem::mouseMoveEvent(event);
}
};
the ucFilter object is created in the scene dropEvent:
void cGraphicsScene::dropEvent(QGraphicsSceneDragDropEvent * event){
QTreeView* source = static_cast<QTreeView*>(event->source());
string name = event->mimeData()->text().toUtf8().constData();
if(0 == name.length()){
event->acceptProposedAction();
return ; // nothing to do anymore
}
QPointF pos = event->scenePos ();
shared_ptr<ucFilter> newFilter = shared_ptr<ucFilter>(new ucFilter(event->mimeData()->text(),event->scenePos ()));
m_filters.push_back(newFilter);
this->addItem(newFilter->getRect());
this->addItem(newFilter->getText());
this->addItem(newFilter.get()); // also add the item to grab mouse events
event->acceptProposedAction();
}
Where could the problem be ?
Here a screenshot of what I'm actually seeing :
I would like the rectangle to be drawn where the mouse is..
You have several issues:
The use of shared pointers to hold everything is completely unwarranted. The scene acts as a container for items - just like QObject is a container for objects.
ucFilter doesn't have children. It holds pointers to other items, but that is unnecessary. The base item can be a rectangle itself, and it can have the text as a child. That way you don't need to handle positioning in a special fashion.
ucFilter can be movable. Don't reimplement that functionality yourself.
When you pass things by reference, pass them as const references unless you are intending to pass the modified value out. If you wish to change the value inside of the body of the function, you can pass it by value instead.
The mouse is already grabbed when you are dragging the item.
Let's start with the ucFilter item. It is really simple and does everything you need. Note that m_text is held by value, and is made a child of the rectangle parent.
// https://github.com/KubaO/stackoverflown/tree/master/questions/graphics-item-drop-32574576
#include <QtWidgets>
class ucFilter : public QGraphicsRectItem {
QGraphicsTextItem m_text;
public:
ucFilter(const QString &name, const QPointF &pos, QGraphicsItem * parent = 0) :
QGraphicsRectItem(parent),
m_text(this)
{
static const QRect defaultRect(0, 0, 160, 80);
setPos(pos);
setRect(QRect(-defaultRect.topLeft()/2, defaultRect.size()));
setFlags(QGraphicsItem::ItemIsMovable);
setName(name);
}
void setName(const QString & text) {
m_text.setPlainText(text);
m_text.setPos(-m_text.boundingRect().width()/2, -30);
}
QString name() const {
return m_text.toPlainText();
}
};
Since we're dropping from a convenience widget (a QListWidget), we need to decode the text from a application/x-qabstractitemmodeldatalist mime type:
const char * kMimeType = "application/x-qabstractitemmodeldatalist";
QVariant decode(const QMimeData* data, Qt::ItemDataRole role = Qt::DisplayRole) {
auto buf = data->data(kMimeType);
QDataStream stream(&buf, QIODevice::ReadOnly);
while (!stream.atEnd()) {
int row, col;
QMap<int, QVariant> map;
stream >> row >> col >> map;
if (map.contains(role)) return map[role];
}
return QVariant();
}
The scene creates an item as soon as a drag enters it, and moves the item during the drag.
Ownership of the items remains with the scene: we don't need to delete any items unless we want to explicitly remove them from the scene. The m_dragItem is used to refer to the currently dragged item simply to move it and add it to m_filters upon completion of the drop. It the drag leaves the scene (or is aborted), the item is simply deleted from the scene.
class cGraphicsScene : public QGraphicsScene {
QList<ucFilter*> m_filters;
ucFilter* m_dragItem;
public:
cGraphicsScene(QObject * parent = 0) : QGraphicsScene(parent), m_dragItem(nullptr) {}
void dragEnterEvent(QGraphicsSceneDragDropEvent * event) Q_DECL_OVERRIDE {
if (!event->mimeData()->hasFormat(kMimeType)) return;
auto name = decode(event->mimeData()).toString();
if (name.isEmpty()) return;
QScopedPointer<ucFilter> filter(new ucFilter(name, event->scenePos()));
addItem(m_dragItem = filter.take());
event->acceptProposedAction();
}
void dragMoveEvent(QGraphicsSceneDragDropEvent * event) Q_DECL_OVERRIDE {
if (!m_dragItem) return;
m_dragItem->setPos(event->scenePos());
event->acceptProposedAction();
}
void dropEvent(QGraphicsSceneDragDropEvent * event) Q_DECL_OVERRIDE {
if (!m_dragItem) return;
m_dragItem->setPos(event->scenePos());
m_filters << m_dragItem;
event->acceptProposedAction();
}
void dragLeaveEvent(QGraphicsSceneDragDropEvent * event) Q_DECL_OVERRIDE {
delete m_dragItem;
m_dragItem = nullptr;
event->acceptProposedAction();
}
};
The test harness is very simple: our scene, a view displaying it, and a list with two items that you can drag onto the scene.
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QWidget w;
cGraphicsScene scene;
QGraphicsView view(&scene);
QListWidget list;
QHBoxLayout l(&w);
l.addWidget(&view);
l.addWidget(&list);
list.setFixedWidth(120);
list.addItem("Item1");
list.addItem("Item2");
list.setDragDropMode(QAbstractItemView::DragOnly);
view.setAcceptDrops(true);
w.resize(500, 300);
w.show();
return app.exec();
}
You can drag items from the list on the right to the scene on the left. You can also move the items within the scene, to relocate them.