Need help with QMenu subclass


#1

Hi all, I have a ‘nested’, a 2-tiered qmenus in which I have created a context menu for renaming and deleting. I created a subclass for the QMenus in hopes of making my code cleaner as I am unable to use eventFilter in my current code as it messes some functionalities…

For the renaming part, while it renames the first tiered, as soon as I tried to do the same for the second-tiered item, the prompt actually renames the first-tiered item.

The following is a code portion of the QMenu subclass that I did and if you do the following:

  • Right click and add in a new object called main
  • In main, create another object called sub
  • (This is an extra right-click) If you perform another right-mouse click on main and select rename options, and have it changed to ‘newMain’, this works
  • Perform the same action as detailed in point #3, but this time, rename on sub to newSub
  • If you open up the overall menu, noticed that newMain was changed to newSub, while sub remains unchanged.

Could someone kindly shed some light towards my QMenu subclass on where I have done it wrong? Appreciate in advance for any replies.

import functools
import sys
from PyQt4 import QtGui, QtCore


class QAddAction(QtGui.QAction):
    def __init__(self, icon=None, text="Add Item", parent=None):
        if icon:
            super(QAddAction, self).__init__(icon, text, parent)
        else:
            super(QAddAction, self).__init__(text, parent)

class QRenameAction(QtGui.QAction):
    def __init__(self, icon=None, text="Rename Item", parent=None):
        if icon:
            super(QRenameAction, self).__init__(icon, text, parent)
        else:
            super(QRenameAction, self).__init__(text, parent)

class QDeleteAction(QtGui.QAction):
    def __init__(self, icon=None, text="Delete Item", parent=None):
        if icon:
            super(QDeleteAction, self).__init__(icon, text, parent)
        else:
            super(QDeleteAction, self).__init__(text, parent)


class QCustomMenu(QtGui.QMenu):
    """Customized QMenu."""

    def __init__(self, title, parent=None):
        super(QCustomMenu, self).__init__(title=str(title), parent=parent)
        self.setup_menu()

    def setup_menu(self):
        self.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu)

    def contextMenuEvent(self, event):
        no_right_click = [QAddAction, QRenameAction, QDeleteAction]
        if any([isinstance(self.actionAt(event.pos()), instance) for instance in no_right_click]):
            return
        self.show_adv_qmenu()

    def show_adv_qmenu(self):
        qmenu = QCustomMenu(self)
        rename_menu_action = QRenameAction(text= "Rename Item", parent=self)
        rename_slot = functools.partial(self.rename_menu_item)
        rename_menu_action.triggered.connect(rename_slot)
        qmenu.addAction(rename_menu_action)

        delete_menu_action = QDeleteAction(text="Delete Item", parent=self)
        delete_slot = functools.partial(self.delete_menu_item, delete_menu_action)
        delete_menu_action.triggered.connect(delete_slot)
        qmenu.addAction(delete_menu_action)
        qmenu.exec_(QtGui.QCursor().pos())

    def addAction(self, action):
        super(QCustomMenu, self).addAction(action)

    def rename_menu_item(self):
        new_name, ok = QtGui.QInputDialog.getText(
            self,
            "Rename Menu Item ({0})".format(self.title()),
            "New name:"
        )
        if ok:
            self.setTitle(new_name)

    def delete_menu_item(self, action):
        reply = QtGui.QMessageBox.question(self, 'Message',
            "Really Delete this item?", QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
        if reply == QtGui.QMessageBox.Yes:
            parent = action.parent()
            print parent
            self.remove_menu_item(self)
        else:
            event.ignore()


    def err_popup(self):
        """Prompts a notification popup."""
        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_()


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


    def initUI(self):         
        self.setGeometry(300, 300, 300, 200)
        self.setWindowTitle('Context menu')    

        self.qmenu = QCustomMenu(title='', parent=self)
        add_item_action = QtGui.QAction('Add new item', self,
            triggered=self.add_new_item)
        self.qmenu.addAction(add_item_action)


    def contextMenuEvent(self, event):
        action = self.qmenu.exec_(self.mapToGlobal(event.pos()))



    def add_new_item(self):
        main_menu_name, ok = QtGui.QInputDialog.getText(
            self,
            'Main Menu',
            'Name of new Menu Item:'
        )
        if ok:
            self._addMenuItemTest(main_menu_name)

    def _addMenuItemTest(self, main_menu_name):
        base_qmenu = QCustomMenu(title=main_menu_name, parent=self)
        
        base_qmenu.setTearOffEnabled(True)
        add_item_action = QAddAction(None, 'Add Item', base_qmenu)
        slot = functools.partial(self.add_sub_item, base_qmenu)
        add_item_action.triggered.connect(slot)
        base_qmenu.addAction(add_item_action)
        self.qmenu.addMenu(base_qmenu)


    def add_sub_item(self, base_menu):
        sub_menu_name, ok = QtGui.QInputDialog.getText(
            self,
            'Sub Menu',
            'Name of new Sub Item:'
        )
        if ok:
            action = QtGui.QAction(sub_menu_name, self)
            slot = functools.partial(
                self._callActionItem,
                str(base_menu.title()),
                str(sub_menu_name)
            )
            action.setCheckable(True)
            action.setChecked(True)
            action.toggled.connect(slot)
            base_menu.addAction(action)

    def _callActionItem(self):
        pass



if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    window = Example()
    window.show()
    sys.exit(app.exec_())