-1

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.

Blanky
  • 63
  • 9
  • please provide a [mcve] – eyllanesc Oct 15 '18 at 20:32
  • @eyllanesc I figured it out, my hunch was correct with setting the `QMenu's` parent to the `Titlebar` in the constructor for the `Titlebar`. Do you have any clue why that would have any effect? Also to show a minimal example would be quite lengthy since I'm implementing 10 classes together to form the scene's widget. I'll go update my post to reflect that, but that doesn't answer why it was happening. – Blanky Oct 15 '18 at 21:34
  • 1
    When you are asked for a [mcve] you are not being asked to share your project, you are being asked to create a new example focused on the problem, please when someone suggests something, take the trouble to read the link, if there is one. I would have understood that it is an MCVE. – eyllanesc Oct 15 '18 at 21:37
  • @eyllanesc I updated the question with an example you can copy/paste and run on any system. – Blanky Oct 15 '18 at 22:41

1 Answers1

0

You need to add one line of code because your using a QGraphicsProxyWidget which is part of a QGraphicsScene. The scene is represented by a QGraphicsView which inherits QAbstractScrollArea. This causes the context menu to be shown via the viewport and not the widget itself. Therefore adding this one line of code will override the title bar to not be embedded in the scene when it's parent was already embedded in the scene. Effectively making it reference the widget again and not the viewport.

In the MainWindow.cpp right after line 26 add

blue->setWindowFlags(Qt::BypassGraphicsProxyWidget);