I have this very strange issue regarding a QMenu
and its position when execing.
Here is the code for my subclassed QMenu
:
DockItemContextMenu::DockItemContextMenu(QWidget *parent) : QMenu(parent){
style = qApp->style();
QPointer<QAction> restoreAction = new QAction(QIcon(style->standardIcon(QStyle::SP_TitleBarMaxButton)), "Restore", this);
QPointer<QAction> minimizeAction = new QAction(style->standardIcon(QStyle::SP_TitleBarMinButton), "Minimize", this);
QPointer<QAction> maximizeAction = new QAction(style->standardIcon(QStyle::SP_TitleBarMaxButton), "Maximize", this);
QPointer<QAction> stayOnTopAction = new QAction("Stay On Top", this);
stayOnTopAction->setCheckable(true);
QPointer<QAction> closeAction = new QAction(style->standardIcon(QStyle::SP_TitleBarCloseButton), "Close", this);
this->addActions({restoreAction, minimizeAction, maximizeAction, stayOnTopAction, closeAction});
connect(restoreAction, &QAction::triggered, parent, [this](){ emit restoreTriggered();}, Qt::QueuedConnection);
connect(minimizeAction, &QAction::triggered, parent, [this](){ emit minimizeTriggered();}, Qt::QueuedConnection);
connect(maximizeAction, &QAction::triggered, parent, [this](){ emit maximizeTriggered();}, Qt::QueuedConnection);
connect(stayOnTopAction, &QAction::triggered, parent, [this](){ emit stayOnTopTriggered();}, Qt::QueuedConnection);
connect(closeAction, &QAction::triggered, parent, [this](){ emit closeTriggered();}, Qt::QueuedConnection);
}
Okay, so essentially I have another widget who holds an instance of this DockItemContextMenu
as a field. In this owning class, called Titlebar
, I made it such that doing a right click will emit the customContextMenuRequested(QPoint)
signal.
TitleBar::TitleBar(QString title, QWidget *parent){
...
this->setContextMenuPolicy(Qt::CustomContextMenu);
contextMenu = new DockItemContextMenu(this);
connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)), Qt::QueuedConnection);
...
}
After this, this widget is essentially inserted into a QGraphicsScene
and is converted implicitly into a QGraphicsItem
. When I do the FIRST right click event on my Titlebar
it will not exec at the correct screen position if I dragged the MainWindow
of the entire QApplication
anywhere other than its starting position on screen. In addition to being in a QGraphicsScene
, this scene itself is always stored in a QSplitter
. Now I would understand if this always had some sort of issue, but it turns out, every time I call the slot for that signal, ONLY the first time will it exec in the incorrect position in the QGraphicsScene
. No matter how I manipulate the size of the Titlebar
widget itself, move commands or maximize commands to the MainWindow
, or even edit the splitter size for the QGraphicsView
that affects the size of the QGraphicsScene
, it will always be in the correct position afterwards. here is the function for execing:
void TitleBar::showContextMenu(QPoint point){
qDebug() << point;
contextMenu->exec(point);
emit _parent->focusChangedIn();
}
I printed the point at which it is calling the exec. The strangest part is that both times I right click in the same location, it will print the SAME value for the slot's positional parameter both the first exec and second exec, but be in the correct location every time other than the first. Did I forget to set some other flag when I added the context menu to the Titlebar
class? Does it have anything to do with setting the QMenu's
parent to the Titlebar
? I'm just dumbfounded how the same QPoint
could exec
at two different screen locations given the same value. Does anybody have a clue what may or may not be happening on the first call to the Titlebar's
slot for execing
the QMenu
?
EDIT: The issue stemmed from doing this line of code in the Titlebar
constructor:
contextMenu = new DockItemContextMenu(this);
Changing it to:
contextMenu = new DockItemContextMenu;
fixed the issue. Does anyone know why, or is this possibly a bug? I rather not accept this as an answer because it does not explain why it happened in the first place.
Here is a minimal example with the same effect.
MainWindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QWidget>
#include <QGraphicsView>
#include <QSplitter>
#include <QHBoxLayout>
#include <QGraphicsScene>
#include <QPointer>
#include <QTreeWidget>
#include "titlebar.h"
class MainWindow : public QMainWindow{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
private:
};
#endif // MAINWINDOW_H
MainWindow.cpp:
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent){
QPointer<QWidget> widgetArea = new QWidget;
QPointer<QHBoxLayout> hLayout = new QHBoxLayout;
widgetArea->setLayout(hLayout);
QPointer<QSplitter> splitter = new QSplitter;
hLayout->addWidget(splitter);
QPointer<QTreeView> tree = new QTreeView;
splitter->addWidget(tree);
QPointer<QGraphicsView> view = new QGraphicsView;
splitter->addWidget(view);
splitter->setStretchFactor(0, 1);
splitter->setStretchFactor(1, 4);
QPointer<QGraphicsScene> scene = new QGraphicsScene;
view->setScene(scene);
QPointer<Titlebar> blue = new Titlebar;
blue->setObjectName("blue");
blue->setStyleSheet(QString("#blue{background-color: rgb(0,0,255)}"));
blue->resize(250,250);
scene->addWidget(blue);
this->setCentralWidget(widgetArea);
this->resize(1000,750);
}
MainWindow::~MainWindow(){
}
Titlebar.h:
#ifndef TITLEBAR_H
#define TITLEBAR_H
#include <QMenu>
#include <QWidget>
#include <QPointer>
#include <QDebug>
#include <QMouseEvent>
class Titlebar : public QWidget{
Q_OBJECT
public:
explicit Titlebar(QWidget *parent = nullptr);
QPointer<QMenu> menu;
QPoint currentPos;
protected slots:
void mousePressEvent(QMouseEvent* event);
void mouseMoveEvent(QMouseEvent* event);
void showContextMenu(QPoint point);
};
#endif // TITLEBAR_H
Titlebar.cpp:
#include "titlebar.h"
Titlebar::Titlebar(QWidget *parent) : QWidget(parent){
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)), Qt::QueuedConnection);
menu = new QMenu(this);
menu->addAction("Test");
}
void Titlebar::showContextMenu(QPoint point){
qDebug() << point;
menu->exec(mapToGlobal(point));
}
void Titlebar::mouseMoveEvent(QMouseEvent *event){
if (event->buttons() && Qt::LeftButton){
QPoint diff = event->pos() - currentPos;
move(pos() + diff);
}
}
void Titlebar::mousePressEvent(QMouseEvent * event){
currentPos = event->pos();
}
main.cpp:
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
So this runs and reproduces the error accordingly. If you change the line in Titlebar.cpp from
menu = new QMenu(this);
to:
menu = new QMenu;
Then it works correctly. ONLY the first right click to open the context menu will spawn in the incorrect location on screen. All subsequent right clicks will now follow either the widget/window/splitter in any combination. I don't get it, can someone tell me if this is actually a bug or not.