Get mouse position in child QGraphicsScene - qt

In Qt5, I have a main window with a scene:
MyWindow::MyWindow(QWidget *parent) : QMainWindow(parent)
{
view = new QGraphicsView();
scene = new QGraphicsScene();
scene->installEventFilter(this);
view->setScene(scene);
...
setCentralWidget(view);
}
view and scene are both private members of MyWindow. I want to know, in the MyWindow class, the mouse position when I click on the scene. That's why I use installEventFilter above. And I have tried to catch the event with this:
bool MyWindow::eventFilter(QObject *target, QEvent *event)
{
if (target == scene)
{
if (event->type() == QEvent::GraphicsSceneMousePress)
{
const QGraphicsSceneMouseEvent* const me = static_cast<const QGraphicsSceneMouseEvent*>(event);
const QPointF position = me->pos();
cout << position.x() << "," << position.y() << endl;
}
}
return QMainWindow::eventFilter(target, event);
}
This code does not work as expected: The position it prints when I click on the scene is always 0,0. Any clue about what is wrong?

QGraphicsSceneMouseEvent.pos() returns position in coordinates of item on which you clicked. Your scene has no items so it returns (0,0). If you want to get position in scene coordinates use scenePos().
const QPointF position = me->scenePos();

Related

Qgraphicsscene troubles to get scenePos() inside a function

I have subclassed a qgraphicsscene and trying to get the mouse coords inside a "normal" function. I only get it working on "mouse involved" function. Sorry I'm amateur programmer.
For exmample here scenePos() works:
void mousePressEvent(QGraphicsSceneMouseEvent *event)
{
// qDebug() << "Custom scene clicked.";
if(event->modifiers() == Qt::ControlModifier) {
if(event->button() == Qt::LeftButton) {
QPointF pos = {event->scenePos().x(), 70};
addChordnueve(pos); // crea 1 item at mouse x e y = 70
// } if(event->modifiers() == Qt::ControlModifier & event->modifiers() == Qt::ShiftModifier) {
qDebug() << "Control!!!";}}
Here it doesn't works at all, but got QCursor::pos() giving "weird" positions:
void preaddExtChord()
{
auto *hellos = scenePos(); //<- It doesn't works
int xplace = QCursor::pos().x()-620;
int yplace = QCursor::pos().y()-380;
QGraphicsSimpleTextItem *item = new QGraphicsSimpleTextItem("n");
item->setFont(QFont ("omheads", 20));
item->setPos(xplace, yplace);
addItem(item);
}
I searched a lot during months but couldn't find a solution,...
maybe I'm doing a wrong approach, or either there is some easier possibilitie to get the mouse coords inside this type of functions?
Thanks! :-)
If you want to obtain the position with respect to the cursor scene you must first obtain that QGraphicsView is below the cursor (a QGraphicsScene can be part of QGraphicsView), for this we must iterate and verify if it is inside the viewport, then calculate the position with respect to the scene using the mapToScene method of QGraphicsView:
QPoint p = QCursor::pos();
for(QGraphicsView *view: views()){
QWidget *viewport = view->viewport();
QRect vr = viewport->rect();
QPoint vp = viewport->mapFromGlobal(p);
if(vr.contains(vp)){
QPointF sp = view->mapToScene(vp);
QGraphicsSimpleTextItem *item = new QGraphicsSimpleTextItem("n");
item->setFont(QFont("omheads", 20));
item->setPos(sp);
addItem(item);
}
}

Programmatic scrolling with QGraphicsView and QGraphicsItem?

