| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877 |
- 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
|