Search code examples
c++qtqt6

CustomContextMenu not showing at given points


I have added a custom context menu to a label. Every thing fine just it places itself randomly on screen every time I right-click and following a diagonal pattern it reaches to the bottom right of the screen where it stays for every next right click. I have printed the QPoint object that is being passed, it is not giving false or wrong Points.

custom_menu.cpp:

#include "custom_menu.h"
#include <iostream>

Menu::Menu(QWidget *parent) :
    QMenu(parent)
{
    QAction *action1 = addAction("Action1");
    connect(action1, &QAction::triggered, this, &Menu::onAction1Triggered);
}
void Menu::onAction1Triggered(){
    std::cout<<"Working";
}

void Menu::showMenu(const QPoint &pos)
{
    qDebug() << pos;
    exec(mapToGlobal(pos));
}

The Custom Label class:

QLabelDragDrop::QLabelDragDrop(QWidget *parent) : QLabel(parent)
{
    setAcceptDrops(true);
    setMouseTracking(true);
}

void QLabelDragDrop::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton) {
        drag_start_position = event->pos();
    }
}
void QLabelDragDrop::mouseMoveEvent(QMouseEvent *event){
    {
        if (!(event->buttons() & Qt::LeftButton)) {
            return;
        }
        if ((event->pos() - drag_start_position).manhattanLength() < QApplication::startDragDistance()) {
            return;
        }
        QDrag *drag = new QDrag(this);
        QMimeData *mimeData = new QMimeData;
        mimeData->setImageData(this->pixmap().toImage());
        drag->setMimeData(mimeData);
        QPixmap pixmap(this->size());
        QPainter painter(&pixmap);
        painter.drawPixmap(this->rect(), this->grab());
        painter.end();
        drag->setPixmap(pixmap);
        //drag->setHotSpot(event->pos() - parentWidget()->pos() - this->pos());
        drag->setHotSpot(event->pos()); // - this->pos()
        drag->exec(Qt::MoveAction);
    }
}

The Custom Frame class that is the parent of Label showing context menu:

custom_frame::custom_frame(QWidget *parent)
    : QFrame(parent)
{
    setAcceptDrops(true);
}

void custom_frame::dragEnterEvent(QDragEnterEvent *event)
{
    if (event->mimeData()->hasImage()) {
        event->acceptProposedAction();
    }
}

void custom_frame::dropEvent(QDropEvent *event)
{
    if (event->mimeData()->hasImage()) {
        QLabel *label = qobject_cast<QLabel*>(event->source());
        if (label) {
            label->move(event->position().toPoint());
            event->setDropAction(Qt::MoveAction);
            event->accept();
            update();
        }

    }
}

void custom_frame::paintEvent(QPaintEvent* event) {
    QFrame::paintEvent(event);
    for (int i = 1; i<=label_count; i++){
        char c[] = "label_";
        QString num = QString::number(i);
        QString name = QString::fromUtf8(c, 6);
        QLabel* my_label = this->findChild<QLabel*>(name + num);
        QPainter painter(this);
        painter.setPen(QPen(Qt::black, 2));
        QPoint start = my_label->frameGeometry().center();
        QGroupBox* groupBox = this->findChild<QGroupBox*>("groupBox");
        QPoint end = groupBox->frameGeometry().center();
        painter.drawLine(start, end);
    }
}

The mainwindow.cpp:

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    QObject::connect(ui->pushButton, &QPushButton::clicked, this, &MainWindow::Create_Device);
}

MainWindow::~MainWindow()
{
    delete ui;
}
void MainWindow::Create_Device(){
    QLabelDragDrop *label = new QLabelDragDrop(ui->frame);
    label->setGeometry(QRect(200,20,80,40));
    ui->frame->label_count++;
    char c[] = "label_";
    QString num = QString::number(ui->frame->label_count);
    QString name = QString::fromUtf8(c, 6);
    QString obj_name = name+num;
    Menu *m_menu = new Menu;
    QPixmap pixmap("C:\\Users\\Farhan Ahmed\\Documents\\untitled\\router.png");
    QPixmap scaledPixmap = pixmap.scaled(QSize(70, 70), Qt::KeepAspectRatio, Qt::SmoothTransformation);    
    label->setObjectName(obj_name);
    //label->setText("New Label");
    label->setPixmap(scaledPixmap);
    label->setContextMenuPolicy(Qt::CustomContextMenu);
    label->setFixedSize(scaledPixmap.size());
    label->setToolTip(obj_name);
    label->setStatusTip(obj_name);
    bool success = connect(label, &QWidget::customContextMenuRequested, m_menu, &Menu::showMenu);
    Q_ASSERT(success);
    label->show();
    ui->frame->update();
}

To summarize, the problem is: The QDragDropLabel is created in mainwindow.h in Create_Devices(). The Menubar is also added there. The slot connected is in the custom_menu.h the function name is showMenu().

The pos prints the same thing if I do not move my mouse and perform right click again and again. But the custom menu display at new location every time.


Solution

  • Problem:

    CustomContextMenu not showing at given points Qt c++

    This is a false statement, your menu is appearing exactly at the pos you're feeding it.

    G.M. said:

    The pos argument passed to Menu::showMenu is the position relative to the widget for which the menu is being requested -- not relative to the menu itself. So exec(mapToGlobal(pos)) is possibly the culprit.

    And I'll add to it the following:

    From the docs of Qt6: mapToGlobal():

    Translates the widget coordinate pos to global screen coordinates. For example, mapToGlobal(QPointF(0,0)) would give the global coordinates of the top-left pixel of the widget.

    And a context menu's default position is top left corner of the screen, which is (x=0,y=0). So, when you pass it pos, it will return its current global position of your menu, plus pos, which is the cursor's current position relative to your label. That is why it always increments, while that message provides you with the same result if you don't move your cursor.

    If I was asked to summarize the problem in an equation, I would use this one:

    menu.globalDiagonalAnimation() = menu.globalPos() + cursor.localPos(label)
    

    Correction:

    So your question should be:

    How to display a context Menu at cursor's position in Qt?

    Solutions:

    Which answer is to simply use QCursor::pos():

    Returns the position of the cursor (hot spot) of the primary screen in global screen coordinates.

    exec(QCursor::pos());
    

    To convert positions:

    auto *w = static_cast<QLabel*>(parent());
    exec(w->mapToGlobal(pos));
    

    You must set the label as your menu's parent, and then use its mapToGlobal(), to convert pos to a global position.

    The static cast is necessary because if you use parent(), you'll get a QObject, which does not have a mapToGlobal() as a member.