Qt: Show popup menu, close if I click anywhere outside of it - c++

I want to show a menu in Qt with the following functionality: clicking anywhere outside of the menu will close it, but the click will be processed by the receiving widget. As far as I know, this rules out using setWindowFlag(Qt::Popup) because the click that closed the popup will be 'eaten' and not processed any further. I'd love to know if there is a way around this.
For the time being, I'm overriding focusOutEvent like so:
class Menu : public QWidget
{
Q_OBJECT
public:
Menu(QWidget* parent = nullptr)
: QWidget(parent){
setWindowFlags(Qt::FramelessWindowHint);
resize(200, 100);
}
public slots:
void showMenu(QPoint point)
{
move(point);
show();
setFocus(); // so that focusOutEvent will be trigerred
}
protected:
void focusOutEvent(QFocusEvent *event) override
{
hide();
}
};
And I'm using it like this:
class Window : public QMainWindow
{
Q_OBJECT
public:
Window(): m_menu()
{
auto* list = new QListWidget();
list->addItems(QStringList() << "item1" << "item2" << "item3");
connect(list, &QListWidget::itemClicked, [this](){
m_menu.showMenu(QCursor::pos());
});
// Layout stuff
auto central_widget = new QWidget();
auto layout = new QVBoxLayout(central_widget);
layout->addWidget(list);
layout->addItem(new QSpacerItem(20,200, QSizePolicy::Minimum, QSizePolicy::Expanding));
setCentralWidget(central_widget);
}
public:
~Window(){}
private:
Menu m_menu;
};
Single clicking on any of the items in the list will show the menu and clicking anywhere on the grey background will close the menu.
The issue I'm facing is that when the menu is shown, focus is lost from the main window (photo on the right, the titlebar buttons and selected item are greyed out). Focus is lost because of calling setFocus on the menu but if I don't use it then the QFocusOutEvents are not trigerred and the menu doesn't close.
The same happens if I use setAttribute(Qt::WA_ShowWithoutActivating) on the menu.
So here are my questions:
Is there a way to use Qt::Popup without the 'eaten' clicks issue?
If not, is there a way to show the menu without losing the focus from the main window but still trigger the focusOutEvents as necessary?
Using Qt 5.15.1 on MacOS 11.2.3. Thanks for reading through my long post!

Related

How to create Mouse right click menu option using eventFilter in Qt?

I have a QGraphicsView which contains many QGraphicsItem. If I click mouse right click on any QGraphicsItem, the item should get select and right menu options should appear and then I will choose one of the options among them.To do that I have installed eventFilter and through it, I am using ContextMenu to create right click menu. Right click menu are getting cretaed properly. But propblem is I am not getting how to connect them to some function so that I can write logic for it.
It means if I clicked on save option that particular QGraphicsItem should get select and I should be able to go to some function where I will write logic for saving.
bool myClass::eventFilter(QObject *watched, QEvent *event)
{
switch(event->type())
{
case QEvent::ContextMenu:
{
QMouseEvent *mouseEvent = static_cast<QMouseEvent*> (event);
menu = new QMenu(this);
option = menu->addMenu("CopyOption");
option->addAction("save");
menu->exec(mouseEvent->globalPos());
break;
}
default:
break;
}
}
In your approach you show a context menu when you have no information if any item is selected. It is rather bad idea. You don't want to show the context menu in any location of view. You have to check if a cursor mouse is over an item.
Why not to derive from QGraphicsItem and just overload mousePressEvent method. Inside this method check if right button of mouse is clicked. If so, show context menu and test which action is clicked. Minimal code would be:
class TItem : public QGraphicsItem
{
bool _selected = false;
public:
TItem(QGraphicsItem* parent = nullptr) : QGraphicsItem(parent) {}
QRectF boundingRect() const override { return QRectF(0,0,20,20); }
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override {
painter->fillRect(QRectF(0,0,20,20),_selected ? QColor(0,255,255) : QColor(255,255,0));
}
void mousePressEvent(QGraphicsSceneMouseEvent* e) override {
QGraphicsItem::mousePressEvent(e);
if (e->button() & Qt::RightButton) {
QMenu menu;
QAction* a1 = menu.addAction(QString("test1"));
QAction* a2 = menu.addAction(QString("test2"));
if(a2 == menu.exec(e->screenPos())) {
test2();
_selected = true;
update();
}
}
}
void test2() {
QMessageBox::information(nullptr,"test2","test2");
}
};
All the job with checking is an item is under mouse is done by QT, mousePressEvent is invoked only if it is necessary.
Another approach would be override mousePressEvent on QGraphicsView. Inside which:
get all items belonging to the scene
iterate over them, checking if an item is under mouse -> QGraphicsItem has isUnderMouse method
if any item is under mouse, create QMenu and show it
check selected QAction, if it is save call a proper method which doing the save and mark the item as selected

