PDA

View Full Version : mapToGlobal/mapFromGlobal isn't working properly



janderson.daz
11th April 2011, 16:37
I am using Qt 4.7 and have come across an interesting problem with positioning widgets using QWidget::mapToGlobal() and QWidget::mapFromGlobal()...

I have a button (in a dock bar) that displays a widget as a popup. I am trying to position the popup relative to the button in different ways depending on where the button is on the screen (since it is in a dock bar the user can move it around).

My method for positioning the popup takes a widget to use as the widget to position the popup on. I then get the global coordinates of this widget and the width and height of the popup widget to determine the correct position of the popup.

What is interesting is that the coordinates for mapToGlobal() and mapFromGlobal are only returning the correct results when in the bottom-right corner of the screen. The other three coordinates return incorrect results and the popup does not display in the correct spot.

Here is the code for positioning our popup:


void DzMainWindow::positionUIPopUp( QWidget* posWgt )
{
if(!posWgt)
return;

DzUIPopUpWgt* popupWgt = getUIPopUp();

if(!popupWgt)
return;

dzApp->processEvents();

// Find a parent widget that has an orientation
Qt::Orientation orient = Qt::Horizontal;
if(QWidget* parentWgt = posWgt->parentWidget())
{
do
{
QVariant data = parentWgt->property("orientation");
if(data.isValid())
{
orient = Qt::Orientation(data.value<int>());
break;
}
parentWgt = parentWgt->parentWidget();
} while (parentWgt);
}

QPoint posWgtGlobalPos = posWgt->mapToGlobal(QPoint(0,0));

// Split screen into quadrants
QDesktopWidget *desktop = QApplication::desktop();
QRect screenRect = desktop->availableGeometry(posWgtGlobalPos);

int screenMidX = screenRect.x() + (screenRect.width() / 2);
int screenMidY = screenRect.y() + (screenRect.height() / 2);

QRect topLeft = screenRect;
topLeft.setRight(screenMidX);
topLeft.setBottom(screenMidY);

QRect topRight = screenRect;
topRight.setLeft(screenMidX);
topRight.setBottom(screenMidY);

QRect bottomLeft = screenRect;
bottomLeft.setRight(screenMidX);
bottomLeft.setTop(screenMidY);

QRect bottomRight = screenRect;
bottomRight.setLeft(screenMidX);
bottomRight.setTop(screenMidY);

// Position the popup widget based on the quadrant posWgt is in
int popX = posWgtGlobalPos.x();
int popY = posWgtGlobalPos.y();

if(topLeft.contains( posWgtGlobalPos ))
{
if (orient == Qt::Horizontal)
{
popY += posWgt->height();
}
else
{
popX += posWgt->width();
}
}
else if(topRight.contains( posWgtGlobalPos ))
{
popX -= popupWgt->width();

if (orient == Qt::Horizontal)
{
popX += posWgt->width();
popY += posWgt->height();
}
}
else if(bottomRight.contains( posWgtGlobalPos ))
{
popX -= popupWgt->width();
popY -= popupWgt->height();

if (orient == Qt::Horizontal)
{
popX += posWgt->width();
}
else
{
popY += posWgt->height();
}
}
else if(bottomLeft.contains( posWgtGlobalPos ))
{
popY -= popupWgt->height();

if (orient == Qt::Vertical)
{
popX += posWgt->width();
popY += posWgt->height();
}
}

QPoint popPnt = popupWgt->mapFromGlobal(QPoint(popX, popY));
popupWgt->setGeometry(
popupWgt->x() + popPnt.x(),
popupWgt->y() + popPnt.y(),
popupWgt->width(),
popupWgt->height());

//popupWgt->move(popupWgt->x() + popPnt.x(), popupWgt->y() + popPnt.y());
}


Are we doing something wrong here? I've had two other guys look at this code and we've already spent 3 days trying to figure this out...

Any tips, pointers, suggestions, would be greatly appreciated

wysota
11th April 2011, 17:18
Does getUIPopUp() create the widget or does it simply return it? Was the widget visible on the screen prior to running mapToGlobal()? What does the following print?

qDebug() << getUIPopUp()->geometry() << getUIPopUp()->window()->geometry();

Are the values what you expect? What is the purpose of the do-while loop?

janderson.daz
11th April 2011, 18:24
Hi Wysota,

Thanks for the quick reply! Here are the answers to your questions:


getUIPopUp() simply returns the widget. It is created previously
The call to processEvents() ensures that the widget is visible before moving it
The posWgt that is passed in is guaranteed to only live within in a dockbar. The do while loop goes through the parents until it finds a parent that has an orientation (the dock bar). The positioning changes based on the orientation of the dock bar.
I'm getting results that I would not expect from the debug code that you gave me... See below for details...