I would like to programmatically scroll a scene to the left / right, but I am not sure how to do that properly. Note that I do not want to have (visible) scroll bars.
I use a standard QGraphicsView + QGraphicsScene + QGraphicsItem setup. I have downsized it to the minimum, with one single QGraphicsItem (a QGraphicsRectItem) in the scene.
I have managed to achieve programmatic scrolling by setting my view like this:
// view setup
view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
and then, in another part of the code:
// programmatic scrolling
QScrollBar* const sb = view->horizontalScrollBar();
sb->setRange(0, 1000); // some values for experimenting
sb->setValue(sb->value() + 100 or -100); // some increment for experimenting
This works, but... scrolling through invisible scrollbars doesn't feel right.
I tried this more straightforward approach:
// programmatic scrolling - doesn't quite work
view->viewport()->scroll(100 or -100, 0); // some increment for experimenting
This code does scroll, but when the rectangle goes off the left edge of the view, and I reverse the scrolling direction (increment changed from 100 to -100 in the call to scroll()), the uncovered part of the rectangle is not repainted. The reason is that QGraphicsRectItem::paint() is not called in that case (it is called when using the scrollbar method).
So, is there a way to get viewport()->scroll() work? Or some other simple way to achieve programmatic scrolling? Or is the artificial scrollbar method just the way to go?
Moving the view assumes that it's smaller than its scene. If they're the same size, it won't move.
QGraphicsView can be set to centerOn any position in scene coordinates. Use a timer to call centerOn to move the view one frame at a time.
Here's a working example: -
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QTimer>
class MyView : public QGraphicsView
{
private:
public:
MyView(QGraphicsScene* pScene)
: QGraphicsView(pScene, NULL)
{}
void AnimateBy(int x)
{
float updateFrequency = (1000/30.0); // ~30 frames per second
QPointF currScenePos = sceneRect().center();
int curX = currScenePos.x();
int endPos = curX + x;
int distanceToAnimate = (endPos - curX);
// speed = dist / time
float updatePosInterval = (float)distanceToAnimate / updateFrequency;
printf("updatePosInterval: %f \n", updatePosInterval);
static float newXPos = sceneRect().center().x();
QTimer* pTimer = new QTimer;
QObject::connect(pTimer, &QTimer::timeout, [=](){
newXPos += updatePosInterval;
centerOn(newXPos, sceneRect().center().y());
// check for end position or time, then....
if(newXPos >= endPos)
{
pTimer->stop();
pTimer->deleteLater();
}
});
pTimer->start(updateFrequency);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene(0, 0, 10000, 20000);
MyView* view = new MyView(&scene);
QGraphicsRectItem* pRect = new QGraphicsRectItem(0, 0, 100, 100);
pRect->setPos(scene.width()/2, scene.height()/2);
scene.addItem(pRect);
// timer to wait for the window to appear, before starting to move
QTimer* pTimer = new QTimer;
pTimer->setSingleShot(true);
QObject::connect(pTimer, &QTimer::timeout,[=](){
view->centerOn(pRect); // centre on the rectangle
view->AnimateBy(100);
pTimer->deleteLater();
});
pTimer->start(1000);
view->show();
return a.exec();
}
So, we create the animation by moving the view frame-by-frame using the call to centerOn.
For simplicity, the code just deals with moving in one axis. To move in 2 axis, use 2D vector maths to calculate the interval position.
Try to change the view transformation with the QGraphicsView::translate() or QGraphicsView::setTransform().
But keep in mind that you can't move the viewport "outside" the scene, so make sure that your scene rectangle is large enough.
If I got your question correctly, there is a dojo classes library with such class as PanWebView that allow QWebView to scroll smoothly with mouse without any scrollbars. Take a look at sources. It supports panning and can be suitable for mobile apps, but maybe it'll help you too.
PanWebView class looks like this
#include <QWebView>
#include <QWebFrame>
#include <QMouseEvent>
#include <QApplication>
class PanWebView : public QWebView
{
Q_OBJECT
private:
bool pressed;
bool scrolling;
QPoint position;
QPoint offset;
QList<QEvent*> ignored;
public:
PanWebView(QWidget *parent = 0): QWebView(parent), pressed(false), scrolling(false) {
QWebFrame *frame = page()->mainFrame();
frame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff);
frame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff);
}
protected:
void mousePressEvent(QMouseEvent *mouseEvent) {
if (ignored.removeAll(mouseEvent))
return QWebView::mousePressEvent(mouseEvent);
if (!pressed && !scrolling && mouseEvent->modifiers() == Qt::NoModifier)
if (mouseEvent->buttons() == Qt::LeftButton) {
pressed = true;
scrolling = false;
position = mouseEvent->pos();
QWebFrame *frame = page()->mainFrame();
int x = frame->evaluateJavaScript("window.scrollX").toInt();
int y = frame->evaluateJavaScript("window.scrollY").toInt();
offset = QPoint(x, y);
QApplication::setOverrideCursor(Qt::OpenHandCursor);
return;
}
return QWebView::mousePressEvent(mouseEvent);
}
void mouseReleaseEvent(QMouseEvent *mouseEvent) {
if (ignored.removeAll(mouseEvent))
return QWebView::mouseReleaseEvent(mouseEvent);
if (scrolling) {
pressed = false;
scrolling = false;
QApplication::restoreOverrideCursor();
return;
}
if (pressed) {
pressed = false;
scrolling = false;
QMouseEvent *event1 = new QMouseEvent(QEvent::MouseButtonPress,
position, Qt::LeftButton,
Qt::LeftButton, Qt::NoModifier);
QMouseEvent *event2 = new QMouseEvent(*mouseEvent);
ignored << event1;
ignored << event2;
QApplication::postEvent(this, event1);
QApplication::postEvent(this, event2);
QApplication::restoreOverrideCursor();
return;
}
return QWebView::mouseReleaseEvent(mouseEvent);
}
void mouseMoveEvent(QMouseEvent *mouseEvent) {
if (scrolling) {
QPoint delta = mouseEvent->pos() - position;
QPoint p = offset - delta;
QWebFrame *frame = page()->mainFrame();
frame- >evaluateJavaScript(QString("window.scrollTo(%1,%2);").arg(p.x()).arg(p.y()));
return;
}
if (pressed) {
pressed = false;
scrolling = true;
return;
}
return QWebView::mouseMoveEvent(mouseEvent);
}
};
And usage:
PanWebView web;
web.setUrl(QUrl("http://news.google.com"));
web.setWindowTitle("Web View - use mouse to drag and pan around");
web.show();
Also did you check this and this topics? I think it can be usefull.