When and how to properly destroy QMenu context menu?

I allow custom context menu to appear over a table. This is how the menu is generated, using a generic function that accepts target widget and coordinates:
#include <QMenu>
void MainWindow::makeContextMenu(const QPoint& pos, QWidget* target)
{
QMenu *menu = new QMenu(this);
menu->addAction(new QAction("Action 1", menu));
menu->addAction(new QAction("Action 2", menu));
menu->addAction(new QAction("Action 3", menu));
// Notify window about clicking
QObject::connect(menu, &QMenu::triggered, this, &MainWindow::menuClicked);
// If this is a scroll area, map coordinates to real app coordinates
if(QAbstractScrollArea* area = dynamic_cast<QAbstractScrollArea*>(target))
menu->popup(area->viewport()->mapToGlobal(pos));
else
menu->popup(pos);
}
The problem is that the QMenu* menu never gets destroyed and removed from memory. It persists as MainWindow's child even after it's hidden.
What should I do? Can I set the menu to delete itself? Or should I reuse the same instance of menu or maybe save it into same pointer?
It doesn't have to be so complicated. That's it already:
menu->setAttribute(Qt::WA_DeleteOnClose);
That way when the QMenu is closed, the class is deleted as soon as the event loop is entered again. And it doesn't matter if an action was triggered or the popup was just closed.
To prove my answer, you can test it yourself by checking when the menu is created and if the 'deleted' message is triggered with the same address:
qDebug() << "created" << (qintptr)menu;
connect(menu, &QMenu::destroyed,
this, [menu]() { qDebug() << "deleted" << (qintptr)menu; });
From your code, it seems like menu should be deleted after this event has taken place?
// Notify window about clicking
QObject::connect(menu, &QMenu::triggered, this, &MainWindow::menuClicked);
Can I set the menu to delete itself?
Yes, you can have the object delete itself like this:
// Notify window about clicking
QObject::connect(menu, &QMenu::triggered, this, &MainWindow::menuClicked);
QObject::connect(menu, &QMenu::triggered, menu, &QMenu::deleteLater);
If you are worried about the order of those slots being called, see this
Or should I reuse the same instance of menu or maybe save it into same
pointer?
Well, you can do something like
//Your constructor
MainWindow::MainWindow(....)
{
menu = nullptr;
....
}
//Make context Menu
void MainWindow::makeContextMenu(const QPoint& pos, QWidget* target)
{
if(menu)
delete menu;
menu = new QMenu(this);
....
}
As for the MainWindow::~MainWindow() destructor, it will take care of the menu's clean up. Since MainWindow (which is a QObject derived class) automatically deletes all children
Lastly, you can simply have the menu as a member of MainWindow, and whenever you need to have fresh actions for menu, you can use QMenu::clear to delete all existing actions.
//Your constructor
MainWindow::MainWindow(....)
{
menu = new QMenu(this);
....
}
void MainWindow::makeContextMenu(const QPoint& pos, QWidget* target)
{
menu->clear();
//QMenu *menu = new QMenu(this);
menu->addAction(new QAction("Action 1", menu));
menu->addAction(new QAction("Action 2", menu));
menu->addAction(new QAction("Action 3", menu));
// Notify window about clicking
QObject::connect(menu, &QMenu::triggered, this, &MainWindow::menuClicked);
// If this is a scroll area, map coordinates to real app coordinates
if(QAbstractScrollArea* area = dynamic_cast<QAbstractScrollArea*>(target))
menu->popup(area->viewport()->mapToGlobal(pos));
else
menu->popup(pos);
}
It is possible to delete QMenu when it's hidden. I designed event filter class for that purpose:
#ifndef DELETEONHIDEFILTER_H
#define DELETEONHIDEFILTER_H
#include <QObject>
#include <QEvent>
class DeleteOnHideFilter : public QObject
{
Q_OBJECT
public:
explicit DeleteOnHideFilter(QObject *parent = 0) : QObject(parent) {}
protected slots:
bool eventFilter(QObject *obj, QEvent *event) override {
if(event->type() == QEvent::Hide) {
obj->deleteLater();
}
return false;
}
};
#endif // DELETEONHIDEFILTER_H
It can be used for other objects as well.

