Find screen position of a QGraphicsItem
Asked Answered
L

3

16

Use case: This should be a fairly common problem. In a normal QMainWindow with QMdiArea lives an mdiChild with a QGraphicsView. This view displays a QGraphicsScene with QGraphicsItems inside. A right-click at one of these items selects (focusses) the item and opens a context menu, which is conveniently placed at the screen coordinates QGraphicsSceneMouseEvent::screenPos(). This is working as expected.

Now I'd like to show the same context menu when the user presses a key (e.g. Qt::Key_Menu). I know the selected (focussed) item, I know the view which displays the scene.

So my question is:
What is the correct way to get the position (in global, screen coordinates) of the visible representation of a QGraphicsItem within a scene?

Here is what's not working:

QGraphicsItem *item = ...; // is the currently selected item next to which the context menu shall be opened
QGraphicsScene *scene = ...; // is the scene that hosts the item
QGraphicsView *graphicsView = ...; // is the view displaying the scene, this inherits from QWidget

// get the position relative to the scene
QPointF sp = item->scenePos();
// or use
QPointF sp = item->mapToScene(item->pos());

// find the global (screen) position of the item
QPoint global = graphicsView->mapToGlobal(graphicsView->mapFromScene(sp));

// now
myContextMenu.exec(global);
// should open the context menu at the top left corner of the QGraphicsItem item, but it goes anywhere

The doc says: If you want to know where in the viewport an item is located, you can call QGraphicsItem::mapToScene() on the item, then QGraphicsView::mapFromScene() on the view.
Which is exactly what I'm doing, right?


Just stumbled upon a thread in a german forum that hints to:

QGraphicsView *view = item->scene()->views().last();

or even nicer:

QGraphicsView *view;
foreach (view,  this->scene()->views())
{
    if (view->underMouse() || view->hasFocus()) break;
}
// (use case in the forum thread:) // QMenu *menu = new QMenu(view);

Using that might allow a more generalized answer to my question...

Library answered 26/3, 2012 at 12:4 Comment(2)
I was about to post a response, but from re-reading the docs I think I agree with your analysis: QGraphicsView::mapFromScene should give viewport coordinates (worth checking). Only question would be if there's a latent bug in mapToGlobal on widgets inside MDI children.Rights
@JamesTurner So what would be your first guess (which you would have written in response)?Library
L
12

I found a working solution.
The QGraphicsItem must be visible on the screen. (Probably if it's not visible because the view shows some other point of the scene, one could restrain the point to the view's viewport's rect.)

// get the screen position of a QGraphicsItem
// assumption: the scene is displayed in only one view or the first view is the one to determine the screen position for
QPoint sendMenuEventPos; // in this case: find the screen position to display a context menu at
QGraphicsItem *pFocusItem = scene()->focusItem();

if(scene() != NULL // the focus item belongs to a scene
    && !scene()->views().isEmpty() // that scene is displayed in a view...
    && scene()->views().first() != NULL // ... which is not null...
    && scene()->views().first()->viewport() != NULL // ... and has a viewport
    )
{
    QGraphicsView *v = scene()->views().first();
    QPointF sceneP = pFocusItem->mapToScene(pFocusItem->boundingRect().bottomRight());
    QPoint viewP = v->mapFromScene(sceneP);
    sendMenuEventPos = v->viewport()->mapToGlobal(viewP);
}

if(sendMenuEventPos != QPoint())
{
    // display the menu:
    QMenu m;
    m.exec(sendMenuEventPos);
}

It is important to use the view's viewport for mapping the view coords to global coords.

The detection of the context menu key (Qt::Key_Menu) happens in the keyPressEvent() of the "main" QGraphicsItem (due to the structure of my program).

Library answered 4/4, 2013 at 10:45 Comment(2)
During an event, the view's viewport is exposed to the scene with QGraphicsSceneEvent::widget(). You can use QWidget::isAncestorOf(event->widget()) to find the "correct" view, instead of just taking the first one.Kwarteng
Unless the application in always run in full-screen, it would be better to map to QMainWindow with sendMenuEventPos = v->viewport()->mapTo(mainWindow, viewP); instead of using mapToGlobalCymric
H
1

The code seems to be correct. But there might be some problem with the creation of the context menu.

Have you set the parent of the QContextMenu to MainWindow (or something of that sort in your application)??

I think that might be the problem in your case.

Good Luck!!

Hispaniola answered 26/3, 2012 at 18:17 Comment(0)
I
1

Just a stab in the dark but have a look at this http://www.qtcentre.org/threads/36992-Keyboard-shortcut-event-not-received.

On looking through the Qt documentation, it seems the use of QGraphicsView may cause some exceptional behaviour with regards to shortcuts.

It looks as if there might be a normative way of achieving the result you desire.

Depending how you are implementing your context menu, shortcuts and QGraphicsView, you might need to set the Qt::ContextMenuPolicy for the QGraphicsView appropriately and build and call the menu differently.

I'm quite interested in this question as I will need to do something quite similar shortly!

Iconoscope answered 27/3, 2012 at 10:40 Comment(2)
Qt::ActionsContextMenu looks shiny. I'll have a look into it, but I'm not very confident that it will help. My general problem is more about positioning than about keyboard input (I can call that function from anywhere).Library
I guess it depends on how you have it implemented as to whether that's relevant or not. I also found the following overloaded exec function definition in the documentation to be of interest: doc.qt.nokia.com/4.7-snapshot/qmenu.html#exec-3. It seems like an odd static method but alludes to the possibility that a QPoint is not enough when "...the parent is embedded in QGraphicsView". It's a little mysterious.Iconoscope

© 2022 - 2024 — McMap. All rights reserved.