Can't get positions of QGraphicsItems in scene

I am trying to get the positions of the graphicsitems in the scene.
But their QPointF value always remains (0,0).
I am painting when mouse-click event occurs. On debugging scene->items(), I get
(QGraphicsItem(this =0x22edff0, parent =0x0, pos =QPointF(0, 0) , z = 0 , flags = ( ) ) )
for each graphics item in scene but with different memory address.
This is my mainwindow.cpp code:
#include "mainwindow.h"
#include <QDebug>
MainWindow::MainWindow()
{
scene = new QGraphicsScene;
view = new QGraphicsView;
view->setScene(scene);
button = new QPushButton("Item");
QGridLayout *layout = new QGridLayout;
layout->addWidget(button);
layout->addWidget(view);
setLayout(layout);
connect(button, SIGNAL(clicked()), this, SLOT(createItem()));
}
void MainWindow::createItem()
{
myEntity = new Item;
scene->addItem(myEntity);
count_items();
}
void MainWindow::count_items()
{
qDebug() << scene->items().count();
qDebug() << scene->items();
}
MainWindow::~MainWindow()
{}
This is my item.cpp code:
#include "item.h"
Item::Item()
{
ClickFlag = true;
PaintFlag = false;
}
Item::~Item(){}
QRectF Item::boundingRect() const
{
// outer most edges
return QRectF(0,0,1450,1400);
}
void Item::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(event->button()==Qt::LeftButton){
if(ClickFlag){
x = event->pos().x();
y = event->pos().y();
PaintFlag = true;
ClickFlag = false;
}
}
}
void Item::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
if(PaintFlag){
QPen paintPen;
paintPen.setWidth(4);
pt.setX(x);
pt.setY(y);
painter->setPen(paintPen);
painter->drawPoint(x,y);
update();
}
}
I can't seem to find the position of these items correctly.
This task is supposed to be implemented in another way. For example:
Use QGraphicsScene::addEllipse to add small ellipse (which will look like a point) to the scene. Save the pointer to it in a class variable. The ellipse itself should be at the center, e.g. (-1, -1, 2, 2).
Reimplement QGraphicsScene::mousePressEvent, detect mouse clicks and call setPos for the ellipse item (or add new ellipse each time and immediately call setPos if you need multiple points).
Use QGraphicsItem::pos to get previously set positions.
Reimplementing QGraphicsItem::paint is usually an over-complication. Qt have plenty of item classes for all common needs. Just build your scene from geometric primitives, pixmaps, etc.

mousePressEvent() method is not working as expected when calling setPos() method