Qt menu bar without pull down menus (single level menu bar)

I would like to have a single level menu widget in Qt, which looks like a regular menu bar, but doesn't display pull down menu when an item on menu bar is clicked. My application needs only a few options and a single line menu bar with single click or single shortcut key activation seems to be the best fit.
I don't want a toolbar or a row of buttons. I don't want to design icons, I prefer a simple text for each option with shortcut key underlined.
Should I use QMenu or something else to implement this?
I'm sure, QToolBar is what you are looking for. It is a toolbar like you know from IDEs or Photoshop programs which shows options using icons or texts.
A code example
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow( QWidget* parent = 0 ) : QMainWindow(parent)
{
QToolBar* toolBar1 = new QToolBar(this);
QAction* action1 = toolBar1->addAction("one");
QObject::connect( action1, SIGNAL(triggered()), this, SLOT(onActionOne()));
action1->setShortcut(QKeySequence("ctrl+o"));
QAction* action2 = toolBar1->addAction("two");
QObject::connect( action2, SIGNAL(triggered()), this, SLOT(onActionTwo()));
action2->setShortcut(QKeySequence("ctrl+t"));
addToolBar(Qt::TopToolBarArea, toolBar1);
}
public slots:
void onActionOne(){ std::cout << "Action one!" << std::endl; }
void onActionTwo(){ std::cout << "Action Two!" << std::endl; }
};

Automatically populate "Edit" menu in menubar with current focus widget context menu

I've been looking for ways to implement the "Edit" menu of Qt application. The "Edit" menu contains items such as "Copy", "Cut", "Paste", etc. and which need to forward to the currently active widget.
I can't seem to find a standard or elegant way to do this. According to this question, it's not possible:
How to implement the "Edit" menu with "Undo", "Cut", "Paste" and "Copy"?
I recently had the idea to trigger a context menu event on the current active widget when the "Edit" menu is shown, via:
// create menus in MainWindow constructor
...
edit_menu = menuBar()->addMenu(tr("&Edit"));
connect(edit_menu, SIGNAL(aboutToShow()), this, SLOT(showEditMenu()));
...
// custom slot to handle the edit menu
void MainWindow::showEditMenu()
{
QWidget* w = QApplication::focusWidget();
// show the context menu of current focus widget in the menubar spot
QPoint global_pos = edit_menu->mapToGlobal(edit_menu->rect().bottomLeft());
QPoint pos = w->mapFromGlobal(global_pos);
QApplication::sendEvent(w, new QContextMenuEvent(QContextMenuEvent::Keyboard, pos, global_pos));
}
This shows the a context menu for the current widget great, but has some problems. For example, it takes focus away from the menubar, or if you click a different menubar item first, the menubar has focus, etc.
One partial solution would be to grab the context menu from the widget and copy it's items into the edit menu dynamically. Is there a way to do this?
Is there a better way build the edit menu in Qt?
Thanks for your help.
well, if you just need to create menu, you can always take actions from actions of a widget. To create edit actions for widgets, you can do something like this:
void MainWindow::addActions (QWidget* widget)
{
QAction * copyAction = new QAction("copy",widget);
if(connect(copyAction,SIGNAL(triggered()),widget,SLOT(copy())))
{
widget->addAction(copyAction);
qDebug()<<"success connection";
}
}
and
foreach (QObject * obj, centralWidget()->children())
{
QWidget * w = dynamic_cast<QWidget*>(obj);
if (w)
addActions(w);
}
then you always can update actions of edit menu with focused widget's actions
This may be not elegant, but it better, than imporssible. The main bad assumption in example is that copy slot is named copy
An elegant solution I guess would be to have a base class for the widgets you need to have copy/paste/... functionality, and have them register themselves with some parent class upon becoming active and unregistering when being deactivated. The actions can then just be connected to slots in the main window, which forwards them to the registered widget. You could even gray out the menu items if no widget is currently registered (e.g. because the active widget does not have the required functionality).
An example for registering/unregistering (untested):
class ActionWidget;
class ActionWidgetManager
{
public:
ActionWidgetManager() : actionWidget_(0){}
void registerWidget(ActionWidget* widget){ actionWidget_ = widget; }
void unregisterWidget(ActionWidget* widget)
{ if (actionWidget_ == widget) actionWidget_ = 0; }
bool hasActiveWidget() const{ return actionWidget_ != 0; }
ActionWidget* getActiveWidget(){ return actionWidget_; }
private:
ActionWidget* actionWidget_;
};
class ActionWidget : public QWidget
{
public:
ActionWidget(ActionWidgetManager* manager, QWidget* parent=0)
: manager_(manager), QWidget(parent) {}
~ActionWidget(){ manager_->unregisterWidget(this); }
void Widget::changeEvent(QEvent *event)
{
QWidget::changeEvent(event);
if(event->type() == QEvent::ActivationChange){
if(isActiveWindow()) {
manager_->registerWidget(this);
}
else {
manager_->unregisterWidget(this);
}
}
}
virtual void doCopy() = 0;
virtual void doPaste() = 0;
virtual void doUndo() = 0;
virtual void doCut() = 0;
private:
ActionWidgetManager* manager_;
};
Or something equivalent using signals and slots.

