Search code examples
pythonpyside2

Can I have eventFilter to ignore widget's events


Can I set my eventFilter to ignore the 'default' events of a widget eg. mousePressEvents etc? Or can these two be mixed in the first place?

In my code, I have a mousePressEvent and a custom event that I have created for a right-click menu for the menu items within a QMenu.

And from time to time, as I execute my code/ or when doing the right mouse click on the tab, I am seeing either:

AttributeError: 'PySide2.QtCore.QEvent' object has no attribute 'pos'

or sometimes:

RuntimeWarning: Invalid return value in function QTabBar.eventFilter, expected bool, got NoneType.

As such, is there a better way to get around it, or should I re-factor of how I proceed with the Rename Item and Delete Item as denoted in show_adv_qmenu()?

class MyWin(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(MyWin, self).__init__()

        self.rename_menu_allowed = False

        central_widget = QtGui.QWidget()
        self.setCentralWidget(central_widget)
        vlay = QtGui.QVBoxLayout(central_widget)
        hlay = QtGui.QHBoxLayout()
        vlay.addLayout(hlay)
        vlay.addStretch()

        self.add_button = QtGui.QToolButton()
        self.tab_bar = QtGui.QTabBar(self)
        self.add_button.setIcon(QtGui.QIcon('add.png'))
        self.add_button.setMenu(self.set_menu())
        self.add_button.setPopupMode(QtGui.QToolButton.InstantPopup)

        self.tab_bar.setTabButton(
            0,
            QtGui.QTabBar.ButtonPosition.RightSide,
            self.add_button
        )

        hlay.addWidget(self.add_button)
        hlay.addWidget(self.tab_bar)

        self.my_extra_menus = defaultdict(list)

    def set_menu(self):
        menu_options = ['food', 'drinks', 'snacks']
        qmenu = QtGui.QMenu(self.add_button)
        for opt in menu_options:
            qmenu.addAction(opt, partial(self.set_new_tab, opt))
        return qmenu

    def set_new_tab(self, opt):
        self.tab_bar.addTab(opt)

    def mousePressEvent(self, event):
        index = self.tab_bar.tabAt(event.pos())

        if event.button() == QtCore.Qt.RightButton:
            self._showContextMenu(event.pos(), index)

        else:
            super(MyWin, self).mousePressEvent(event)


    def eventFilter(self, obj, event):
        # Custom event only for right mouse click within the qmenu

        if obj == self.qmenu:
            if event.type() == QtCore.QEvent.MouseButtonPress and event.button() == QtCore.Qt.RightButton:
                index = self.tab_bar.tabAt(event.pos())
                action = obj.actionAt(event.pos())
                self.show_adv_qmenu(obj, action, index)

    def _showContextMenu(self, position, index):
        self.qmenu = QtGui.QMenu(self)
        self.qmenu.setTearOffEnabled(True) # New item is not shown in tearoff mode
        self.qmenu.setTitle(self.tab_bar.tabText(index))

        self.qmenu.installEventFilter(self)

        add_item_action = QtGui.QAction('Add Menu Item', self)
        slot = partial(self._addMenuItem, index)
        add_item_action.triggered.connect(slot)
        self.qmenu.addAction(add_item_action)
        self.qmenu.addSeparator()

        if self.my_extra_menus.get(index):
            for menuItem in self.my_extra_menus[index]:
                self.qmenu.addMenu(menuItem)

        self.qmenu.addSeparator()

        global_position = self.mapToGlobal(self.pos())
        self.qmenu.exec_(QtCore.QPoint(
            global_position.x() - self.pos().x() + position.x(),
            global_position.y() - self.pos().y() + position.y()
        ))

    def _addMenuItem(self, index):
        # For first tier menu
        first_tier_menu = []
        for i in self.qmenu.actions():
            first_tier_menu.append(i.text())

        new_menu_name, ok = QtGui.QInputDialog.getText(
            self,
            "Name of Menu",
            "Name of new Menu Item:"
        )

        if ok:
            if new_menu_name in list(filter(None, first_tier_menu)):
                self.err_popup()
            else:
                menu = QtGui.QMenu(new_menu_name, self)
                menu.setTearOffEnabled(True) # New item is shown in tearoff mode, unless I close and re-tearoff
                add_item_action = QtGui.QAction('Add sub Item', menu)
                slot = partial(self._addActionItem, menu)
                add_item_action.triggered.connect(slot)
                menu.addAction(add_item_action)
                menu.addSeparator()

                self.my_extra_menus[index].append(menu)

    def _addActionItem(self, menu):
        # For second tier menu
        new_item_name, ok = QtGui.QInputDialog.getText(
            self,
            "Name of Menu Item",
            "Name of new Menu Item:"
        )

        second_tier_menu = []
        for i in menu.actions():
            second_tier_menu.append(i.text())

        if ok:
            if new_item_name in list(filter(None, second_tier_menu)):
                self.err_popup()
            else:
                action = QtGui.QAction(new_item_name, self)
                slot = partial(self._callActionItem, new_item_name)
                action.setCheckable(True)
                action.toggled.connect(slot)
                menu.addAction(action)


    def _callActionItem(self, name, flag):
        # Function for the checked items..
        print name
        print flag

    def show_adv_qmenu(self, obj, action, index):
        self.adv_qmenu = QtGui.QMenu()

        rename_menu_action = QtGui.QAction('Rename Item', self)
        rename_slot = partial(self.rename_menu_item, obj, action)
        rename_menu_action.triggered.connect(rename_slot)
        self.adv_qmenu.addAction(rename_menu_action)

        delete_menu_action = QtGui.QAction('Delete Item', self)
        delete_slot = partial(self.delete_menu_item, obj, action, index)
        delete_menu_action.triggered.connect(delete_slot)
        self.adv_qmenu.addAction(delete_menu_action)

        # global_position = self.mapToGlobal(self.pos())
        # self.adv_qmenu.exec_(QtCore.QPoint(global_position))

        self.adv_qmenu.exec_(QtGui.QCursor().pos())


    def rename_menu_item(self, obj, selected_action):       
        rename_name, ok = QtGui.QInputDialog.getText(
            self,
            "renaming",
            "New name:"
        )

        if ok:
            for i in obj.actions():
                if selected_action.text() == i.text():
                    i.setText(rename_name)
                    print "Name changed : {0} --> {1}".format(selected_action.text(), i.text())


    def delete_menu_item(self, obj, selected_action, index):
        obj.removeAction(selected_action)



    def err_popup(self):
        msg = QtGui.QMessageBox()
        msg.setIcon(QtGui.QMessageBox.Critical)
        msg.setText("Input name already exists. Please check.")
        msg.setWindowTitle('Unable to add item')
        msg.setStandardButtons(QtGui.QMessageBox.Ok)
        msg.exec_()

Sorry for the long code..


Solution

  • eventFilter must return a boolean, but in your case it does not return anything so the second error is throwing you. Also I have improved your logic:

    def eventFilter(self, obj, event):
        # Custom event only for right mouse click within the qmenu
        if obj is self.qmenu and event.type() == QtCore.QEvent.MouseButtonPress:
            if event.button() == QtCore.Qt.RightButton:
                index = self.tab_bar.tabAt(event.pos())
                action = self.qmenu.actionAt(event.pos())
                if index != -1 and action is not None:
                    self.show_adv_qmenu(obj, action, index)
        return super(MyWin, self).eventFilter(obj, event)