I have a class MenuItem which inherits from QGraphicsItem and reimplemented boundingRect(), shape(), paint(), outlineRect():
MenuItem::MenuItem(const QString& qsText, qreal qrYPos)
{
m_qsText = qsText;
m_BackgroundColor = Qt::white;
m_OutlineColor = Qt::darkBlue;
m_TextColor = Qt::darkGreen;
qDebug() << pos();
setPos(mapToParent(200,200)); //<-- when calling this method, mousePressEvent()
// behaves not as expected
qDebug() << pos();
}
QRectF MenuItem::boundingRect() const
{
const int iMargin = 1;
return outlineRect().adjusted(-iMargin, -iMargin, +iMargin, +iMargin);
}
QPainterPath MenuItem::shape() const
{
QRectF rect = outlineRect();
QPainterPath path;
path.addRect(rect);
return path;
}
void MenuItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QPen pen(m_OutlineColor);
painter->setPen(pen);
painter->setBrush(m_BackgroundColor);
QRectF rect = outlineRect();
painter->drawRect(rect);
painter->setPen(m_TextColor);
painter->drawText(rect, Qt::AlignCenter, m_qsText);
}
void MenuItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
qDebug() << "Item Mouse Pressed";
}
QString MenuItem::getText() const
{
return m_qsText;
}
QRectF MenuItem::outlineRect() const
{
const int iPadding = 8;
QFontMetricsF metrics = QFontMetricsF(QApplication::font());
QRectF rect = metrics.boundingRect(m_qsText);
rect.adjust(-iPadding, -iPadding, +iPadding, +iPadding);
rect.translate(-rect.center());
return rect;
}
In another class, called Menu which inherits from QGraphicsScene, I added one instance of MenuItem:
Menu::Menu()
: QGraphicsScene()
{
setSceneRect(0, 0, 800, 600);
m_miNewGame = new MenuItem("New Game", 300);
this->addItem(m_miNewGame);
//m_miNewGame->setPos(200,200);
}
The Menu class reimplements mousePressEvent
void Menu::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
//qDebug() << "Menu Mouse Pressed";
MenuItem *gi = dynamic_cast<MenuItem*>(itemAt(event->pos(), QTransform()));
if (gi)
qDebug() << gi->getText();
QGraphicsScene::mousePressEvent(event); // this forwards the event to the item
if (itemAt(event->pos(), QTransform()))
{
qDebug() << "You Pressed an Item";
}
}
If I use setPos() method inside the MenuItem constructor the MenuItem gets positioned right but inside the Menu::mousePressEvent() method, MenuItem* returned from itemAt() is always NULL.
Omitting the setPos() method, the MenuItem stays in the top left corner (0,0) of the scene and mousePressEvents are handled as expected: returning the MenuItems Text with the getText() method.
Why is the MenuItem* NULL when calling setPos()?
Do I have to reimplement setPos() or what am I doing wrong?
Any help is welcome.
In MenuItem() constructor you use mapToParent. But your item doesn't have any parent item. So using mapToParent is pointless, it's equivalent to mapToScene in this case. And since your item's initial position is (0, 0) and no transformation has been applied, mapToScene will return its argument's value without changes. So it's equivalent to setPos(200, 200). It seems strange to use the result of mapToParent or mapToScene in setPos. I don't understand what you were trying to do.
QGraphicsSceneMouseEvent::pos returns coordinates of the event in target item's coordinates. Since you're using it in QGraphicsScene::keyPressEvent, the event has not been propagated to any item, and pos() always returns (0, 0). The documentation isn't clear about it, but I've checked it.
If you didn't use setPos, your item's position will be (0, 0) and itemAt(0, 0) will find your item (regardless of the point the user have actually clicked). But if you did use setPos, itemAt(0, 0) returns 0 because there is no item at this point. If you replace event->pos() with event->scenePos(), it will work correct.
However, it's unusual to reimplement QGraphicsScene::keyPressEvent to catch clicking on item. You should reimplement QGraphicsItem::mousePressEvent instead. It will be called only if the item has been clicked, and you don't have to check event's coordinates to determine that.

Position of icon in QTreeWidgetItem

