File indexing completed on 2024-04-21 04:01:48

0001 # -*- coding: utf-8 -*-
0002 
0003 """
0004 Copyright (C) 2008-2016 Wolfgang Rohdewald <wolfgang@rohdewald.de>
0005 
0006 SPDX-License-Identifier: GPL-2.0
0007 
0008 """
0009 
0010 # pylint: disable=invalid-name,W0611
0011 # invalid names, unused imports
0012 
0013 import inspect
0014 
0015 from twisted.internet.defer import Deferred, succeed
0016 
0017 from kde import KMessageBox, KDialog
0018 
0019 from qt import Qt, QDialog, QMessageBox, QWidget
0020 
0021 from common import Options, Internal, isAlive, StrMixin
0022 
0023 
0024 class IgnoreEscape:
0025 
0026     """as the name says. Use as a mixin for dialogs"""
0027 
0028     def keyPressEvent(self, event):
0029         """catch and ignore the Escape key"""
0030         if event.key() == Qt.Key_Escape:
0031             event.ignore()
0032         else:
0033             # pass on to the first declared ancestor class which
0034             # currently is either KDialog or QDialog
0035             self.__class__.__mro__[1].keyPressEvent(self, event)
0036 
0037 
0038 class KDialogIgnoringEscape(KDialog, IgnoreEscape):
0039 
0040     """as the name says"""
0041 
0042 
0043 class MustChooseKDialog(KDialogIgnoringEscape):
0044 
0045     """this dialog can only be closed if a choice has been done. Currently,
0046     the self.chosen thing is not used, code removed.
0047     So this dialog can only be closed by calling accept() or reject()"""
0048 
0049     def __init__(self):
0050         parent = Internal.mainWindow  # default
0051         # if we are (maybe indirectly) called from a method belonging to a QWidget, take that as parent
0052         # this does probably not work for classmethod or staticmethod but it is
0053         # good enough right now
0054         for frametuple in inspect.getouterframes(inspect.currentframe())[1:]:
0055             if 'self' in frametuple[0].f_locals:
0056                 obj = frametuple[0].f_locals['self']
0057                 if isinstance(obj, QWidget) and not isinstance(obj, QDialog) and isAlive(obj):
0058                     parent = obj
0059                     break
0060         if not isAlive(parent):
0061             parent = None
0062         KDialogIgnoringEscape.__init__(self, parent)
0063 
0064     def closeEvent(self, event): # pylint: disable=no-self-use
0065         """self.chosen is currently not used, never allow this"""
0066         event.ignore()
0067 
0068 
0069 class Prompt(MustChooseKDialog, StrMixin):
0070 
0071     """common code for things like QuestionYesNo, Information"""
0072 
0073     def __init__(self, msg, icon=QMessageBox.Information,
0074                  buttons=KDialog.Ok, caption=None, default=None):
0075         """buttons is button codes or-ed like KDialog.Ok | KDialog.Cancel. First one is default."""
0076         if r'\n' in msg:
0077             print(r'*********************** Fix this! Prompt gets \n in', msg)
0078             msg = msg.replace(r'\n', '\n')
0079         self.msg = msg
0080         self.default = default
0081         if Options.gui:
0082             MustChooseKDialog.__init__(self)
0083             self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint)
0084             self.setCaption(caption or '')
0085             KMessageBox.createKMessageBox(
0086                 self, icon, msg,
0087                 [], "", False,
0088                 KMessageBox.Options(KMessageBox.NoExec | KMessageBox.AllowLink))
0089             self.setButtons(KDialog.ButtonCode(buttons))
0090             # buttons is either Yes/No or Ok
0091             defaultButton = KDialog.Yes if KDialog.Yes & buttons else KDialog.Ok
0092             assert defaultButton & buttons, buttons
0093             self.button(defaultButton).setFocus()
0094 
0095     def returns(self, button=None):
0096         """the user answered"""
0097         if button is None:
0098             button = self.default
0099         return button in (KDialog.Yes, KDialog.Ok)
0100 
0101     def __str__(self):
0102         return self.msg
0103 
0104 
0105 class DeferredDialog(Deferred):
0106 
0107     """make dialogs usable as Deferred"""
0108 
0109     def __init__(self, dlg, modal=True, always=False):
0110         Deferred.__init__(self)
0111         self.dlg = dlg
0112         self.modal = modal
0113         self.always = always
0114         if Options.gui:
0115             if hasattr(self.dlg, 'buttonClicked'):
0116                 self.dlg.buttonClicked.connect(self.clicked)
0117             else:
0118                 self.dlg.accepted.connect(self.clicked)
0119                 self.dlg.rejected.connect(self.cancel)
0120         if Internal.reactor:
0121             Internal.reactor.callLater(0, self.__execute)
0122         else:
0123             # we do not yet have a reactor in initDb()
0124             self.__execute()
0125 
0126     def __execute(self):
0127         """now do the actual action"""
0128         if self.dlg is None:
0129             return None
0130         scene = Internal.scene
0131         if not Options.gui or not isAlive(self.dlg):
0132             return self.clicked()
0133         autoPlay = scene and scene.game and scene.game.autoPlay
0134         autoAnswerDelayed = autoPlay and not self.always
0135         if self.modal and not autoAnswerDelayed:
0136             self.dlg.exec_()
0137         else:
0138             self.dlg.show()
0139         if autoAnswerDelayed:
0140             Internal.reactor.callLater(
0141                 Internal.Preferences.animationDuration() / 500.0,
0142                 self.clicked)
0143         return None
0144 
0145     def clicked(self, button=None):
0146         """we got a reaction"""
0147         if self.dlg:
0148             result = self.dlg.returns(button)
0149         else:
0150             result = None
0151         self.__removeFromScene()
0152         self.callback(result)
0153 
0154     def cancel(self):
0155         """we want no answer, just let the dialog disappear"""
0156         self.__removeFromScene()
0157         Deferred.cancel(self)
0158 
0159     def __removeFromScene(self):
0160         """remove ourself"""
0161         if self.dlg and Internal.scene and isAlive(self.dlg):
0162             self.dlg.hide()
0163         self.dlg = None
0164 
0165 
0166 class QuestionYesNo(DeferredDialog):
0167 
0168     """wrapper, see class Prompt"""
0169 
0170     def __init__(self, msg, modal=True, always=False, caption=None):
0171         dialog = Prompt(msg, icon=QMessageBox.Question,
0172                         buttons=KDialog.Yes | KDialog.No, default=KDialog.Yes, caption=caption)
0173         DeferredDialog.__init__(self, dialog, modal=modal, always=always)
0174 
0175 
0176 class WarningYesNo(DeferredDialog):
0177 
0178     """wrapper, see class Prompt"""
0179 
0180     def __init__(self, msg, modal=True, caption=None):
0181         dialog = Prompt(msg, icon=QMessageBox.Warning,
0182                         buttons=KDialog.Yes | KDialog.No, default=KDialog.Yes, caption=caption)
0183         DeferredDialog.__init__(self, dialog, modal=modal)
0184 
0185 
0186 class Information(DeferredDialog):
0187 
0188     """wrapper, see class Prompt"""
0189 
0190     def __init__(self, msg, modal=True, caption=None):
0191         dialog = Prompt(msg, icon=QMessageBox.Information,
0192                         buttons=KDialog.Ok, caption=caption)
0193         DeferredDialog.__init__(self, dialog, modal=modal)
0194 
0195 
0196 class Sorry(DeferredDialog):
0197 
0198     """wrapper, see class Prompt"""
0199 
0200     def __init__(self, msg, modal=True, caption=None, always=False):
0201         dialog = Prompt(msg, icon=QMessageBox.Information,
0202                         buttons=KDialog.Ok, caption=caption or 'Sorry')
0203         DeferredDialog.__init__(self, dialog, modal=modal, always=always)
0204 
0205 
0206 def NoPrompt(unusedMsg):
0207     """we just want to be able to add callbacks even if non-interactive"""
0208     return succeed(None)