Search code examples
pythonqtpyqtstyles

How to add padding to Qt Menu Items for Width?


Basically I am using Qt Menu by adding actions with Shortcuts. They look fine when the menu shows up.

But I want to add more spacing between the action item text and its shortcut. Is this possible? I couldn't find any function to do this.

setMinimumWidth works but I need add on top of the dynamic width computed by Qt.


Solution

  • Due to the complex way QMenu computes its size, using setMinimumWidth() is not a reliable option: once that size is set, there's no way to unset it, and the sizeHint() will always use that minimum.

    There are two possible workarounds for that, though.

    Force layouting of the menu

    This is a bit hacky and dirty, and might not work well in all situations. The trick is to force layouting again by adding a "fake" action that will be instantly removed.

    You can do this by connecting specific functions to the aboutToShow and aboutToHide signals:

    def resizeMenu(menu):
        for action in menu.actions():
            if action.shortcut():
                menu.setMinimumWidth(menu.sizeHint().width() + 50)
                menu.removeAction(menu.addAction(''))
                break
    
    def restoreMenu(menu):
        menu.setMinimumWidth(0)
        menu.removeAction(menu.addAction(''))
    
    
    menu.aboutToShow.connect(lambda: resizeMenu(menu))
    menu.aboutToHide.connect(lambda: restoreMenu(menu))
    

    You could also install an event filter on the application so that it will work for all menus:

    class MenuWatcher(QtCore.QObject):
        def eventFilter(self, obj, event):
            if isinstance(obj, QtWidgets.QMenu):
                if event.type() == event.Show:
                    for action in obj.actions():
                        if action.shortcut():
                            menu.setMinimumWidth(menu.sizeHint().width() + 50)
                            menu.removeAction(menu.addAction(''))
                            break
                elif event.type() == event.Hide:
                    if menu.minimumWidth():
                        menu.setMinimumWidth(0)
                        menu.removeAction(menu.addAction(''))
            return super().eventFilter(obj, event)
    
    # ...
    watcher = MenuWatcher()
    app.installEventFilter(watcher)
    

    Using a QProxyStyle

    By overriding the sizeFromContents() of a QProxyStyle we can check whether the option is a CT_MenuItem, and if it has a tabWidth (indicating that at least a previous action has a shortcut) or a \t in its text (meaning that it probably has a shortcut), we can add a specific width before returning the size provided by the default implementation:

    class ProxyStyle(QtWidgets.QProxyStyle):
        def sizeFromContents(self, ct, opt, size, widget=None):
            size = super().sizeFromContents(ct, opt, size, widget)
            if ct == self.CT_MenuItem and (opt.tabWidth or '\t' in opt.text):
                size.setWidth(size.width() + 50)
            return size
    
    # ...
    app.setStyle(ProxyStyle())