My QTreeWidget has a single column. Its items have a check box, an icon, and text. If the user clicks inside an item, I want to know whether the icon was clicked. How can I find the position and size of the icon in a QTreeWidgetItem?
Updated to add: Here is the code for my eventual solution, as requested by webclectic.
First, I sub-classed QItemDelegate so that I could access the coordinates of each part of a QTreeWidgetItem (check box, icon, and text). Here is the header file:
#include <QItemDelegate>
class MyItemDelegate : public QItemDelegate
{
Q_OBJECT
public:
explicit MyItemDelegate (MyTreeWidget *parent)
: QItemDelegate (parent), ParentView (parent) { }
~MyItemDelegate() { }
void GetRects (const QModelIndex &index, QRect& CheckBox, QRect& Icon, QRect& Text) const ;
private:
MyTreeWidget* ParentView ;
} ;
And here is the source file:
void MyItemDelegate::GetRects (const QModelIndex &index, QRect& CheckBox, QRect& Icon, QRect& Text) const
{
QStyleOptionViewItem option = ParentView -> viewOptions() ;
CheckBox = rect (option, index, Qt::CheckStateRole) ;
Icon = rect (option, index, Qt::DecorationRole) ;
Text = rect (option, index, Qt::DisplayRole) ;
doLayout (option, &CheckBox, &Icon, &Text, true) ;
QRect VisualRect = ParentView -> visualRect (index) ;
CheckBox.translate (VisualRect.topLeft()) ;
Icon.translate (VisualRect.topLeft()) ;
Text.translate (VisualRect.topLeft()) ;
}
Then I added a MyItemDelegate* member to MyTreeWidget, and set it as the item view's delegate. In the header:
class MyTreeWidget : public QTreeWidget
{
...
MyItemDelegate* Delegate ;
...
} ;
In the source :
MyTreeWidget::MyTreeWidget (QObject* parent)
{
...
Delegate = new MyItemDelegate (this) ;
setItemDelegate (ItemDelegate) ;
}
Now, to get the coordinates of each part of a QTreeWidgetItem:
QTreeWidgetItem* item ;
...
QModelIndex ModelIndex = indexFromItem (item) ;
QRect CheckBoxRect, IconRect, TextRect ;
ItemDelegate -> GetRects (ModelIndex, &CheckBoxRect, &IconRect, &TextRect) ;
Unfortunately there is no simple way to achieve what you want. The problem is that QTreeWidget is responsible for painting its items so the item itself has no information about the position of its elements in the view.
First of all you have to subclass QTreeWidget and reimplement the mousePressEvent (or mouseReleaseEvent if you prefer). Inside the event you should calculate the position of the icon and handle it correspondingly.
Sample code (but untested) follows:
void mousePressEvent(QMouseEvent *event)
{
QModelIndex clickedIndex = indexAt(event->pos());
// make sure the event was on a valid item
if (clickedIndex.isValid() == false)
return;
// Get the tree widget's x position
int treeX = header()->sectionViewportPosition(0);
// Get the x coordinate of the root item. It is required in order to calculate
// the identation of the item
int rootX = visualRect(rootIndex()).x();
// Get the rectangle of the viewport occupied by the pressed item
QRect vrect = visualRect(clickedIndex);
// Now we can easily calculate the x coordinate of the item
int itemX = treeX + vrect.x() - rootX;
// The item is a checkbox, then an icon and finally the text.
// 1. Get the rect surrounding the checkbox
QRect checkboxRect = QRect(itemX,
vrect.y(),
style()->pixelMetric(QStyle::PM_IndicatorWidth),
vrect.height());
// 2. Get the rect surrounding the icon
QRect iconRect = QRect(itemX + checkboxRect.width(),
vrect.y(),
iconSize().width(),
vrect.height());
// 3. Finally get the rect surrounding the text
QRect textRect = QRect(itemX + checkboxRect.width() + iconRect.width(),
vrect.y(),
vrect.width() - checkboxRect.width() - iconRect.width(),
vrect.height());
// Now check where the press event took place and handle it correspondingly
if(checkboxRect.contains(event->pos()))
{
qDebug() << "Checkbox pressed";
QTreeWidget::mousePressEvent(event);
return;
}
else if (iconRect.contains(event->pos()))
{
qDebug() << "Icon pressed";
QTreeWidget::mousePressEvent(event);
return;
}
else
{
qDebug() << "Text pressed";
QTreeWidget::mousePressEvent(event);
return;
}
}
I repeat that the code is untested but you get the idea about how to achieve what you want.

Resources