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()