Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Create widget modal or detect mousePressEvent outside QDialog



  • Hi, In my app, I want to display a window modal at a moment. But if the user press the mouse outside the widget, the widget will be closed.
    I tried to detect in a QDialog a solution to detect mouse outside the widget but I can't
    And I tried to create a new widget as a modal widget too but it's same, I can't...
    Anyone?

    My class :

    class ModalPopup : public QWidget
    {
    	Q_OBJECT
    
    public:
    	ModalPopup(QPoint cursor);
    	~ModalPopup(void);
    	QString getAct() { return act; }
    
    private:
    	void onActClicked(void);
    
    	QVBoxLayout *layout;
    	QPushButton *ActNew;
    	QPushButton *ActAdd;
    	QPushButton *ActSub;
    	QString act;
    
    protected:
    	void mousePressEvent(QMouseEvent *event) override;
    };
    
    ModalPopup::ModalPopup(QPoint cursor)
    {
    	grabMouse();
    	setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
    
    	ActNew = new QPushButton(tr("new"));
    	connect(ActNew, &QPushButton::clicked, this, &ModalPopup::onActClicked);
    
    	ActAdd = new QPushButton(tr("add"));
    	connect(ActAdd, &QPushButton::clicked, this, &ModalPopup::onActClicked);
    
    	ActSub = new QPushButton(tr("sub"));
    	connect(ActSub, &QPushButton::clicked, this, &ModalPopup::onActClicked);
    
    	layout = new QVBoxLayout(this);
    	layout->addWidget(ActNew);
    	layout->addWidget(ActAdd);
    	layout->addWidget(ActSub);
    	move(cursor);
    	show();
    }
    
    ModalPopup::~ModalPopup(void)
    {
    	delete ActNew;
    	delete ActAdd;
    	delete ActSub;
    	delete layout;
    }
    
    void ModalPopup::onActClicked(void)
    {
    	act = qobject_cast<QPushButton *>(sender())->text();
    	close();
    }
    
    void ModalPopup::mousePressEvent(QMouseEvent *event)
    {
    	QRect pos = this->geometry();
    	QPoint cursor = event->globalPos();
    
    	if (cursor.x() < pos.x() || cursor.x() > pos.x() + pos.width())
    		close();
    	else if (cursor.y() < pos.y() || cursor.y() > pos.y() + pos.height())
    		close();
    }
    

    Thanks in advance



  • Hi,

    I have solved my problem with QMenu, adding QAction, and exec(pos)

    Thanks a lot :)


  • Lifetime Qt Champion

    Hi,

    The goal of a modal dialog is to block all inputs outside of the dialog. Can you explain your use case ?



  • I want to close the widget and don't play the sound.


  • Lifetime Qt Champion

    What sound ?



  • The beep of the QDialog when I presse the left mouse button outside the QDialog



  • So ? Nobody ? :/


  • Lifetime Qt Champion

    Then why make it a modal dialog at all ?



  • Yesterday I have tried to do that with a QWidget no modal but when I want to show it, it doesn't work


  • Lifetime Qt Champion

    it doesn't work is not a statement that allows to go further.

    Show what you are doing, how you are using your dialog etc.



  • @bozo6919
    As @SGaist says, if you really care about getting rid of the native-OS-windows behaviour of beeping when clicking outside a modal dialog (why?), then you should use a modeless dialog instead and implement your desired behaviour in its totality. Though it's a lot easier to just accept the natural window manager behaviour....



  • My class modalpopup :

    	class ModalPopup : public QWidget
    	{
    		Q_OBJECT
    
    	public:
    		ModalPopup(QPoint cursor);
    		~ModalPopup(void);
    		QString getAct() { return act; }
    
    	private:
    		void onActClicked(void);
    		QVBoxLayout *layout;
    		QPushButton *ActNew;
    		QPushButton *ActAdd;
    		QPushButton *ActSub;
    		QString act;
    
    	protected:
    		void mousePressEvent(QMouseEvent *event) override;
    	};
    

    It's implementation :

    #include "ModalPopup.hpp"
    
    ModalPopup::ModalPopup(QPoint cursor)
    {
    	setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
    
    	ActNew = new QPushButton(tr("new"));
    	connect(ActNew, &QPushButton::clicked, this, &ModalPopup::onActClicked);
    
    	ActAdd = new QPushButton(tr("add"));
    	connect(ActAdd, &QPushButton::clicked, this, &ModalPopup::onActClicked);
    
    	ActSub = new QPushButton(tr("sub"));
    	connect(ActSub, &QPushButton::clicked, this, &ModalPopup::onActClicked);
    
    	layout = new QVBoxLayout(this);
    	layout->addWidget(ActNew);
    	layout->addWidget(ActAdd);
    	layout->addWidget(ActSub);
    
    	move(cursor);
    	show();
    }
    
    ModalPopup::~ModalPopup(void)
    {
    	delete ActNew;
    	delete ActAdd;
    	delete ActSub;
    	delete layout;
    }
    
    void ModalPopup::onActClicked(void)
    {
    	act = qobject_cast<QPushButton *>(sender())->text();
    	close();
    }
    
    void ModalPopup::mousePressEvent(QMouseEvent *event)
    {
    	if (event->buttons() != Qt::LeftButton)
    		return;
    	QRect pos = this->geometry();
    	QPoint cursor = event->globalPos();
    
    	if (cursor.x() < pos.x() || cursor.x() > pos.x() + pos.width())
    		close();
    	else if (cursor.y() < pos.y() || cursor.y() > pos.y() + pos.height())
    		close();
    }
    

    His call :

    
    void FlowView::dropEvent(QDropEvent *event)
    {
    	ModalPopup modal(event->pos());
    
    	foreach(const QUrl &url, event->mimeData()->urls()) {
    		if (modal.getAct() == "new")
    			_scene->loadThisPath(url.toLocalFile());
    		else if (modal.getAct() == "add")
    			_scene->add(url.toLocalFile());
    	}
    }
    

    But the widget is never display



  • @bozo6919
    Your modal is modeless now. The constructor ends with a show(). That does not block, and it will not actually show till the next time the event loop is hit. Your code (dropEvent) constructs it, then immediately tests some action, and then the local variable modal goes out of scope and the function exits, destroying the dialog (step through in debugger or put in some qDebug()s to see the flow). Hence you probably see nothing.

    If you are going to use a modeless dialog, it's your job to allow the event loop to be hit and the modeless to stay in existence & open until such time as the user does something where you want it closed.



  • Yes, what I'm searching for, it's that my program in dropEvent wait that my modalPopup is closed. But I don't know how to do that. So my program continues and my modalPopup is destroyed, as you said. But he has to wait for the close of my modalPopup.



  • @bozo6919
    If you cannot rewrite your logic such that the actions are executed from within the dialog on button press, instead of returning to calling code and having it do it, then I think (untested) you can simulate a modal dialog via something like:

    1. Create a QEventLoop at the very start of dropEvent.
    2. Pass the QEventLoop * to the ModalPopup constructor.
    3. Have dropEvent go loop.exec() after the ModalPopup constructor.
    4. Have the actions in ModalPopup go loop.quit.

    Or, you could put the QEventLoop into the ModalPopup constructor right at the end after the show() (or in another function) if that seems easier, keeping all the code in ModalPopup.



  • I have already tried this without result... But this was my first time that I use QEventLoop, so I'm going to try this method again ^^



  • I have try again :

    #include "ModalPopup.hpp"
    
    ModalPopup::ModalPopup(QPoint cursor)
    	: loop(this)
    {
    	setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
    
    	ActNew = new QPushButton(tr("new"));
    	connect(ActNew, &QPushButton::clicked, this, &ModalPopup::onActClicked);
    
    	ActAdd = new QPushButton(tr("add"));
    	connect(ActAdd, &QPushButton::clicked, this, &ModalPopup::onActClicked);
    
    	ActSub = new QPushButton(tr("sub"));
    	connect(ActSub, &QPushButton::clicked, this, &ModalPopup::onActClicked);
    
    	layout = new QVBoxLayout(this);
    	layout->addWidget(ActNew);
    	layout->addWidget(ActAdd);
    	layout->addWidget(ActSub);
    
    	move(cursor);
    	show();
    	loop.exec(QEventLoop::AllEvents);
    }
    
    ModalPopup::~ModalPopup(void)
    {
    	delete ActNew;
    	delete ActAdd;
    	delete ActSub;
    	delete layout;
    }
    
    void ModalPopup::onActClicked(void)
    {
    	act = qobject_cast<QPushButton *>(sender())->text();
    	loop.quit();
    	close();
    }
    
    void ModalPopup::mousePressEvent(QMouseEvent *event)
    {
    	if (event->buttons() != Qt::LeftButton)
    		return;
    	QRect pos = this->geometry();
    	QPoint cursor = event->globalPos();
    
    	if (cursor.x() < pos.x() || cursor.x() > pos.x() + pos.width()) {
    		loop.quit();
    		close();
    	} else if (cursor.y() < pos.y() || cursor.y() > pos.y() + pos.height()) {
    		loop.quit();
    		close();
    	}
    }
    
    

    My widget is displayed, but I never enter in my function mousePressEvent so, my window is never closed :/



  • @bozo6919
    And are you saying your mousePressEvent() function would be entered if you removed the loop.exec()? How do you know the function is not even entered (please put some debug in)?

    I don't really get the concept here. I believe your ModalPopup::mousePressEvent() is intending to recognise that a mouse click's coordinates are outside of the "dialog" and then close itself, right? But why would a mouse press event outside of a window be sent to that window? I would have thought you'd need this test in the app/main window's event filter or widget or something, not in an override of the window you want to close? Unless I'm not getting it....

    (P.S. There's a window style like "Popup" [Qt::Popup] or similar, I believe, which does auto-dismiss on click outside without beep, which I think is what you're wanting. I don't know if it stays there while you interact with its buttons. But if that would do what you want it would be a lot simpler, you sure that won't do you...)

    EDIT: The longer I think about this, I wonder if the last paragraph above is your problem? Go back to how you started. If I were doing this I would expect to create a modeless dialog (you said you know how to do that). But the code to recognise the "click outside" (elsewhere in your app, e.g. on the main window) cannot be written in the dialog (I assume it won't be sent the event). I would expect that code to be in your other window or main event loop, and there you would recognise a click on it and tell the modeless dialog to close if it's open. No?

    I see stuff like https://stackoverflow.com/a/16513021/489865, where the OP does

    2.I installed an event filter with my Qt::tool window and I began receiving events that assisted me in understanding when other parts of my application were clicked, or if the application itself lost focus to another application. This was what I needed, functionality wise. I could also get an event when users click the non-client areas of the application's main window, such as the windows caption so that I can close it when dragging begins etc.

    All sounds hideous...



  • Thanks a lot, I have some result, my window displays and closed when I click outside. But I'm searching for how close the QEventLoop when I switch windows whit Alt+Tab
    In my constructor, I added :

    	setWindowFlags(Qt::Popup | Qt::FramelessWindowHint);
    	setAttribute(Qt::WA_NoSystemBackground, true);
    	setAttribute(Qt::WA_TranslucentBackground, true);
    	setWindowFlag(Qt::WindowType::NoDropShadowWindowHint, true);
    	setFocusPolicy(Qt::ClickFocus);
    

    And in my class, a function reimplemented :

    
    void ModalPopup::leaveEvent(QEvent *event)
    {
    	if (event->type() == QEvent::ApplicationDeactivate)
    		loop.quit();
    }
    

    But with no effect :/



  • Hi,

    I have solved my problem with QMenu, adding QAction, and exec(pos)

    Thanks a lot :)


Log in to reply