I placed the debug code just before moving popupWgt and just after moving popupWgt. I ran two test cases, one with posWgt being in the Bottom-Right corner (this works) and one with posWgt being in the Top-Left corner (this doesn't work). However, In both cases I'm getting results that I didn't expect.

Bottom-Right (WORKS)

Before setGeometry() or move()

getUIPopup()->geometry() //(805, 280, 310x521)
getUIPopup()->window()->geometry() //(805, 280, 310x521)

After setGeometry() or move()

getUIPopup()->geometry() //(1524, 456, 314x566)
getUIPopup()->window()->geometry() //(1524, 456, 314x566)

There are two things that are confusing me:

The width and height changes after the move
The new x,y coordinates are not offset by the values of popPnt: (723, 221)



Top-Left (BROKEN)

Before setGeometry() or move()

getUIPopup()->geometry() //(805, 280, 310x521)
getUIPopup()->window()->geometry() //(805, 280, 310x521)

After setGeometry() or move()

getUIPopup()->geometry() //(736, 74, 314x566)
getUIPopup()->window()->geometry() //(736, 74, 314x566)

There are two things that are confusing me:

The width and height changes after the move
The new x,y coordinates are not offset by the values of popPnt: (-65, -161)

wysota
11th April 2011, 19:00
The posWgt that is passed in is guaranteed to only live within in a dockbar. The do while loop goes through the parents until it finds a parent that has an orientation (the dock bar). The positioning changes based on the orientation of the dock bar.
Wouldn't it be safer to ask the widget for its class name? More than one class may have a property called "orientation".

Is the parent of the widget you are trying to move managed by a layout?

Finally, what is the ultimate effect you are trying to achieve? Maybe there is a better approach to reach the goal.

janderson.daz
11th April 2011, 19:33
Wouldn't it be safer to ask the widget for its class name? More than one class may have a property called "orientation".

That is a good point. This code was originally written to be as generic as possible, allowing for any widget with an orientation property. This has since been narrowed down to just a dock bar. We will change this code to do as you suggested...


Is the parent of the widget you are trying to move managed by a layout?

The parent of the popup widget is DzMainWindow, which is a subclass of QMainWindow, and are not doing our own layout. We use methods such as QMainWindow::setMenuWidget() so we are using the default layout that QMainWindow uses.


Finally, what is the ultimate effect you are trying to achieve? Maybe there is a better approach to reach the goal.

We are trying to position the popup widget so that it's corners align with one of the corners of the reference widget (posWgt), based on the orientation of the divider bar and the quadrant that the reference widget.

Examples:
posWgt Quadrant: Bottom-Right
Dock Bar Orientation: Horizontal
Alignment: popupWgt's bottom-right corner should align with posWgt's top-right corner

posWgt Quadrant: Bottom-Right
Dock Bar Orientation: Vertical
Alignment: popupWgt's bottom-right corner should align with posWgt's top-left corner

posWgt Quadrant: Top-Left
Dock Bar Orientation: Horizontal
Alignment: popupWgt's top-left corner should align with posWgt's bottom-left corner

wysota
11th April 2011, 20:47
The parent of the popup widget is DzMainWindow, which is a subclass of QMainWindow, and are not doing our own layout. We use methods such as QMainWindow::setMenuWidget() so we are using the default layout that QMainWindow uses.

So it is managed by a layout which might explain why your own geometry management fails. If you wish to do your own moving/resizing, you can't use layouts at the same time.


We are trying to position the popup widget so that it's corners align with one of the corners of the reference widget (posWgt), based on the orientation of the divider bar and the quadrant that the reference widget.

Examples:
posWgt Quadrant: Bottom-Right
Dock Bar Orientation: Horizontal
Alignment: popupWgt's bottom-right corner should align with posWgt's top-right corner

posWgt Quadrant: Bottom-Right
Dock Bar Orientation: Vertical
Alignment: popupWgt's bottom-right corner should align with posWgt's top-left corner

posWgt Quadrant: Top-Left
Dock Bar Orientation: Horizontal
Alignment: popupWgt's top-left corner should align with posWgt's bottom-left corner

I suggest you forget about docks for now and implement your solution as working for any possible widget. When you have that done you can adjust the code to work for your specific case. mapToGlobal() and mapFromGlobal() both work fine so if they fail in your case then it has to be caused by your own code. Move the popup code out of your specific use-case, make it work as a standalone solution and then integrate it back to your main project.

janderson.daz
11th April 2011, 21:03
So it is managed by a layout which might explain why your own geometry management fails. If you wish to do your own moving/resizing, you can't use layouts at the same time.

That makes sense. However, as a quick test I changed the code that creates the popup so that it doesn't have a parent. Shouldn't that mean that it's position is no longer affected by the main window's layout? When I did this, I didn't see any change in behavior...


I suggest you forget about docks for now and implement your solution as working for any possible widget. When you have that done you can adjust the code to work for your specific case. mapToGlobal() and mapFromGlobal() both work fine so if they fail in your case then it has to be caused by your own code. Move the popup code out of your specific use-case, make it work as a standalone solution and then integrate it back to your main project.

I will look into doing this

janderson.daz
13th April 2011, 22:03
Problem has been "solved". It is not a layout issue as was suspected. While performing different tests I noticed a few things that led me to the "fix":

The geometry before and after the move was different
This caused the final x and y coordinates to be off by a certain amount
I noticed that the amount the coordinates were off also happened to coincide directly with the geometry changing (e.g. If the x coordinate was off by 4 pixels from what was expected, the width of the final geometry would change by 4 pixels. The same was true for the y coordinates).


These things led me to believe that I was calculating my positions based on bad geometry, which would in turn cause my positions to be incorrect... I tried calling updateGeometry() on the popup widget but that didn't have any effect.

As a test, I tried calling move on the popup widget before calculating my offsets. This turned out to work! So, for some reason that I can't figure out, the geometry is not correct until after the first time the widget has been moved.

I admit this is a bit hackish but I get the results I want. Everything lines up perfectly now. I am open to other solutions if anyone has any to offer. Until then this is how we are "fixing" the problem.