import os import logging from PySide6.QtCore import QRect, QSize, Qt, QRectF, QPoint, QEvent from PySide6.QtWidgets import QMenu, QStyle, QStyleOptionMenuItem, QStyleOption, QWidget, QApplication from PySide6.QtWidgets import QToolTip from PySide6.QtGui import QIcon, QImage, QPainter, QFont, QPalette, QColor, QKeySequence, QAction, QActionGroup # import kikka from Kikka.KikkaConst import * from Kikka.Utils.Singleton import singleton @singleton class KikkaMenu: _instance = None isDebug = False @staticmethod def getMenu(ghost_id, soul_id): pass # ghost = kikka.core.getGhost(ghost_id) # if ghost is not None: # return ghost.getSoul(soul_id).getMenu() # else: # logging.warning('menu lost') # return None @staticmethod def setAppMenu(menu): # QApplication.instance().trayIcon.setContextMenu(None) # QApplication.instance().trayIcon.setContextMenu(menu) pass @staticmethod def createSoulMainMenu(ghost): return # import kikka # # parent = QWidget(f=Qt.WindowType.Dialog) # mainmenu = KMenu(parent, ghost.ID, "Main") # # # shell list # menu = KMenu(mainmenu, ghost.ID, "Shells") # group1 = QActionGroup(parent) # for i in range(kikka.shell.getShellCount()): # shell = kikka.shell.getShellByIndex(i) # callbackfunc = lambda checked, name=shell.name: ghost.changeShell(name) # act = menu.addMenuItem(shell.unicode_name, callbackfunc, None, group1) # act.setData(shell.name) # act.setCheckable(True) # act.setToolTip(shell.description) # menu.setToolTipsVisible(True) # mainmenu.addSubMenu(menu) # # # clothes list # menu = KMenu(mainmenu, ghost.ID, "Clothes") # menu.setEnabled(False) # mainmenu.addSubMenu(menu) # # # balloon list # menu = KMenu(mainmenu, ghost.ID, "Balloons") # group2 = QActionGroup(parent) # for i in range(kikka.balloon.getBalloonCount()): # balloon = kikka.balloon.getBalloonByIndex(i) # callbackfunc = lambda checked, name=balloon.name: ghost.setBalloon(name) # act = menu.addMenuItem(balloon.unicode_name, callbackfunc, None, group2) # act.setCheckable(True) # mainmenu.addSubMenu(menu) # # optionmenu = KikkaMenu.createOptionMenu(parent, ghost) # mainmenu.addSubMenu(optionmenu) # # # debug option # if kikka.core.isDebug is True: # # def callbackfunction1(): # kikka.core.isDebug = not kikka.core.isDebug # kikka.core.repaintAllGhost() # # def callbackfunction2(): # kikka.shell.isDebug = not kikka.shell.isDebug # kikka.core.repaintAllGhost() # # menu = KMenu(mainmenu, ghost.ID, "Debug") # # act = menu.addMenuItem("Show ghost data", callbackfunction1) # act.setCheckable(True) # act.setChecked(kikka.core.isDebug is True) # # act = menu.addMenuItem("Show shell frame", callbackfunction2) # act.setCheckable(True) # act.setChecked(kikka.shell.isDebug is True) # # menu.addSubMenu(KMenu(menu, ghost.ID, "TestSurface")) # menu.addSubMenu(KikkaMenu.createTestMenu(menu)) # # mainmenu.addSeparator() # mainmenu.addSubMenu(menu) # pass # # from kikka_app import KikkaApp # callbackfunc = lambda: KikkaApp.this().exitApp() # mainmenu.addSeparator() # mainmenu.addMenuItem("Exit", callbackfunc) # return mainmenu @staticmethod def createSoulDefaultMenu(ghost): import kikka parent = QWidget(f=Qt.WindowType.Dialog) mainmenu = KMenu(parent, ghost.ID, "Main") # shell list menu = KMenu(mainmenu, ghost.ID, "Shells") group1 = QActionGroup(parent) for i in range(kikka.shell.getShellCount()): shell = kikka.shell.getShellByIndex(i) callbackfunc = lambda checked, name=shell.name: ghost.changeShell(name) act = menu.addMenuItem(shell.unicode_name, callbackfunc, None, group1) act.setData(shell.name) act.setCheckable(True) mainmenu.addSubMenu(menu) # clothes list menu = KMenu(mainmenu, ghost.ID, "Clothes") menu.setEnabled(False) mainmenu.addSubMenu(menu) from kikka_app import KikkaApp callbackfunc = lambda: KikkaApp.this().exitApp() mainmenu.addMenuItem("Exit", callbackfunc) return mainmenu @staticmethod def createOptionMenu(parent, ghost): optionmenu = KMenu(parent, ghost.ID, "Option") callbackfunc1 = lambda checked: ghost.resetWindowsPosition(True, False) optionmenu.addMenuItem("Reset Shell Position", callbackfunc1) callbackfunc2 = lambda checked, g=ghost: g.setIsLockOnTaskbar(checked) act = optionmenu.addMenuItem("Lock on taskbar", callbackfunc2) act.setCheckable(True) act.setChecked(ghost.getIsLockOnTaskbar()) return optionmenu # ########################################################################### @staticmethod def createTestMenu(parent=None): # test callback function def _test_callback(index=0, title=''): logging.info("MainMenu_callback: click [%d] %s" % (index, title)) def _test_Exit(testmenu): # from kikka_app import KikkaApp callbackfunc = lambda: print("Exit!!") testmenu.addMenuItem("Exit", callbackfunc) def _test_MenuItemState(testmenu): menu = KMenu(testmenu, 0, "MenuItem State") c = 16 for i in range(c): text = str("%s-item%d" % (menu.title(), i)) callbackfunc = lambda checked, a=i, b=text: _test_callback(a, b) act = menu.addMenuItem(text, callbackfunc) if i >= c / 2: act.setDisabled(True) act.setText("%s-disable" % act.text()) if i % 8 >= c / 4: act.setIcon(icon) act.setText("%s-icon" % act.text()) if i % 4 >= c / 8: act.setCheckable(True) act.setText("%s-ckeckable" % act.text()) if i % 2 >= c / 16: act.setChecked(True) act.setText("%s-checked" % act.text()) testmenu.addSubMenu(menu) def _test_Shortcut(testmenu): menu = KMenu(testmenu, 0, "Shortcut") c = 4 for i in range(c): text = str("%s-item" % (str(chr(ord('A') + i)))) callbackfunc = lambda checked, a=i, b=text: _test_callback(a, b) act = menu.addMenuItem(text, callbackfunc) if i == 0: act.setShortcut(QKeySequence("Ctrl+T")) act.setShortcutContext(Qt.ShortcutContext.ApplicationShortcut) act.setShortcutVisibleInContextMenu(True) testmenu.addSubMenu(menu) pass def _test_StatusTip(testmenu): pass def _test_Separator(testmenu): menu = KMenu(testmenu, 0, "Separator") menu.addSeparator() c = 5 for i in range(c): text = str("%s-item%d" % (menu.title(), i)) callbackfunc = lambda checked, a=i, b=text: _test_callback(a, b) menu.addMenuItem(text, callbackfunc) for j in range(i + 2): menu.addSeparator() testmenu.addSubMenu(menu) def _test_MultipleItem(testmenu): menu = KMenu(testmenu, 0, "Multiple item") for i in range(100): text = str("%s-item%d" % (menu.title(), i)) callbackfunc = lambda checked, a=i, b=text: _test_callback(a, b) menu.addMenuItem(text, callbackfunc) testmenu.addSubMenu(menu) def _test_LongTextItem(testmenu): menu = KMenu(testmenu, 0, "Long text item") for i in range(5): text = str("%s-item%d " % (menu.title(), i)) * 20 callbackfunc = lambda checked, a=i, b=text: _test_callback(a, b) menu.addMenuItem(text, callbackfunc) testmenu.addSubMenu(menu) def _test_LargeMenu(testmenu): menu = KMenu(testmenu, 0, "Large menu") for i in range(60): text = str("%s-item%d " % (menu.title(), i)) * 10 callbackfunc = lambda checked, a=i, b=text: _test_callback(a, b) menu.addMenuItem(text, callbackfunc) if i % 5 == 0: menu.addSeparator() testmenu.addSubMenu(menu) def _test_LimitTest(testmenu): menu = KMenu(testmenu, 0, "LimitTest") _test_LargeMenu(menu) _test_MultipleItem(menu) _test_LongTextItem(menu) testmenu.addSubMenu(menu) def _test_Submenu(testmenu): menu = KMenu(testmenu, 0, "Submenu") testmenu.addSubMenu(menu) submenu = KMenu(menu, 0, "submenu1") menu.addSubMenu(submenu) m = submenu for i in range(8): next = KMenu(testmenu, 0, "submenu%d" % (i + 2)) m.addSubMenu(next) m = next submenu = KMenu(menu, 0, "submenu2") menu.addSubMenu(submenu) m = submenu for i in range(8): for j in range(10): text = str("%s-item%d" % (m.title(), j)) callbackfunc = lambda checked, a=j, b=text: _test_callback(a, b) m.addMenuItem(text, callbackfunc) next = KMenu(testmenu, 0, "submenu%d" % (i + 2)) m.addSubMenu(next) m = next submenu = KMenu(testmenu, 0, "SubMenu State") c = 16 for i in range(c): text = str("%s-%d" % (submenu.title(), i)) m = KMenu(submenu, 0, text) act = submenu.addSubMenu(m) callbackfunc = lambda checked, a=i, b=text: _test_callback(a, b) act.triggered.connect(callbackfunc) if i >= c / 2: act.setDisabled(True) act.setText("%s-disable" % act.text()) if i % 8 >= c / 4: act.setIcon(icon) act.setText("%s-icon" % act.text()) if i % 4 >= c / 8: act.setCheckable(True) act.setText("%s-ckeckable" % act.text()) if i % 2 >= c / 16: act.setChecked(True) act.setText("%s-checked" % act.text()) submenu.addSubMenu(m) menu.addSubMenu(submenu) def _test_ImageTest(testmenu): imagetestmenu = KMenu(testmenu, 0, "ImageTest") testmenu.addSubMenu(imagetestmenu) menu = KMenu(imagetestmenu, 0, "MenuImage-normal") for i in range(32): text = " " * 54 menu.addMenuItem(text) imagetestmenu.addSubMenu(menu) menu = KMenu(imagetestmenu, 0, "MenuImage-bit") menu.addMenuItem('') imagetestmenu.addSubMenu(menu) menu = KMenu(imagetestmenu, 0, "MenuImage-small") for i in range(10): text = " " * 30 menu.addMenuItem(text) imagetestmenu.addSubMenu(menu) menu = KMenu(imagetestmenu, 0, "MenuImage-long") for i in range(64): text = " " * 54 menu.addMenuItem(text) imagetestmenu.addSubMenu(menu) menu = KMenu(imagetestmenu, 0, "MenuImage-long2") for i in range(32): text = " " * 30 menu.addMenuItem(text) imagetestmenu.addSubMenu(menu) menu = KMenu(imagetestmenu, 0, "MenuImage-large") for i in range(64): text = " " * 300 menu.addMenuItem(text) imagetestmenu.addSubMenu(menu) menu = KMenu(imagetestmenu, 0, "MenuImage-verylarge") for i in range(100): text = " " * 600 menu.addMenuItem(text) imagetestmenu.addSubMenu(menu) if parent is None: parent = QWidget(f=Qt.WindowType.Dialog) icon = QIcon(r"icon.ico") menu_test = KMenu(parent, 0, "TestMenu") _test_Exit(menu_test) menu_test.addSeparator() _test_MenuItemState(menu_test) _test_Shortcut(menu_test) _test_StatusTip(menu_test) _test_Separator(menu_test) _test_LimitTest(menu_test) _test_Submenu(menu_test) menu_test.addSeparator() _test_ImageTest(menu_test) menu_test.addSeparator() _test_Exit(menu_test) return menu_test @staticmethod def updateTestSurface(menu, ghost, curSurface=-1): if kikka.core.isDebug is False or menu is None: return debugmenu = None for act in menu.actions(): if act.text() == 'Debug': debugmenu = act.menu() break if debugmenu is None: return sufacemenu = None for act in debugmenu.actions(): if act.text() == 'TestSurface': sufacemenu = act.menu() break if sufacemenu is None: return sufacemenu.clear() surfacelist = ghost.getShell().getSurfaceNameList() group = QActionGroup(sufacemenu.parent()) for surfaceID, item in surfacelist.items(): callbackfunc = lambda checked, faceID=surfaceID: ghost.getSoul(0).setSurface(faceID) name = "%3d - %s(%s)" % (surfaceID, item[0], item[1]) act = sufacemenu.addMenuItem(name, callbackfunc, None, group) act.setCheckable(True) if surfaceID == curSurface: act.setChecked(True) pass def getDefaultImage(): return QImage(os.path.join(RESOURCES_PATH, "shell.png")) def getMenuStyle(): shellMenuStyle = ShellMenuStyle() shellMenuStyle.background_image = os.path.join(RESOURCES_PATH, "menu_background.png") shellMenuStyle.foreground_image = os.path.join(RESOURCES_PATH, "menu_foreground.png") shellMenuStyle.sidebar_image = os.path.join(RESOURCES_PATH, "menu_sidebar.png") return MenuStyle(shellMenuStyle) class ShellMenuStyle: def __init__(self): self.hidden = False self.font_family = '' self.font_size = -1 self.background_image = '' self.background_font_color = [-1, -1, -1] self.background_alignment = 'lefttop' self.foreground_image = '' self.foreground_font_color = [-1, -1, -1] self.foreground_alignment = 'lefttop' self.disable_font_color = [-1, -1, -1] self.separator_color = [-1, -1, -1] self.sidebar_image = '' self.sidebar_alignment = 'lefttop' class MenuStyle: def __init__(self, shellMenu): # image if os.path.exists(shellMenu.background_image): self.bg_image = QImage(shellMenu.background_image) else: self.bg_image = getDefaultImage() logging.warning("Menu background image NOT found: %s" % shellMenu.background_image) if os.path.exists(shellMenu.foreground_image): self.fg_image = QImage(shellMenu.foreground_image) else: self.fg_image = getDefaultImage() logging.warning("Menu foreground image NOT found: %s" % shellMenu.foreground_image) if os.path.exists(shellMenu.sidebar_image): self.side_image = QImage(shellMenu.sidebar_image) else: self.side_image = getDefaultImage() logging.warning("Menu sidebar image NOT found: %s" % shellMenu.sidebar_image) # font and color if shellMenu.font_family != '': self.font = QFont(shellMenu.font_family, shellMenu.font_size) else: self.font = None if -1 in shellMenu.background_font_color: self.bg_font_color = None else: self.bg_font_color = QColor(shellMenu.background_font_color[0], shellMenu.background_font_color[1], shellMenu.background_font_color[2]) if -1 in shellMenu.foreground_font_color: self.fg_font_color = None else: self.fg_font_color = QColor(shellMenu.foreground_font_color[0], shellMenu.foreground_font_color[1], shellMenu.foreground_font_color[2]) if -1 in shellMenu.disable_font_color: self.disable_font_color = None else: self.disable_font_color = QColor(shellMenu.disable_font_color[0], shellMenu.disable_font_color[1], shellMenu.disable_font_color[2]) if -1 in shellMenu.separator_color: self.separator_color = None else: self.separator_color = QColor(shellMenu.separator_color[0], shellMenu.separator_color[1], shellMenu.separator_color[2]) # others self.hidden = shellMenu.hidden self.background_alignment = shellMenu.background_alignment self.foreground_alignment = shellMenu.foreground_alignment self.sidebar_alignment = shellMenu.sidebar_alignment pass def getPenColor(self, opt): if opt.menuItemType == QStyleOptionMenuItem.MenuItemType.Separator: color = self.separator_color elif opt.state & QStyle.StateFlag.State_Selected and opt.state & QStyle.StateFlag.State_Enabled: color = self.fg_font_color elif not (opt.state & QStyle.StateFlag.State_Enabled): color = self.disable_font_color else: color = self.bg_font_color if color is None: color = opt.palette.color(QPalette.ColorRole.Text) return color class KMenu(QMenu): def __init__(self, parent, gid, title=''): QMenu.__init__(self, title, parent) self.gid = gid self._parent = parent self._aRect = {} self._bg_image = None self._fg_image = None self._side_image = None self.installEventFilter(self) self.setMouseTracking(True) self.setStyleSheet("QMenu { menu-scrollable: 1; }") self.setSeparatorsCollapsible(False) def addMenuItem(self, text, callbackfunc=None, iconfilepath=None, group=None): if iconfilepath is None: act = QAction(text, self._parent) elif os.path.exists(iconfilepath): act = QAction(QIcon(iconfilepath), text, self._parent) else: logging.info("fail to add menu item") return if callbackfunc is not None: act.triggered.connect(callbackfunc) if group is None: self.addAction(act) else: self.addAction(group.addAction(act)) self.confirmMenuSize(act) return act def addSubMenu(self, menu): act = self.addMenu(menu) self.confirmMenuSize(act, menu.title()) return act def getSubMenu(self, menuName): for i in range(len(self.actions())): act = self.actions()[i] if act.text() == menuName: return act.menu() return None def getAction(self, actionName): for i in range(len(self.actions())): act = self.actions()[i] if act.text() == actionName: return act return None def getActionByData(self, data): for i in range(len(self.actions())): act = self.actions()[i] if act.data() == data: return act return None def checkAction(self, actionName, isChecked): for i in range(len(self.actions())): act = self.actions()[i] if act.text() != actionName: continue act.setChecked(isChecked) pass def confirmMenuSize(self, item, text=''): # s = self.sizeHint() # w, h = kikka.helper.getScreenResolution() # if text == '': # text = item.text() # if KikkaMenu.isDebug and s.height() > h: # logging.warning("the Menu_Height out of Screen_Height, too many menu item when add: %s" % text) # if KikkaMenu.isDebug and s.width() > w: # logging.warning("the Menu_Width out of Screen_Width, too menu item text too long when add: %s" % text) return def setPosition(self, pos): rect = QApplication.instance().primaryScreen().geometry() w = rect.width() h = rect.height() if pos.y() + self.height() > h: pos.setY(h - self.height()) if pos.y() < 0: pos.setY(0) if pos.x() + self.width() > w: pos.setX(w - self.width()) if pos.x() < 0: pos.setX(0) self.move(pos) def updateActionRect(self): """ void QMenuPrivate::updateActionRects(const QRect &screen) const https://cep.xray.aps.anl.gov/software/qt4-x11-4.8.6-browser/da/d61/class_q_menu_private.html#acf93cda3ebe88b1234dc519c5f1b0f5d """ self._aRect = {} topmargin = 0 leftmargin = 0 rightmargin = 0 # qmenu.cpp Line 259: # init max_column_width = 0 dh = self.height() y = 0 style = self.style() opt = QStyleOption() opt.initFrom(self) hmargin = style.pixelMetric(QStyle.PixelMetric.PM_MenuHMargin, opt, self) vmargin = style.pixelMetric(QStyle.PixelMetric.PM_MenuVMargin, opt, self) icone = style.pixelMetric(QStyle.PixelMetric.PM_SmallIconSize, opt, self) fw = style.pixelMetric(QStyle.PixelMetric.PM_MenuPanelWidth, opt, self) deskFw = style.pixelMetric(QStyle.PixelMetric.PM_MenuDesktopFrameWidth, opt, self) tearoffHeight = style.pixelMetric(QStyle.PixelMetric.PM_MenuTearoffHeight, opt, self) if self.isTearOffEnabled() else 0 # for compatibility now - will have to refactor this away tabWidth = 0 maxIconWidth = 0 hasCheckableItems = False # ncols = 1 # sloppyAction = 0 for i in range(len(self.actions())): act = self.actions()[i] if act.isSeparator() or act.isVisible() is False: continue # ..and some members hasCheckableItems |= act.isCheckable() ic = act.icon() if ic.isNull() is False: maxIconWidth = max(maxIconWidth, icone + 4) # qmenu.cpp Line 291: # calculate size qfm = self.fontMetrics() previousWasSeparator = True # this is true to allow removing the leading separators for i in range(len(self.actions())): act = self.actions()[i] if act.isVisible() is False \ or (self.separatorsCollapsible() and previousWasSeparator and act.isSeparator()): # we continue, this action will get an empty QRect self._aRect[i] = QRect() continue previousWasSeparator = act.isSeparator() # let the style modify the above size.. opt = QStyleOptionMenuItem() self.initStyleOption(opt, act) fm = opt.fontMetrics sz = QSize() # sz = self.sizeHint().expandedTo(self.minimumSize()).expandedTo(self.minimumSizeHint()).boundedTo(self.maximumSize()) # calc what I think the size is.. if act.isSeparator(): sz = QSize(2, 2) else: s = act.text() if '\t' in s: t = s.index('\t') act.setText(s[t + 1:]) tabWidth = max(int(tabWidth), qfm.width(s[t + 1:])) else: seq = act.shortcut() if seq.isEmpty() is False: tabWidth = max(int(tabWidth), qfm.boundingRect(seq.toString()).width()) sz.setWidth(fm.boundingRect(QRect(), Qt.TextFlag.TextSingleLine | Qt.TextFlag.TextShowMnemonic, s).width()) sz.setHeight(fm.height()) if not act.icon().isNull(): is_sz = QSize(icone, icone) if is_sz.height() > sz.height(): sz.setHeight(is_sz.height()) sz = style.sizeFromContents(QStyle.ContentsType.CT_MenuItem, opt, sz, self) if sz.isEmpty() is False: max_column_width = max(max_column_width, sz.width()) # wrapping if y + sz.height() + vmargin > dh - deskFw * 2: # ncols += 1 y = vmargin y += sz.height() # update the item self._aRect[i] = QRect(0, 0, sz.width(), sz.height()) pass # exit for max_column_width += tabWidth # finally add in the tab width sfcMargin = style.sizeFromContents(QStyle.ContentsType.CT_Menu, opt, QSize(0, 0), self).width() min_column_width = self.minimumWidth() - (sfcMargin + leftmargin + rightmargin + 2 * (fw + hmargin)) max_column_width = max(min_column_width, max_column_width) # qmenu.cpp Line 259: # calculate position base_y = vmargin + fw + topmargin + tearoffHeight x = hmargin + fw + leftmargin y = base_y for i in range(len(self.actions())): if self._aRect[i].isNull(): continue if y + self._aRect[i].height() > dh - deskFw * 2: x += max_column_width + hmargin y = base_y self._aRect[i].translate(x, y) # move self._aRect[i].setWidth(max_column_width) # uniform width y += self._aRect[i].height() # update menu size s = self.sizeHint() self.resize(s) def drawControl(self, p, opt, arect, icon, menustyle): """ due to overrides the "paintEvent" method, so we must repaint all menu item by self. luckly, we have qt source code to reference. void drawControl (ControlElement element, const QStyleOption *opt, QPainter *p, const QWidget *w=0) const https://cep.xray.aps.anl.gov/software/qt4-x11-4.8.6-browser/df/d91/class_q_style_sheet_style.html#ab92c0e0406eae9a15bc126b67f88c110 Line 3533: element = CE_MenuItem """ style = self.style() p.setPen(menustyle.getPenColor(opt)) # Line 3566: draw icon and checked sign checkable = opt.checkType != QStyleOptionMenuItem.CheckType.NotCheckable checked = opt.checked if checkable else False if opt.icon.isNull() is False: # has custom icon dis = not (opt.state & QStyle.StateFlag.State_Enabled) active = opt.state & QStyle.StateFlag.State_Selected mode = QIcon.Mode.Disabled if dis else QIcon.Mode.Normal if active != 0 and not dis: mode = QIcon.Mode.Active fw = style.pixelMetric(QStyle.PixelMetric.PM_MenuPanelWidth, opt, self) icone = style.pixelMetric(QStyle.PixelMetric.PM_SmallIconSize, opt, self) iconRect = QRectF(arect.x() - fw, arect.y(), self._side_image.width(), arect.height()) if checked: pixmap = icon.pixmap(QSize(icone, icone), mode, QIcon.State.On) else: pixmap = icon.pixmap(QSize(icone, icone), mode) pixw = pixmap.width() pixh = pixmap.height() pmr = QRectF(0, 0, pixw, pixh) pmr.moveCenter(iconRect.center()) if checked: p.drawRect(QRectF(pmr.x() - 1, pmr.y() - 1, pixw + 2, pixh + 2)) p.drawPixmap(pmr.topLeft(), pixmap) elif checkable and checked: # draw default checked sign opt.rect = QRect(0, arect.y(), self._side_image.width(), arect.height()) opt.palette.setColor(QPalette.ColorRole.Text, menustyle.getPenColor(opt)) style.drawPrimitive(QStyle.PrimitiveElement.PE_IndicatorMenuCheckMark, opt, p, self) # Line 3604: draw emnu text font = menustyle.font if font is not None: p.setFont(font) else: p.setFont(opt.font) text_flag = Qt.AlignmentFlag.AlignVCenter | Qt.TextFlag.TextShowMnemonic | Qt.TextFlag.TextDontClip | Qt.TextFlag.TextSingleLine tr = QRect(arect) s = opt.text if '\t' in s: ss = s[s.index('\t') + 1:] fontwidth = opt.fontMetrics.width(ss) tr.moveLeft(opt.rect.right() - fontwidth) tr = QStyle.visualRect(opt.direction, opt.rect, tr) p.drawText(tr, text_flag, ss) tr.moveLeft(self._side_image.width() + arect.x()) tr = QStyle.visualRect(opt.direction, opt.rect, tr) p.drawText(tr, text_flag, s) # Line 3622: draw sub menu arrow if opt.menuItemType == QStyleOptionMenuItem.MenuItemType.SubMenu: arrowW = style.pixelMetric(QStyle.PixelMetric.PM_IndicatorWidth, opt, self) arrowH = style.pixelMetric(QStyle.PixelMetric.PM_IndicatorHeight, opt, self) arrowRect = QRect(0, 0, arrowW, arrowH) arrowRect.moveBottomRight(arect.bottomRight()) arrow = QStyle.PrimitiveElement.PE_IndicatorArrowLeft if opt.direction == Qt.LayoutDirection.RightToLeft else QStyle.PrimitiveElement.PE_IndicatorArrowRight opt.rect = arrowRect opt.palette.setColor(QPalette.ColorRole.ButtonText, menustyle.getPenColor(opt)) style.drawPrimitive(arrow, opt, p, self) pass def paintEvent(self, event): # init menustyle = getMenuStyle() self._bg_image = menustyle.bg_image self._fg_image = menustyle.fg_image self._side_image = menustyle.side_image self.updateActionRect() p = QPainter(self) # draw background p.fillRect(QRect(QPoint(), self.size()), self._side_image.pixelColor(0, 0)) vertical = False y = self.height() while y > 0: yy = y - self._bg_image.height() p.drawImage(0, yy, self._side_image.mirrored(False, vertical)) x = self._side_image.width() while x < self.width(): p.drawImage(x, yy, self._bg_image.mirrored(False, vertical)) x += self._bg_image.width() p.drawImage(x, yy, self._bg_image.mirrored(True, vertical)) x += self._bg_image.width() + 1 y -= self._bg_image.height() vertical = not vertical # draw item actioncount = len(self.actions()) for i in range(actioncount): act = self.actions()[i] arect = QRect(self._aRect[i]) if event.rect().intersects(arect) is False: continue opt = QStyleOptionMenuItem() self.initStyleOption(opt, act) opt.rect = arect if opt.state & QStyle.StateFlag.State_Selected and opt.state & QStyle.StateFlag.State_Enabled: # Selected Item, draw foreground image p.setClipping(True) p.setClipRect(arect.x() + self._side_image.width(), arect.y(), self.width() - self._side_image.width(), arect.height()) p.fillRect(QRect(QPoint(), self.size()), self._fg_image.pixelColor(0, 0)) vertical = False y = self.height() while y > 0: x = self._side_image.width() while x < self.width(): yy = y - self._fg_image.height() p.drawImage(x, yy, self._fg_image.mirrored(False, vertical)) x += self._fg_image.width() p.drawImage(x, yy, self._fg_image.mirrored(True, vertical)) x += self._fg_image.width() + 1 y -= self._fg_image.height() vertical = not vertical p.setClipping(False) if opt.menuItemType == QStyleOptionMenuItem.MenuItemType.Separator: # Separator p.setPen(menustyle.getPenColor(opt)) y = int(arect.y() + arect.height() / 2) p.drawLine(self._side_image.width(), y, arect.width(), y) else: # MenuItem self.drawControl(p, opt, arect, act.icon(), menustyle) pass # exit for def eventFilter(self, obj, event): if obj == self: if event.type() == QEvent.Type.WindowDeactivate: self.Hide() elif event.type() == QEvent.Type.ToolTip: act = self.activeAction() if act != 0 and act.toolTip() != act.text(): QToolTip.showText(event.globalPos(), act.toolTip()) else: QToolTip.hideText() return False