QDialog not closing right away when pressing the X, how to make it NOT on top?

I open QDialog window from QMainWindow. Now when I press the QDialog window
its not always closing in the first press - I need to press few times (3-4) to close it .
I have closeEvent slot that has simple event->accept(); inside it.
This is how I call the QDialog from the main window:
void MyManager::DialogContainerOpen(type t)
{
if(pMyDialogContainer == NULL)
{
pMyDialogContainer = new MyDialogContainer();
}
int returnVal = QDialog::Rejected;
if(!m_bContainer)
{
m_bContainer = true;
int returnVal = pMyDialogContainer->exec();
if(returnVal != QDialog::Accepted ) {
m_bContainer = false;
}
}
}
This is the first problem.
The second problem is how do i set the QDialog windows NOT to be allays on top? (I don’t want it to block the parent window.
UPDATE
well i found out that the function from the MainWindow that showing the contexMenu
and inside it has the connect single/slot is keeps to invoke so i just used the disconnect
i dont know if its the best sulotion but its working.
now i juat have the final problem .
here is the code i hope its ok
void MainWindowContainer::ShowContextMenu(const QPoint& pos) // this is a slot
{
QModelIndex modelIndx;
QPoint globalPos = ui.treeView_mainwindow->mapToGlobal(pos);
bool b1 = connect(OpenAction, SIGNAL(triggered()),m_SignalMapper, SLOT(map()) );
m_SignalMapper->setMapping(OpenAction,voidID);
bool b2 = connect(m_SignalMapper, SIGNAL(mapped(QString)), this, SLOT(OpenWin(QString)));
QAction* selectedItem = ContextMenu.exec(globalPos);
}
void MainWindowContainer::OpenWin(QString gid)
{
//disconnect(sender0, SIGNAL(overflow()),receiver1, SLOT(handleMathError()));
disconnect(m_SignalMapper, SIGNAL(mapped(QString)),this, SLOT(OpenWin(QString)));
disconnect(OpenAction,SIGNAL(triggered()),m_SignalMapper, SLOT(map()));
....
....
}
For your second question, the term you are looking for is modal vs modeless dialogs. The QDialog documentation tells exactly how you create non-modal dialogs:
Modeless dialogs are displayed using show(), which returns control to the caller immediately.
i.e. don't use exec() as that will make a modal dialog (which blocks the parent).
You should not connect the same signal/slot more than once unless you want the action run multiple times. All you need to do is to connect the QAction's signal to the slot once. This is usually done in the constructor (or a dedicated function called from the constructor) where you create the action.