Question

I'm trying to implement an "open" button such that if a user presses it and holds, a menu of applications for the user to choose from will pop up; but as soon as the user releases the mouse, the menu should disappear. if the user releases the mouse without choosing an application on the menu, it should open the file with the default application. I'm implementing this button as a QToolButton and connect the signals as follows:

self.ui_open_btn.pressed.connect(self._onOpenBtnPressed)
self.ui_open_btn.triggered.connect(self._onOpenBtnTriggered)

def _onOpenBtnPressed(self):
    self.ui_open_btn.showMenu()

def _onOpenBtnTriggered(self, action):
    application_name = action.text()
    # code to launch the application

Right now, when the user presses the button, the menu will pop up. However, the menu is still there when the user release the button, and action on the menu is triggered by clicking on it. I tried under both DelayedPopup and InstantPopup mode. As long as the menu has been set for self.ui_open_btn, I can no longer catch any released signal. How can I hide the menu when the user releases the mouse? How can the action on the menu be triggered by releasing the mouse?

--added---

I found another problem about using QToolButton: the menu always pops up when the button is pressed. Instead, I would like to catch the pressed signal, do some check to determine if the menu should pop up or not. So I changed my approach to write my customized toolbutton by subclassing QPushButton and QMenu. Please see my code posed in the answer below.

Thanks.

Was it helpful?

Solution 2

I managed to achieve what I want by subclassing QPushButton and QMennu:

class MyMenu(QtGui.QMenu):
    """ Custom menu which will close when mouse is released. ""'"
    def mouseReleaseEvent(self, event):
        action = self.actionAt(event.pos())
        self.triggered.emit(action)
        self.close()

class MyButton(QtGui.QPushButton):
    triggered = QtCore.pyqtSignal("QAction")

    def __init__(self, menu=None, parent=None):
        super(MyButton, self).__init__(parent)
        self.setMenu(menu)

    def menu(self):
        return self._menu

    def setMenu(self, menu):
        self._menu = menu if menu else MyMenu(self)
        self._menu.triggered.connect(self.triggered.emit)

and in the QDialog containing this button, I do the following:

menu = MyMenu(self)
# insert here code to add actions to menu
self.open_btn = MyButton(parent=self, menu=menu)
self.open_btn.pressed.connect(self._onOpenBtnPressed)
self.open_btn.triggered.connect(self._onOpenBtnTriggered)

def _onOpenBtnPressed(self):
    # insert here code to check whether we should pop up the menu
    pos = self.mapToGlobal(self.open_btn.pos())
    pos.setY(pos.y() + self.open_btn.height())
    self.open_btn.menu().move(pos)
    self.open_btn.menu().show()

def _onOpenBtnTriggered(self, action):
    if action:
        application_name = str(action.text())
        # insert here code to launch this application
    else:
        # insert here code to launch the default application
    self.close() # close this dialog

OTHER TIPS

Create a custom QToolButton that will filter events from the menu, and react on the mouse release event received by the menu:

class MyToolButton(QtGui.QToolButton):
    def __init__(self, *args):
        QtGui.QToolButton.__init__(self, *args)
    def eventFilter(self, menu, event):
        if event.type() == QtCore.QEvent.MouseButtonRelease:
            if self.underMouse():
                menu.close()
                # and now do default action
                print "doing default action"
                return True
        return False

Install the event filter after you have set the menu:

self.ui_open_btn.menu().installEventFilter(self.ui_open_btn)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top