File indexing completed on 2024-04-21 04:01:50
0001 # pylint: disable=too-many-lines 0002 # -*- coding: utf-8 -*- 0003 0004 """ 0005 Copyright (C) 2008-2016 Wolfgang Rohdewald <wolfgang@rohdewald.de> 0006 0007 SPDX-License-Identifier: GPL-2.0 0008 0009 0010 0011 Here we define replacement classes for the case that we have no 0012 python interface to KDE. 0013 0014 """ 0015 0016 0017 import sys 0018 import os 0019 import subprocess 0020 import getpass 0021 import webbrowser 0022 import codecs 0023 import weakref 0024 from collections import defaultdict 0025 0026 # pylint: disable=wrong-import-order 0027 0028 from configparser import ConfigParser, NoSectionError, NoOptionError 0029 0030 # here come the replacements: 0031 0032 # pylint: disable=wildcard-import,unused-wildcard-import 0033 from qt import * 0034 from qtpy import QT5, QT6, PYSIDE2, PYSIDE6, QT_VERSION, API_NAME, PYQT_VERSION 0035 if QT6: 0036 from qtpy.QtCore import QKeyCombination 0037 0038 from mi18n import MLocale, KDETranslator, i18n, i18nc 0039 0040 from common import Internal, isAlive, Debug 0041 from util import popenReadlines 0042 from statesaver import StateSaver 0043 0044 if os.name != 'nt': 0045 import pwd 0046 0047 __all__ = ['KApplication', 'KConfig', 0048 'KMessageBox', 'KConfigSkeleton', 'KDialogButtonBox', 0049 'KConfigDialog', 'KDialog', 0050 'KUser', 'KStandardAction', 0051 'KXmlGuiWindow', 'KGlobal', 'KIcon'] 0052 0053 0054 class KApplication(QApplication): 0055 0056 """stub""" 0057 0058 def __init__(self): 0059 assert not Internal.isServer, 'KApplication is not supported for the server' 0060 QApplication.__init__(self, sys.argv) 0061 0062 # Qt uses sys.argv[0] as application name 0063 # which is used by QStandardPaths - if we start kajongg.py directly, 0064 # the search path would look like /usr/share/kajongg.py/ 0065 0066 self.translators = [] 0067 self.setApplicationName('kajongg') 0068 self.setApplicationVersion(str(Internal.defaultPort)) 0069 0070 self.initQtTranslator() 0071 0072 def installTranslatorFile(self, qmName): 0073 """qmName is a full path to a .qm file""" 0074 if os.path.exists(qmName): 0075 translator = KDETranslator(self) 0076 translator.load(qmName) 0077 self.installTranslator(translator) 0078 self.translators.append(translator) 0079 if Debug.i18n: 0080 Internal.logger.debug('Installed Qt translator from %s', qmName) 0081 0082 0083 def initQtTranslator(self): 0084 """load translators using Qt .qm files""" 0085 for _ in self.translators: 0086 self.removeTranslator(_) 0087 self.translators = [] 0088 _ = KDETranslator(self) 0089 self.translators.append(_) 0090 self.installTranslator(_) 0091 for language in reversed(list(MLocale.extendRegionLanguages(MLocale.currentLanguages()))): 0092 self.installTranslatorFile(os.path.join( 0093 QLibraryInfo.location(QLibraryInfo.TranslationsPath), 'qtbase_{}.qm'.format(language))) 0094 self.installTranslatorFile('/usr/share/locale/{}/LC_MESSAGES/kwidgetsaddons5_qt.qm'.format(language)) 0095 0096 @classmethod 0097 def desktopSize(cls) -> QSize: 0098 """The size of the current screen""" 0099 try: 0100 result = Internal.app.desktop().availableGeometry() 0101 except AttributeError: 0102 result = Internal.mainWindow.screen().availableGeometry() 0103 return result 0104 0105 0106 class CaptionMixin: 0107 0108 """used by KDialog and KXmlGuiWindow""" 0109 0110 def setCaption(self, caption): 0111 """append app name""" 0112 if caption: 0113 if not caption.endswith(i18n('Kajongg')): 0114 caption += ' – {}'.format(i18n('Kajongg')) 0115 else: 0116 caption = i18n('Kajongg') 0117 self.setWindowTitle(caption) 0118 self.setWindowIcon(KIcon('kajongg')) 0119 0120 0121 0122 0123 class Help: 0124 """Interface to the KDE help system""" 0125 0126 @staticmethod 0127 def __getDocUrl(languages): 0128 """return the best match for the online user manual""" 0129 from twisted.web import client 0130 0131 def processResult(unusedResult, fallbacks): 0132 """if status 404, try the next fallback language""" 0133 return Help.__getDocUrl(fallbacks) if factory.status == '404' else url 0134 host = 'docs.kde.org' 0135 path = '?application=kajongg&language={}'.format(languages[0]) 0136 url = 'https://' + host + path 0137 factory = client.HTTPClientFactory(url.encode('ascii')) 0138 factory.protocol = client.HTTPPageGetter 0139 factory.protocol.handleEndHeaders = lambda x: x 0140 Internal.reactor.connectTCP(host, 80, factory) 0141 factory.deferred.addCallback(processResult, languages[1:]) 0142 return factory.deferred 0143 0144 @staticmethod 0145 def start(): 0146 """start the KDE help center for kajongg or go to docs.kde.org""" 0147 try: 0148 subprocess.Popen(['khelpcenter', 'help:/kajongg/index.html']) 0149 except OSError: 0150 def gotUrl(url): 0151 """now we know where the manual is""" 0152 webbrowser.open(url) 0153 languages = Internal.kajonggrc.group( 0154 'Locale').readEntry('Language').split(':') 0155 Help.__getDocUrl(languages).addCallback(gotUrl) 0156 0157 0158 class IconLabel(QLabel): 0159 0160 """for use in messages and about dialog""" 0161 0162 def __init__(self, iconName, dialog): 0163 QLabel.__init__(self) 0164 icon = KIcon(iconName) 0165 option = QStyleOption() 0166 option.initFrom(dialog) 0167 self.setPixmap(icon.pixmap(dialog.style().pixelMetric( 0168 QStyle.PM_MessageBoxIconSize, option, dialog))) 0169 0170 0171 class KMessageBox: 0172 0173 """again only what we need""" 0174 NoExec = 1 0175 AllowLink = 2 0176 Options = int 0177 0178 @staticmethod 0179 def createKMessageBox( 0180 dialog, icon, text, unusedStrlist, unusedAsk, unusedCheckboxReturn, options): 0181 """translated as far as needed from kmessagegox.cpp""" 0182 # pylint: disable=too-many-locals 0183 mainLayout = QVBoxLayout() 0184 0185 hLayout = QHBoxLayout() 0186 hLayout.setContentsMargins(0, 0, 0, 0) 0187 hLayout.setSpacing(-1) 0188 mainLayout.addLayout(hLayout, 5) 0189 0190 iconName = { 0191 QMessageBox.Information: 'dialog-information', 0192 QMessageBox.Warning: 'dialog-warning', 0193 QMessageBox.Question: 'dialog-information'}[icon] 0194 icon = KIcon(iconName) 0195 iconLayout = QVBoxLayout() 0196 iconLayout.addStretch(1) 0197 iconLayout.addWidget(IconLabel(iconName, dialog)) 0198 iconLayout.addStretch(5) 0199 hLayout.addLayout(iconLayout, 0) 0200 0201 messageLabel = QLabel(text) 0202 flags = Qt.TextSelectableByMouse 0203 if options & KMessageBox.AllowLink: 0204 flags |= Qt.LinksAccessibleByMouse 0205 messageLabel.setOpenExternalLinks(True) 0206 messageLabel.setTextInteractionFlags(flags) 0207 0208 desktop = KApplication.desktopSize() 0209 if messageLabel.sizeHint().width() > desktop.width() * 0.5: 0210 messageLabel.setWordWrap(True) 0211 0212 usingScrollArea = desktop.height( 0213 ) // 3 < messageLabel.sizeHint( 0214 ).height( 0215 ) 0216 if usingScrollArea: 0217 scrollArea = QScrollArea(dialog) 0218 scrollArea.setWidget(messageLabel) 0219 scrollArea.setWidgetResizable(True) 0220 scrollArea.setFocusPolicy(Qt.NoFocus) 0221 scrollPal = QPalette(scrollArea.palette()) 0222 scrollArea.viewport().setPalette(scrollPal) 0223 hLayout.addWidget(scrollArea, 5) 0224 else: 0225 hLayout.addWidget(messageLabel, 5) 0226 0227 mainLayout.addWidget(dialog.buttonBox) 0228 dialog.setLayout(mainLayout) 0229 0230 KDialogButtonBox = QDialogButtonBox # pylint: disable=invalid-name 0231 0232 0233 class KDialog(CaptionMixin, QDialog): 0234 0235 """QDialog should be enough for kajongg""" 0236 NoButton = 0 0237 Ok = QDialogButtonBox.Ok # pylint: disable=invalid-name 0238 Cancel = QDialogButtonBox.Cancel 0239 Yes = QDialogButtonBox.Yes 0240 No = QDialogButtonBox.No # pylint: disable=invalid-name 0241 Help = QDialogButtonBox.Help 0242 Apply = QDialogButtonBox.Apply 0243 RestoreDefaults = QDialogButtonBox.RestoreDefaults 0244 Default = QDialogButtonBox.RestoreDefaults 0245 Close = QDialogButtonBox.Close 0246 0247 def __init__(self, parent=None): 0248 QDialog.__init__(self, parent) 0249 self.buttonBox = QDialogButtonBox() 0250 self.buttonBox.accepted.connect(self.accept) 0251 self.buttonBox.rejected.connect(self.reject) 0252 self.__mainWidget = None 0253 0254 def setButtons(self, buttonMask): 0255 """(re)create the buttonbox and put all wanted buttons into it""" 0256 if not buttonMask: 0257 self.buttonBox.clear() 0258 return 0259 self.buttonBox.setStandardButtons(buttonMask) 0260 if KDialog.Ok & buttonMask: 0261 self.buttonBox.button(KDialog.Ok).setText(i18n('&OK')) 0262 if KDialog.Apply & buttonMask: 0263 self.buttonBox.button(KDialog.Apply).setText(i18n('&Apply')) 0264 if KDialog.Cancel & buttonMask: 0265 self.buttonBox.button(KDialog.Cancel).setText(i18n('&Cancel')) 0266 if KDialog.Help & buttonMask: 0267 self.buttonBox.button(KDialog.Help).setText(i18n('&Help')) 0268 if KDialog.RestoreDefaults & buttonMask: 0269 self.buttonBox.button( 0270 KDialog.RestoreDefaults).setText(i18n('&Defaults')) 0271 self.buttonBox.button(KDialog.RestoreDefaults).clicked.connect(self.restoreDefaults) 0272 if KDialog.Help & buttonMask: 0273 self.buttonBox.button(KDialog.Help).clicked.connect(Help.start) 0274 0275 def restoreDefaults(self): 0276 """virtual""" 0277 0278 def setMainWidget(self, widget): 0279 """see KDialog.setMainWidget""" 0280 if self.layout() is None: 0281 QVBoxLayout(self) 0282 self.layout().addWidget(self.buttonBox) 0283 if self.__mainWidget: 0284 self.layout().removeWidget(self.__mainWidget) 0285 self.layout().removeWidget(self.buttonBox) 0286 self.__mainWidget = widget 0287 self.layout().addWidget(widget) 0288 self.layout().addWidget(self.buttonBox) 0289 0290 def button(self, buttonCode): 0291 """return the matching button""" 0292 return self.buttonBox.button(buttonCode) 0293 0294 @staticmethod 0295 def ButtonCode(value): # pylint: disable=invalid-name 0296 """not needed in Python""" 0297 return value 0298 0299 @staticmethod 0300 def spacingHint(): 0301 """stub""" 0302 return QApplication.style().pixelMetric(QStyle.PM_DefaultLayoutSpacing) 0303 0304 @staticmethod 0305 def marginHint(): 0306 """stub""" 0307 return QApplication.style().pixelMetric(QStyle.PM_DefaultChildMargin) 0308 0309 0310 class KUser: 0311 0312 """only the things kajongg needs""" 0313 0314 def __init__(self, uid=None): 0315 self.__uid = uid 0316 0317 def fullName(self): 0318 """stub""" 0319 if os.name == 'nt': 0320 return self.loginName() 0321 return pwd.getpwnam(self.loginName()).pw_gecos.replace(',', '') 0322 0323 @staticmethod 0324 def loginName(): 0325 """stub""" 0326 return getpass.getuser() 0327 0328 0329 class KStandardAction: 0330 0331 """stub""" 0332 @classmethod 0333 def preferences(cls, slot, actionCollection): 0334 """should add config dialog menu entry""" 0335 mainWindow = Internal.mainWindow 0336 separator = QAction(Internal.mainWindow) 0337 separator.setSeparator(True) 0338 mainWindow.actionStatusBar = Action(mainWindow, 'options_show_statusbar', None) 0339 mainWindow.actionStatusBar.setCheckable(True) 0340 mainWindow.actionStatusBar.setEnabled(True) 0341 mainWindow.actionStatusBar.toggled.connect(mainWindow.toggleStatusBar) 0342 mainWindow.actionStatusBar.setText( 0343 i18nc('@action:inmenu', "Show St&atusbar")) 0344 mainWindow.actionToolBar = Action(mainWindow, 'options_show_toolbar', None) 0345 mainWindow.actionToolBar.setCheckable(True) 0346 mainWindow.actionToolBar.setEnabled(True) 0347 mainWindow.actionToolBar.toggled.connect(mainWindow.toggleToolBar) 0348 mainWindow.actionToolBar.setText( 0349 i18nc('@action:inmenu', "Show &Toolbar")) 0350 0351 actionCollection.addAction('', separator) 0352 0353 action = QAction(mainWindow) 0354 action.triggered.connect(mainWindow.configureToolBar) 0355 action.setText(i18n('Configure Tool&bars...')) 0356 action.setIcon(KIcon('configure-toolbars')) # TODO: winprep 0357 action.setIconText(i18n('Configure toolbars')) 0358 separator = QAction(Internal.mainWindow) 0359 separator.setSeparator(True) 0360 actionCollection.addAction('options_configure_toolbars', action) 0361 0362 action = QAction(mainWindow) 0363 action.triggered.connect(slot) 0364 action.setText(i18n('Configure &Kajongg...')) 0365 action.setIcon(KIcon('configure')) 0366 action.setIconText(i18n('Configure')) 0367 actionCollection.addAction('options_configure', action) 0368 0369 0370 class KActionCollection: 0371 0372 """stub""" 0373 0374 def __init__(self, mainWindow): 0375 self.__actions = {} 0376 self.mainWindow = mainWindow 0377 0378 def addAction(self, name, action): 0379 """stub""" 0380 self.__actions[name] = action 0381 for content in self.mainWindow.menus.values(): 0382 if name in content[1]: 0383 content[0].addAction(action) 0384 break 0385 0386 def actions(self): 0387 """the actions in this collection""" 0388 return self.__actions 0389 0390 0391 class MyStatusBarItem: 0392 0393 """one of the four player items""" 0394 0395 def __init__(self, text, idx, stretch=0): 0396 self.idx = idx 0397 self.stretch = stretch 0398 self.label = QLabel() 0399 self.label.setText(text) 0400 self.label.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) 0401 0402 0403 class KStatusBar(QStatusBar): 0404 0405 """stub""" 0406 0407 def __init__(self, *args, **kwargs): 0408 QStatusBar.__init__(self, *args, **kwargs) 0409 self.__items = [] 0410 0411 def hasItem(self, idx): 0412 """stub""" 0413 return len(self.__items) > idx 0414 0415 def changeItem(self, text, idx): 0416 """stub""" 0417 self.__items[idx].label.setText(text) 0418 0419 def insertItem(self, text, idx, stretch): 0420 """stub""" 0421 item = MyStatusBarItem(text, idx, stretch) 0422 self.__items.append(item) 0423 self.insertWidget(item.idx, item.label, stretch=item.stretch) 0424 0425 def removeItem(self, idx): 0426 """stub""" 0427 item = self.__items[idx] 0428 self.removeWidget(item.label) 0429 del self.__items[idx] 0430 0431 def setItemAlignment(self, idx, alignment): 0432 """stub""" 0433 self.__items[idx].label.setAlignment(alignment) 0434 0435 0436 class KXmlGuiWindow(CaptionMixin, QMainWindow): 0437 0438 """stub""" 0439 0440 def __init__(self): 0441 QMainWindow.__init__(self) 0442 self._actions = KActionCollection(self) 0443 self._toolBar = QToolBar(self) 0444 self._toolBar.setObjectName('Toolbar') 0445 self.addToolBar(self.toolBar()) 0446 self.setStatusBar(KStatusBar(self)) 0447 self.statusBar().setObjectName('StatusBar') 0448 self.menus = {} 0449 # the menuItems are added to the main menu by KActionCollection.addAction 0450 # their order is not defined by this here but by the order in which MainWindow 0451 # creates the menu entries. This only defines which action goes into which main menu. 0452 for menu, menuItems in ( 0453 (i18n('&Game'), ('scoreGame', 'play', 'abort', 'quit')), 0454 (i18n('&View'), ('scoreTable', 'explain', 'chat', 'fullscreen')), 0455 (i18n('&Settings'), ('players', 'rulesets', 'angle', 'demoMode', '', 'options_show_statusbar', 0456 'options_show_toolbar', '', 'options_configure_toolbars', 'options_configure')), 0457 (i18n('&Help'), ('help', 'language', 'aboutkajongg'))): 0458 mainMenu = QMenu(menu) 0459 self.menus[menu] = (mainMenu, menuItems) 0460 self.menuBar().addMenu(mainMenu) 0461 self.setCaption('') 0462 self.actionHelp = Action(self, "help", "help-contents", Help.start) 0463 self.actionHelp.setText(i18nc('@action:inmenu', '&Help')) 0464 self.actionLanguage = Action(self, 0465 "language", "preferences-desktop-locale", self.selectLanguage) 0466 self.actionLanguage.setText(i18n('Switch Application Language')) 0467 self.actionAboutKajongg = Action(self, 0468 'aboutkajongg', 'kajongg', self.aboutKajongg) 0469 self.actionAboutKajongg.setText( 0470 i18nc('@action:inmenu', 'About &Kajongg')) 0471 self.toolBar().setMovable(False) 0472 self.toolBar().setFloatable(False) 0473 self.toolBar().setToolButtonStyle(Qt.ToolButtonTextBesideIcon) 0474 0475 def showEvent(self, event): 0476 """now that the MainWindow code has run, we know all actions""" 0477 self.refreshToolBar() 0478 self.toolBar().setVisible(Internal.Preferences.toolBarVisible) 0479 self.actionStatusBar.setChecked(self.statusBar().isVisible()) 0480 self.actionToolBar.setChecked(self.toolBar().isVisible()) 0481 self.actionFullscreen.setChecked(self.windowState() & Qt.WindowFullScreen == Qt.WindowFullScreen) 0482 QMainWindow.showEvent(self, event) 0483 0484 def hideEvent(self, event): 0485 """save status""" 0486 Internal.Preferences.toolBarVisible = self.toolBar( 0487 ).isVisible( 0488 ) # pylint: disable=attribute-defined-outside-init 0489 QMainWindow.hideEvent(self, event) 0490 0491 def toggleStatusBar(self, checked): 0492 """show / hide status bar""" 0493 self.statusBar().setVisible(checked) 0494 0495 def toggleToolBar(self, checked): 0496 """show / hide status bar""" 0497 self.toolBar().setVisible(checked) 0498 0499 def configureToolBar(self): 0500 """configure toolbar""" 0501 dlg = KEditToolBar(self) 0502 dlg.show() 0503 0504 def refreshToolBar(self): 0505 """reload settings for toolbar actions""" 0506 self.toolBar().clear() 0507 for name in Internal.Preferences.toolBarActions.split(','): 0508 self.toolBar().addAction(self.actionCollection().actions()[name]) 0509 0510 def actionCollection(self): 0511 """stub""" 0512 return self._actions 0513 0514 def setupGUI(self): 0515 """stub""" 0516 0517 def toolBar(self): 0518 """stub""" 0519 return self._toolBar 0520 0521 @staticmethod 0522 def selectLanguage(): 0523 """switch the language""" 0524 KSwitchLanguageDialog(Internal.mainWindow).exec_() 0525 0526 @staticmethod 0527 def aboutKajongg(): 0528 """show an about dialog""" 0529 AboutKajonggDialog(Internal.mainWindow).exec_() 0530 0531 def queryClose(self): # pylint: disable=no-self-use 0532 """default""" 0533 return True 0534 0535 def queryExit(self): # pylint: disable=no-self-use 0536 """default""" 0537 return True 0538 0539 def closeEvent(self, event): 0540 """call queryClose/queryExit""" 0541 if self.queryClose() and self.queryExit(): 0542 event.accept() 0543 else: 0544 event.ignore() 0545 0546 0547 class KConfigGroup: 0548 0549 """mimic KConfigGroup as far as we need it""" 0550 0551 def __init__(self, config, groupName): 0552 self.config = weakref.ref(config) 0553 self.groupName = groupName 0554 0555 def __default(self, name, default): 0556 """defer computation of Languages until really needed""" 0557 if default is not None: 0558 return default 0559 if self.groupName == 'Locale' and name == 'Language': 0560 return QLocale().name() 0561 return None 0562 0563 def readEntry(self, name, default=None): 0564 """get an entry from this group.""" 0565 try: 0566 items = self.config().items(self.groupName) 0567 except NoSectionError: 0568 return self.__default(name, default) 0569 items = {x: y for x, y in items if x.startswith(name)} 0570 i18nItems = {x: y for x, y in items.items() if x.startswith(name + '[')} 0571 if i18nItems: 0572 languages = Internal.kajonggrc.group('Locale').readEntry('Language').split(':') 0573 languages = [x.split('_')[0] for x in languages] 0574 for language in languages: 0575 key = '%s[%s]' % (name, language) 0576 if key in i18nItems: 0577 return i18nItems[key] 0578 if name in items: 0579 if self.groupName == 'Locale' and name == 'Language': 0580 languages = [x for x in items[name].split(':') if MLocale.isLanguageInstalled(x)] 0581 if languages: 0582 return ':'.join(languages) 0583 return QLocale().name() 0584 return items[name] 0585 return self.__default(name, default) 0586 0587 def readInteger(self, name, default=None): 0588 """calls readEntry and returns it as an int or raises an Exception.""" 0589 try: 0590 return int(self.readEntry(name, default)) 0591 except Exception as _: 0592 raise Exception('cannot parse group {} in {}: {}={}'.format( 0593 self.groupName, self.config().path, name, 0594 self.readEntry(name, default) 0595 )) from _ 0596 0597 0598 class KGlobal: 0599 0600 """stub""" 0601 0602 @classmethod 0603 def initStatic(cls): 0604 """init class members""" 0605 Internal.kajonggrc = KConfig() 0606 0607 class KConfig(ConfigParser): 0608 0609 """Parse KDE config files. 0610 This mimics KDE KConfig but can also be used like any ConfigParser but 0611 without support for a default section.""" 0612 0613 def __init__(self, path=None): 0614 ConfigParser.__init__(self, delimiters=('=', )) 0615 if path is None: 0616 path = QStandardPaths.writableLocation(QStandardPaths.AppConfigLocation) 0617 path = os.path.join(path, 'kajonggrc') 0618 self.path = os.path.expanduser(path) 0619 if os.path.exists(self.path): 0620 with codecs.open(self.path, 'r', encoding='utf-8') as cfgFile: 0621 self.read_file(cfgFile) 0622 0623 def optionxform(self, optionstr): 0624 """KDE needs upper/lowercase distinction""" 0625 return optionstr 0626 0627 def setValue(self, section, option, value): 0628 """like set but add missing section""" 0629 if section not in self._sections: 0630 self.add_section(section) 0631 self.set(section, option, str(value)) 0632 0633 def writeToFile(self): 0634 """Write an .ini-format representation of the configuration state.""" 0635 with open(self.path, 'w') as filePointer: 0636 self.write(filePointer, space_around_delimiters=False) 0637 0638 def group(self, groupName): 0639 """just like KConfig""" 0640 return KConfigGroup(self, groupName) 0641 0642 def KIcon(name=None): # pylint: disable=invalid-name 0643 """simple wrapper""" 0644 if os.name == 'nt': 0645 return QIcon(os.path.join('share', 'icons', name) if name else None) 0646 return QIcon.fromTheme(name) 0647 0648 0649 class Action(QAction): 0650 0651 """helper for creation QAction""" 0652 0653 def __init__(self, parent, name, icon, slot=None, shortcut=None, actionData=None): 0654 super().__init__(parent) 0655 if icon: 0656 self.setIcon(KIcon(icon)) 0657 if slot: 0658 self.triggered.connect(slot) 0659 if parent: 0660 parent.actionCollection().addAction(name, self) 0661 if shortcut: 0662 if QT6: 0663 if isinstance(shortcut, QKeyCombination): 0664 shortcut = shortcut.key() 0665 self.setShortcut(QKeySequence(shortcut | Qt.CTRL)) 0666 self.setShortcutContext(Qt.ApplicationShortcut) 0667 if actionData is not None: 0668 self.setData(actionData) 0669 0670 0671 class KConfigSkeletonItem: 0672 0673 """one preferences setting used by KOnfigSkeleton""" 0674 0675 def __init__(self, skeleton, key, value, default=None): 0676 assert skeleton 0677 self.skeleton = skeleton 0678 self.group = skeleton.currentGroup 0679 self.key = key 0680 self._value = value 0681 if value is None: 0682 self._value = default 0683 self.default = default 0684 0685 def value(self): 0686 """default getter""" 0687 return self._value 0688 0689 def setValue(self, value): 0690 """default setter""" 0691 if self._value != value: 0692 self._value = value 0693 self.skeleton.configChanged.emit() 0694 0695 def getFromConfig(self): 0696 """if not there, use default""" 0697 try: 0698 self._value = Internal.kajonggrc.get(self.group, self.key) 0699 except (NoSectionError, NoOptionError): 0700 self._value = self.default 0701 0702 0703 class ItemBool(KConfigSkeletonItem): 0704 0705 """boolean preferences setting used by KOnfigSkeleton""" 0706 0707 def __init__(self, skeleton, key, value, default=None): 0708 KConfigSkeletonItem.__init__(self, skeleton, key, value, default) 0709 0710 def getFromConfig(self): 0711 """if not there, use default""" 0712 try: 0713 self._value = Internal.kajonggrc.getboolean(self.group, self.key) 0714 except (NoSectionError, NoOptionError): 0715 self._value = self.default 0716 0717 0718 class ItemString(KConfigSkeletonItem): 0719 0720 """string preferences setting used by KOnfigSkeleton""" 0721 0722 def __init__(self, skeleton, key, value, default=None): 0723 if value == '': 0724 value = default 0725 KConfigSkeletonItem.__init__(self, skeleton, key, value, default) 0726 0727 0728 class ItemInt(KConfigSkeletonItem): 0729 0730 """integer preferences setting used by KOnfigSkeleton""" 0731 0732 def __init__(self, skeleton, key, value, default=0): 0733 KConfigSkeletonItem.__init__(self, skeleton, key, value, default) 0734 self.minValue = -99999 0735 self.maxValue = 99999999 0736 0737 def getFromConfig(self): 0738 """if not there, use default""" 0739 try: 0740 self._value = Internal.kajonggrc.getint(self.group, self.key) 0741 except (NoSectionError, NoOptionError): 0742 self._value = self.default 0743 0744 def setMinValue(self, value): 0745 """minimum value for this setting""" 0746 self.minValue = value 0747 0748 def setMaxValue(self, value): 0749 """maximum value for this setting""" 0750 self.maxValue = value 0751 0752 0753 class KConfigSkeleton(QObject): 0754 0755 """handles preferences settings""" 0756 configChanged = Signal() 0757 0758 def __init__(self): 0759 QObject.__init__(self) 0760 self.currentGroup = None 0761 self.items = [] 0762 self.addBool('MainWindow', 'toolBarVisible', True) 0763 self.addString( 0764 'MainWindow', 0765 'toolBarActions', 0766 'quit,play,scoreTable,explain,players,options_configure') 0767 0768 def addBool(self, group, name, default=None): 0769 """to be overridden""" 0770 0771 def addString(self, group, name, default=None): 0772 """to be overridden""" 0773 0774 def readConfig(self): 0775 """init already read config""" 0776 for item in self.items: 0777 item.getFromConfig() 0778 0779 def writeConfig(self): 0780 """to the same file name""" 0781 for item in self.items: 0782 Internal.kajonggrc.setValue(item.group, item.key, item.value()) 0783 Internal.kajonggrc.writeToFile() 0784 0785 def as_dict(self): 0786 """a dict of dicts""" 0787 result = defaultdict(dict) 0788 for item in self.items: 0789 result[item.group][item.key] = item.value() 0790 return result 0791 0792 def setCurrentGroup(self, group): 0793 """to be used by following add* calls""" 0794 self.currentGroup = group 0795 0796 def addItem(self, key, value, default=None): 0797 """add a string preference""" 0798 if isinstance(value, bool): 0799 cls = ItemBool 0800 elif isinstance(value, int): 0801 cls = ItemInt 0802 elif isinstance(value, str): 0803 cls = ItemString 0804 else: 0805 raise Exception('addiItem accepts only bool, int, str but not {}/{}'.format(type(value), value)) 0806 result = cls(self, key, value, default) 0807 result.getFromConfig() 0808 self.items.append(result) 0809 return result 0810 0811 0812 class KSwitchLanguageDialog(KDialog): 0813 """select application language""" 0814 0815 def __init__(self, parent): 0816 super().__init__(parent) 0817 self.languageRows = dict() 0818 self.languageButtons = list() 0819 self.setCaption(i18n('Switch Application Language')) 0820 self.widget = QWidget() 0821 topLayout = QVBoxLayout() 0822 self.widget.setLayout(topLayout) 0823 0824 topLayout.addWidget(QLabel(i18n("Please choose the language which should be used for this application:"))) 0825 0826 languageHorizontalLayout = QHBoxLayout() 0827 topLayout.addLayout(languageHorizontalLayout) 0828 0829 self.languagesLayout = QGridLayout() 0830 languageHorizontalLayout.addLayout(self.languagesLayout) 0831 languageHorizontalLayout.addStretch() 0832 0833 defined = Internal.kajonggrc.group( 0834 'Locale').readEntry('Language').split(':') 0835 if not defined: 0836 defined = [QLocale.name()] 0837 for idx, _ in enumerate(defined): 0838 self.addLanguageButton(_, isPrimaryLanguage=idx == 0) 0839 0840 addButtonHorizontalLayout = QHBoxLayout() 0841 topLayout.addLayout(addButtonHorizontalLayout) 0842 0843 addLangButton = QPushButton(i18n("Add Fallback Language"), self) 0844 addLangButton.clicked.connect(self.slotAddLanguageButton) 0845 addButtonHorizontalLayout.addWidget(addLangButton) 0846 addButtonHorizontalLayout.addStretch() 0847 0848 topLayout.addStretch(10) 0849 0850 self.setButtons(KDialog.Ok | KDialog.Cancel | KDialog.RestoreDefaults) 0851 self.setMainWidget(self.widget) 0852 0853 def addLanguageButton(self, languageCode, isPrimaryLanguage): 0854 """add button for language""" 0855 labelText = i18n("Primary language:") if isPrimaryLanguage else i18n("Fallback language:") 0856 languageButton = KLanguageButton('', self.widget) 0857 languageButton.current = languageCode 0858 0859 removeButton = None 0860 if not isPrimaryLanguage: 0861 removeButton = QPushButton(i18n("Remove")) 0862 removeButton.clicked.connect(self.removeButtonClicked) 0863 0864 languageButton.setToolTip( 0865 i18n("This is the main application language which will be used first, before any other languages.") 0866 if isPrimaryLanguage else 0867 i18n("This is the language which will be used if any previous " 0868 "languages do not contain a proper translation.")) 0869 0870 numRows = self.languagesLayout.rowCount() 0871 0872 languageLabel = QLabel(labelText) 0873 self.languagesLayout.addWidget(languageLabel, numRows + 1, 1, Qt.AlignLeft) 0874 self.languagesLayout.addWidget(languageButton.button, numRows + 1, 2, Qt.AlignLeft) 0875 0876 if not isPrimaryLanguage: 0877 self.languagesLayout.addWidget(removeButton, numRows + 1, 3, Qt.AlignLeft) 0878 removeButton.show() 0879 self.languageRows[removeButton] = tuple([languageLabel, languageButton]) 0880 0881 self.languageRows[languageButton] = tuple([languageLabel, languageButton]) 0882 self.languageButtons.append(languageButton) 0883 0884 def accept(self): 0885 """OK""" 0886 newValue = ':'.join(x.current for x in self.languageButtons) 0887 Internal.kajonggrc.setValue('Locale', 'Language', newValue) 0888 super().accept() 0889 0890 def slotAddLanguageButton(self): 0891 """adding a new button with en_US as it should always be present""" 0892 self.addLanguageButton('en_US', len(self.languageButtons) == 0) 0893 0894 def restoreDefaults(self): 0895 """reset values to default""" 0896 for _ in self.languageRows: 0897 if isinstance(_, KLanguageButton): 0898 self.removeLanguage(_) 0899 for removeButton in self.languageRows: 0900 if isAlive(removeButton): 0901 removeButton.deleteLater() 0902 self.languageRows = dict() 0903 self.addLanguageButton(QLocale().name(), True) 0904 0905 def removeButtonClicked(self): 0906 """remove this language""" 0907 self.removeLanguage(self.sender()) 0908 0909 def removeLanguage(self, button): 0910 """remove this language""" 0911 label, languageButton = self.languageRows[button] 0912 label.deleteLater() 0913 languageButton.deleteLater() 0914 button.deleteLater() 0915 del self.languageRows[languageButton] 0916 self.languageButtons.remove(languageButton) 0917 0918 0919 class KLanguageButton(QWidget): 0920 """A language button for KSwitchLanguageDialog""" 0921 0922 def __init__(self, txt, parent=None): 0923 super().__init__(parent) 0924 self.button = QPushButton(txt) 0925 self.popup = QMenu() 0926 self.button.setMenu(self.popup) 0927 self.setText(txt) 0928 self.__currentItem = None 0929 for _ in MLocale.availableLanguages_().split(':'): 0930 self.addLanguage(_) 0931 self.popup.triggered.connect(self.slotTriggered) 0932 self.popup.show() 0933 self.button.show() 0934 self.show() 0935 0936 def deleteLater(self): 0937 """self and children""" 0938 self.button.deleteLater() 0939 self.popup.deleteLater() 0940 QWidget.deleteLater(self) 0941 0942 def setText(self, txt): 0943 """proxy: sets the button text""" 0944 self.button.setText(txt) 0945 0946 0947 def addLanguage(self, languageCode): 0948 """add language to popup""" 0949 text = languageCode 0950 locale = QLocale(languageCode) 0951 if locale != QLocale.c(): 0952 text = locale.nativeLanguageName() 0953 0954 action = QAction(QIcon(), text, self) 0955 action.setData(languageCode) 0956 self.popup.addAction(action) 0957 0958 @property 0959 def current(self): 0960 """current languageCode""" 0961 return self.__currentItem 0962 0963 @current.setter 0964 def current(self, languageCode): 0965 """point to languageCode""" 0966 action = ( 0967 self.findAction(languageCode) 0968 or self.findAction(languageCode.split('_')[0]) 0969 or self.popup.actions()[0]) 0970 self.__currentItem = action.data() 0971 self.button.setText(action.text()) 0972 0973 def slotTriggered(self, action): 0974 """another language has been selected from the popup""" 0975 self.current = action.data() 0976 0977 def findAction(self, data): 0978 """find action by name""" 0979 for action in self.popup.actions(): 0980 if action.data() == data: 0981 return action 0982 return None 0983 0984 0985 class AboutKajonggDialog(KDialog): 0986 """about kajongg dialog""" 0987 0988 def __init__(self, parent): 0989 # pylint: disable=too-many-locals, too-many-statements 0990 from twisted import __version__ 0991 0992 KDialog.__init__(self, parent) 0993 self.setCaption(i18n('About Kajongg')) 0994 self.setButtons(KDialog.Close) 0995 vLayout = QVBoxLayout() 0996 hLayout1 = QHBoxLayout() 0997 hLayout1.addWidget(IconLabel('kajongg', self)) 0998 h1vLayout = QVBoxLayout() 0999 h1vLayout.addWidget(QLabel('Kajongg')) 1000 try: 1001 from appversion import VERSION 1002 except ImportError: 1003 VERSION = "Unknown" 1004 1005 underVersions = ['Qt' + QT_VERSION +' API=' + API_NAME] 1006 if PYQT_VERSION: 1007 from sip import SIP_VERSION_STR 1008 underVersions.append('sip ' + SIP_VERSION_STR) 1009 if PYSIDE2: 1010 import PySide2 # pylint: disable=import-error 1011 underVersions.append('PySide2 ' + PySide2.__version__) 1012 if PYSIDE6: 1013 import PySide6 # pylint: disable=import-error 1014 underVersions.append('PySide6 ' + PySide6.__version__) 1015 1016 1017 h1vLayout.addWidget(QLabel(i18n('Version: %1', VERSION))) 1018 h1vLayout.addWidget(QLabel(i18n('Protocol version %1', Internal.defaultPort))) 1019 authors = (( 1020 "Wolfgang Rohdewald", 1021 i18n("Original author"), 1022 "wolfgang@rohdewald.de"), ) 1023 try: 1024 versions = popenReadlines('kf5-config -v') 1025 versionsDict = dict(x.split(': ') for x in versions if ':' in x) 1026 underVersions.append('KDE Frameworks %s' % versionsDict['KDE Frameworks']) 1027 except OSError: 1028 underVersions.append(i18n('KDE Frameworks (not installed or not usable)')) 1029 underVersions.append('Twisted %s' % __version__) 1030 underVersions.append( 1031 'Python {}.{}.{} {}'.format(*sys.version_info[:5])) 1032 h1vLayout.addWidget( 1033 QLabel( 1034 i18nc('kajongg', 1035 'Using versions %1', 1036 ', '.join( 1037 underVersions)))) 1038 hLayout1.addLayout(h1vLayout) 1039 spacerItem = QSpacerItem( 1040 20, 1041 20, 1042 QSizePolicy.Expanding, 1043 QSizePolicy.Expanding) 1044 hLayout1.addItem(spacerItem) 1045 vLayout.addLayout(hLayout1) 1046 tabWidget = QTabWidget() 1047 1048 aboutWidget = QWidget() 1049 aboutLayout = QVBoxLayout() 1050 aboutLabel = QLabel() 1051 aboutLabel.setWordWrap(True) 1052 aboutLabel.setOpenExternalLinks(True) 1053 aboutLabel.setText( 1054 '<br /><br />'.join([ 1055 i18n("Mah Jongg - the ancient Chinese board game for 4 players"), 1056 i18n("This is the classical Mah Jongg for four players. " 1057 "If you are looking for Mah Jongg solitaire please " 1058 "use the application kmahjongg."), 1059 "(C) 2008-2017 Wolfgang Rohdewald", 1060 '<a href="{link}">{link}</a>'.format( 1061 link='https://apps.kde.org/kajongg')])) 1062 licenseLabel = QLabel() 1063 licenseLabel.setText( 1064 '<a href="file://{link}">GNU General Public License Version 2</a>'.format( 1065 link=self.licenseFile())) 1066 licenseLabel.linkActivated.connect(self.showLicense) 1067 aboutLayout.addWidget(aboutLabel) 1068 aboutLayout.addWidget(licenseLabel) 1069 aboutWidget.setLayout(aboutLayout) 1070 tabWidget.addTab(aboutWidget, '&About') 1071 1072 authorWidget = QWidget() 1073 authorLayout = QVBoxLayout() 1074 bugsLabel = QLabel( 1075 i18n('Please use <a href="https://bugs.kde.org">https://bugs.kde.org</a> to report bugs.')) 1076 bugsLabel.setContentsMargins(0, 2, 0, 4) 1077 bugsLabel.setOpenExternalLinks(True) 1078 authorLayout.addWidget(bugsLabel) 1079 1080 titleLabel = QLabel(i18n('Authors:')) 1081 authorLayout.addWidget(titleLabel) 1082 1083 for name, description, mail in authors: 1084 label = QLabel('{name} <a href="mailto:{mail}">{mail}</a>: {description}'.format( 1085 name=name, mail=mail, description=description)) 1086 label.setOpenExternalLinks(True) 1087 authorLayout.addWidget(label) 1088 spacerItem = QSpacerItem( 1089 20, 1090 20, 1091 QSizePolicy.Expanding, 1092 QSizePolicy.Expanding) 1093 authorLayout.addItem(spacerItem) 1094 authorWidget.setLayout(authorLayout) 1095 tabWidget.addTab(authorWidget, 'A&uthor') 1096 vLayout.addWidget(tabWidget) 1097 1098 vLayout.addWidget(self.buttonBox) 1099 self.setLayout(vLayout) 1100 self.buttonBox.setFocus() 1101 1102 @staticmethod 1103 def licenseFile(): 1104 """which may currently only be 1: GPL_V2""" 1105 prefix = QLibraryInfo.location(QLibraryInfo.PrefixPath) 1106 for path in ('COPYING', '../COPYING', 1107 '%s/share/kf5/licenses/GPL_V2' % prefix): 1108 path = os.path.abspath(path) 1109 if os.path.exists(path): 1110 return path 1111 return None 1112 1113 @classmethod 1114 def showLicense(cls): 1115 """as the name says""" 1116 LicenseDialog(Internal.mainWindow, cls.licenseFile()).exec_() 1117 1118 1119 class LicenseDialog(KDialog): 1120 1121 """see kaboutapplicationdialog.cpp""" 1122 1123 def __init__(self, parent, licenseFile): 1124 KDialog.__init__(self, parent) 1125 self.setAttribute(Qt.WA_DeleteOnClose) 1126 self.setCaption(i18n("License Agreement")) 1127 self.setButtons(KDialog.Close) 1128 self.buttonBox.setFocus() 1129 licenseText = open(licenseFile, 'r').read() 1130 self.licenseBrowser = QTextBrowser() 1131 self.licenseBrowser.setLineWrapMode(QTextEdit.NoWrap) 1132 self.licenseBrowser.setText(licenseText) 1133 1134 vLayout = QVBoxLayout() 1135 vLayout.addWidget(self.licenseBrowser) 1136 vLayout.addWidget(self.buttonBox) 1137 self.setLayout(vLayout) 1138 1139 def sizeHint(self): 1140 """try to set up the dialog such that the full width of the 1141 document is visible without horizontal scroll-bars being required""" 1142 idealWidth = self.licenseBrowser.document().idealWidth() + (2 * self.marginHint()) \ 1143 + self.licenseBrowser.verticalScrollBar().width() * 2 + 1 1144 # try to allow enough height for a reasonable number of lines to be 1145 # shown 1146 metrics = QFontMetrics(self.licenseBrowser.font()) 1147 idealHeight = metrics.height() * 30 1148 return KDialog.sizeHint(self).expandedTo(QSize(idealWidth, idealHeight)) 1149 1150 1151 class KConfigDialog(KDialog): 1152 1153 """for the game preferences""" 1154 dialog = None 1155 getFunc = { 1156 'QCheckBox': 'isChecked', 1157 'QSlider': 'value', 1158 'QLineEdit': 'text'} 1159 setFunc = { 1160 'QCheckBox': 'setChecked', 1161 'QSlider': 'setValue', 1162 'QLineEdit': 'setText'} 1163 1164 def __init__(self, parent, name, preferences): 1165 KDialog.__init__(self, parent) 1166 self.setCaption(i18n('Configure')) 1167 self.name = name 1168 self.preferences = preferences 1169 self.orgPref = None 1170 self.configWidgets = {} 1171 self.iconList = QListWidget() 1172 self.iconList.setViewMode(QListWidget.IconMode) 1173 self.iconList.setFlow(QListWidget.TopToBottom) 1174 self.iconList.setUniformItemSizes(True) 1175 self.iconList.itemClicked.connect(self.iconClicked) 1176 self.iconList.currentItemChanged.connect(self.iconClicked) 1177 self.tabSpace = QStackedWidget() 1178 self.setButtons(KDialog.Help | KDialog.Ok | 1179 KDialog.Apply | KDialog.Cancel | KDialog.RestoreDefaults) 1180 self.buttonBox.button( 1181 KDialog.Apply).clicked.connect( 1182 self.applySettings) 1183 cmdLayout = QHBoxLayout() 1184 cmdLayout.addWidget(self.buttonBox) 1185 self.contentLayout = QHBoxLayout() 1186 self.contentLayout.addWidget(self.iconList) 1187 self.contentLayout.addWidget(self.tabSpace) 1188 layout = QVBoxLayout() 1189 layout.addLayout(self.contentLayout) 1190 layout.addLayout(cmdLayout) 1191 self.setLayout(layout) 1192 1193 @classmethod 1194 def showDialog(cls, settings): 1195 """constructor""" 1196 assert settings == 'settings' 1197 if cls.dialog: 1198 cls.dialog.updateWidgets() 1199 cls.dialog.updateButtons() 1200 cls.dialog.show() 1201 return cls.dialog 1202 return None 1203 1204 def showEvent(self, unusedEvent): 1205 """if the settings dialog shows, remember current values 1206 and show them in the widgets""" 1207 self.orgPref = self.preferences.as_dict() 1208 self.updateWidgets() 1209 1210 def iconClicked(self, item): 1211 """show the wanted config tab""" 1212 self.setCurrentPage( 1213 self.pages[self.iconList.indexFromItem(item).row()]) 1214 1215 @classmethod 1216 def allChildren(cls, widget): 1217 """recursively find all widgets holding settings: Their object name 1218 starts with kcfg_""" 1219 result = [] 1220 for child in widget.children(): 1221 if child.objectName().startswith('kcfg_'): 1222 result.append(child) 1223 else: 1224 result.extend(cls.allChildren(child)) 1225 return result 1226 1227 def addPage(self, configTab, name, iconName): 1228 """add a page to the config dialog""" 1229 item = QListWidgetItem(KIcon(iconName), name) 1230 item.setTextAlignment(Qt.AlignHCenter) 1231 font = item.font() 1232 font.setBold(True) 1233 item.setFont(font) 1234 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) 1235 self.iconList.addItem(item) 1236 self.tabSpace.addWidget(configTab) 1237 self.iconList.setIconSize(QSize(80, 80)) 1238 icons = [self.iconList.item(x) for x in range(self.iconList.count())] 1239 neededIconWidth = max(self.iconList.visualItemRect(x).width() 1240 for x in icons) 1241 margins = self.iconList.contentsMargins() 1242 neededIconWidth += margins.left() + margins.right() 1243 self.iconList.setFixedWidth(neededIconWidth) 1244 self.iconList.setMinimumHeight(120 * self.iconList.count()) 1245 for child in self.allChildren(self): 1246 self.configWidgets[ 1247 child.objectName().replace('kcfg_', '')] = child 1248 if isinstance(child, QCheckBox): 1249 child.stateChanged.connect(self.updateButtons) 1250 elif isinstance(child, QSlider): 1251 child.valueChanged.connect(self.updateButtons) 1252 elif isinstance(child, QLineEdit): 1253 child.textChanged.connect(self.updateButtons) 1254 self.updateButtons() 1255 return configTab 1256 1257 def applySettings(self): 1258 """Apply pressed""" 1259 if self.updateButtons(): 1260 self.updateSettings() 1261 self.updateButtons() 1262 1263 def accept(self): 1264 """OK pressed""" 1265 if self.updateButtons(): 1266 self.updateSettings() 1267 KDialog.accept(self) 1268 1269 def updateButtons(self): 1270 """Updates the Apply and Default buttons. Returns True if there was a changed setting""" 1271 changed = False 1272 for name, widget in self.configWidgets.items(): 1273 oldValue = self.preferences[name] 1274 newValue = getattr( 1275 widget, 1276 self.getFunc[widget.__class__.__name__])() 1277 if oldValue != newValue: 1278 changed = True 1279 break 1280 self.buttonBox.button(KDialog.Apply).setEnabled(changed) 1281 return changed 1282 1283 def updateSettings(self): 1284 """Update the settings from the dialog""" 1285 for name, widget in self.configWidgets.items(): 1286 self.preferences[name] = getattr( 1287 widget, 1288 self.getFunc[widget.__class__.__name__])() 1289 self.preferences.writeConfig() 1290 1291 def updateWidgets(self): 1292 """Update the dialog based on the settings""" 1293 self.preferences.readConfig() 1294 for name, widget in self.configWidgets.items(): 1295 getattr(widget, self.setFunc[widget.__class__.__name__])( 1296 getattr(self.preferences, name)) 1297 1298 def setCurrentPage(self, page): 1299 """show wanted page and select its icon""" 1300 self.tabSpace.setCurrentWidget(page) 1301 for idx in range(self.tabSpace.count()): 1302 self.iconList.item(idx).setSelected( 1303 idx == self.tabSpace.currentIndex()) 1304 1305 1306 class KSeparator(QFrame): 1307 1308 """used for toolbar editor""" 1309 1310 def __init__(self, parent=None): 1311 QFrame.__init__(self, parent) 1312 self.setLineWidth(1) 1313 self.setMidLineWidth(0) 1314 self.setFrameShape(QFrame.HLine) 1315 self.setFrameShadow(QFrame.Sunken) 1316 self.setMinimumSize(0, 2) 1317 1318 1319 class ToolBarItem(QListWidgetItem): 1320 1321 """a toolbar item""" 1322 emptyIcon = None 1323 1324 def __init__(self, action, parent): 1325 self.action = action 1326 self.parent = parent 1327 QListWidgetItem.__init__(self, self.__icon(), self.__text(), parent) 1328 # drop between items, not onto items 1329 self.setFlags( 1330 (self.flags() | Qt.ItemIsDragEnabled) & ~Qt.ItemIsDropEnabled) 1331 1332 def __icon(self): 1333 """the action icon, default is an empty icon""" 1334 result = self.action.icon() 1335 if result.isNull(): 1336 if not self.emptyIcon: 1337 iconSize = self.parent.style().pixelMetric( 1338 QStyle.PM_SmallIconSize) 1339 self.emptyIcon = QPixmap(iconSize, iconSize) 1340 self.emptyIcon.fill(Qt.transparent) 1341 self.emptyIcon = QIcon(self.emptyIcon) 1342 result = self.emptyIcon 1343 return result 1344 1345 def __text(self): 1346 """the action text""" 1347 return self.action.text().replace('&', '') 1348 1349 1350 class ToolBarList(QListWidget): 1351 1352 """QListWidget without internal moves""" 1353 1354 def __init__(self, parent): 1355 QListWidget.__init__(self, parent) 1356 self.setDragDropMode(QAbstractItemView.DragDrop) # no internal moves 1357 1358 1359 class KEditToolBar(KDialog): 1360 1361 """stub""" 1362 1363 def __init__(self, parent=None): 1364 # pylint: disable=too-many-statements 1365 KDialog.__init__(self, parent) 1366 self.setCaption(i18n('Configure Toolbars')) 1367 StateSaver(self) 1368 self.inactiveLabel = QLabel(i18n("A&vailable actions:"), self) 1369 self.inactiveList = ToolBarList(self) 1370 self.inactiveList.setDragEnabled(True) 1371 self.inactiveList.setMinimumSize(180, 250) 1372 self.inactiveList.setDropIndicatorShown(False) 1373 self.inactiveLabel.setBuddy(self.inactiveList) 1374 self.inactiveList.itemSelectionChanged.connect( 1375 self.inactiveSelectionChanged) 1376 self.inactiveList.itemDoubleClicked.connect(self.insertButton) 1377 self.inactiveList.setSortingEnabled(True) 1378 1379 self.activeLabel = QLabel(i18n('Curr&ent actions:'), self) 1380 self.activeList = ToolBarList(self) 1381 self.activeList.setDragEnabled(True) 1382 self.activeLabel.setBuddy(self.activeList) 1383 self.activeList.itemSelectionChanged.connect( 1384 self.activeSelectionChanged) 1385 self.activeList.itemDoubleClicked.connect(self.removeButton) 1386 1387 self.upAction = QToolButton(self) 1388 self.upAction.setIcon(KIcon('go-up')) 1389 self.upAction.setEnabled(False) 1390 self.upAction.setAutoRepeat(True) 1391 self.upAction.clicked.connect(self.upButton) 1392 self.insertAction = QToolButton(self) 1393 self.insertAction.setIcon( 1394 KIcon('go-next' if QApplication.isRightToLeft else 'go-previous')) 1395 self.insertAction.setEnabled(False) 1396 self.insertAction.clicked.connect(self.insertButton) 1397 self.removeAction = QToolButton(self) 1398 self.removeAction.setIcon( 1399 KIcon('go-previous' if QApplication.isRightToLeft else 'go-next')) 1400 self.removeAction.setEnabled(False) 1401 self.removeAction.clicked.connect(self.removeButton) 1402 self.downAction = QToolButton(self) 1403 self.downAction.setIcon(KIcon('go-down')) 1404 self.downAction.setEnabled(False) 1405 self.downAction.setAutoRepeat(True) 1406 self.downAction.clicked.connect(self.downButton) 1407 1408 top_layout = QVBoxLayout(self) 1409 top_layout.setContentsMargins(0, 0, 0, 0) 1410 list_layout = QHBoxLayout() 1411 1412 inactive_layout = QVBoxLayout() 1413 active_layout = QVBoxLayout() 1414 1415 button_layout = QGridLayout() 1416 1417 button_layout.setSpacing(0) 1418 button_layout.setRowStretch(0, 10) 1419 button_layout.addWidget(self.upAction, 1, 1) 1420 button_layout.addWidget(self.removeAction, 2, 0) 1421 button_layout.addWidget(self.insertAction, 2, 2) 1422 button_layout.addWidget(self.downAction, 3, 1) 1423 button_layout.setRowStretch(4, 10) 1424 1425 inactive_layout.addWidget(self.inactiveLabel) 1426 inactive_layout.addWidget(self.inactiveList, 1) 1427 active_layout.addWidget(self.activeLabel) 1428 active_layout.addWidget(self.activeList, 1) 1429 1430 list_layout.addLayout(inactive_layout) 1431 list_layout.addLayout(button_layout) 1432 list_layout.addLayout(active_layout) 1433 1434 top_layout.addLayout(list_layout, 10) 1435 top_layout.addWidget(KSeparator(self)) 1436 self.loadActions() 1437 1438 self.buttonBox = QDialogButtonBox() 1439 top_layout.addWidget(self.buttonBox) 1440 self.setButtons(KDialog.Ok | KDialog.Cancel) 1441 self.buttonBox.accepted.connect(self.accept) 1442 self.buttonBox.rejected.connect(self.reject) 1443 1444 def accept(self): 1445 """save and close""" 1446 self.saveActions() 1447 self.hide() 1448 1449 def reject(self): 1450 """do not save and close""" 1451 self.hide() 1452 1453 def inactiveSelectionChanged(self): 1454 """update buttons""" 1455 if self.inactiveList.selectedItems(): 1456 self.insertAction.setEnabled(True) 1457 else: 1458 self.insertAction.setEnabled(False) 1459 1460 def activeSelectionChanged(self): 1461 """update buttons""" 1462 row = self.activeList.currentRow() 1463 toolItem = None 1464 if self.activeList.selectedItems(): 1465 toolItem = self.activeList.selectedItems()[0] 1466 self.removeAction.setEnabled(bool(toolItem)) 1467 if toolItem: 1468 self.upAction.setEnabled(bool(row)) 1469 self.downAction.setEnabled(row < len(self.activeList) - 1) 1470 else: 1471 self.upAction.setEnabled(False) 1472 self.downAction.setEnabled(False) 1473 1474 def insertButton(self): 1475 """activate an action""" 1476 self.__moveItem(toActive=True) 1477 1478 def removeButton(self): 1479 """deactivate an action""" 1480 self.__moveItem(toActive=False) 1481 1482 def __moveItem(self, toActive): 1483 """move item between the two lists""" 1484 if toActive: 1485 fromList = self.inactiveList 1486 toList = self.activeList 1487 else: 1488 fromList = self.activeList 1489 toList = self.inactiveList 1490 item = fromList.takeItem(fromList.currentRow()) 1491 ToolBarItem(item.action, toList) 1492 1493 def upButton(self): 1494 """move action up""" 1495 self.__moveUpDown(moveUp=True) 1496 1497 def downButton(self): 1498 """move action down""" 1499 self.__moveUpDown(moveUp=False) 1500 1501 def __moveUpDown(self, moveUp): 1502 """change place of action in list""" 1503 active = self.activeList 1504 row = active.currentRow() 1505 item = active.takeItem(row) 1506 offset = -1 if moveUp else 1 1507 newRow = row + offset 1508 active.insertItem(newRow, item) 1509 active.setCurrentRow(newRow) 1510 1511 def loadActions(self): 1512 """load active actions from Preferences""" 1513 for name, action in Internal.mainWindow.actionCollection().actions().items(): 1514 if action.text(): 1515 if name in Internal.Preferences.toolBarActions: 1516 ToolBarItem(action, self.activeList) 1517 else: 1518 ToolBarItem(action, self.inactiveList) 1519 1520 def saveActions(self): 1521 """write active actions into Preferences""" 1522 activeActions = (self.activeList.item( 1523 x).action for x in range(len(self.activeList))) 1524 names = { 1525 v: k for k, 1526 v in Internal.mainWindow.actionCollection( 1527 ).actions( 1528 ).items( 1529 )} 1530 Internal.Preferences.toolBarActions = ','.join( 1531 names[x] for x in activeActions) # pylint: disable=attribute-defined-outside-init 1532 Internal.mainWindow.refreshToolBar() 1533 1534 KGlobal.initStatic()