Exemplo n.º 1
0
class ScoringScene(GameScene):
    """a scoring game"""

    # pylint: disable=too-many-instance-attributes

    def __init__(self, parent=None):
        self.scoringDialog = None
        super(ScoringScene, self).__init__(parent)
        self.selectorBoard.hasFocus = True

    @GameScene.game.setter
    def game(self, value):  # pylint: disable=arguments-differ
        game = self._game
        changing = value != game
        GameScene.game.fset(self, value)
        if changing:
            if value is not None:
                self.scoringDialog = ScoringDialog(scene=self)
            else:
                self.scoringDialog.hide()
                self.scoringDialog = None

    def handSelectorChanged(self, handBoard):
        """update all relevant dialogs"""
        GameScene.handSelectorChanged(self, handBoard)
        if self.scoringDialog:
            self.scoringDialog.slotInputChanged()

    def setupUi(self):
        """create all other widgets"""
        GameScene.setupUi(self)
        self.setObjectName("ScoringScene")
        self.selectorBoard = SelectorBoard()
        self.addItem(self.selectorBoard)
        QMetaObject.connectSlotsByName(self)

    def abort(self):
        """abort current game"""
        def answered(result):
            """got answer"""
            if result:
                self.game = None
            return result

        if Debug.quit:
            logDebug('ScoringScene.abort invoked')
        if not self.game:
            return succeed(True)
        elif self.game.finished():
            self.game = None
            return succeed(True)
        else:
            return QuestionYesNo(
                i18n("Do you really want to abort this game?"),
                always=True).addCallback(answered)

    def __moveTile(self, uiTile, wind, toConcealed):
        """the user pressed a wind letter or X for center, wanting to move a uiTile there"""
        # this tells the receiving board that this is keyboard, not mouse navigation>
        # needed for useful placement of the popup menu
        currentBoard = uiTile.board
        if wind == 'X':
            receiver = self.selectorBoard
        else:
            receiver = self.game.players[wind].handBoard
        movingMeld = currentBoard.uiMeldWithTile(uiTile)
        if receiver != currentBoard or toConcealed != movingMeld.meld.isConcealed:
            movingLastMeld = movingMeld.meld == self.computeLastMeld()
            if movingLastMeld:
                self.scoringDialog.clearLastTileCombo()
            receiver.dropTile(uiTile, toConcealed)
            if movingLastMeld and receiver == currentBoard:
                self.scoringDialog.fillLastTileCombo()

    def __navigateScoringGame(self, event):
        """keyboard navigation in a scoring game"""
        mod = event.modifiers()
        key = event.key()
        wind = chr(key % 128)
        windsX = ''.join(x.char for x in Wind.all)
        moveCommands = i18nc(
            'kajongg:keyboard commands for moving tiles to the players '
            'with wind ESWN or to the central tile selector (X)', windsX)
        uiTile = self.focusItem()
        if wind in moveCommands:
            # translate i18n wind key to ESWN:
            wind = windsX[moveCommands.index(wind)]
            self.__moveTile(uiTile, wind, bool(mod & Qt.ShiftModifier))
            return True
        if key == Qt.Key_Tab and self.game:
            tabItems = [self.selectorBoard]
            tabItems.extend(
                list(p.handBoard for p in self.game.players
                     if p.handBoard.uiTiles))
            tabItems.append(tabItems[0])
            currentBoard = uiTile.board
            currIdx = 0
            while tabItems[currIdx] != currentBoard and currIdx < len(
                    tabItems) - 2:
                currIdx += 1
            tabItems[currIdx + 1].hasFocus = True
            return True

    def keyPressEvent(self, event):
        """navigate in the selectorboard"""
        mod = event.modifiers()
        if mod in (Qt.NoModifier, Qt.ShiftModifier):
            if self.game:
                if self.__navigateScoringGame(event):
                    return
        GameScene.keyPressEvent(self, event)

    def adjustSceneView(self):
        """adjust the view such that exactly the wanted things are displayed
        without having to scroll"""
        if self.game:
            with AnimationSpeed():
                self.selectorBoard.maximize()
        GameScene.adjustSceneView(self)

    def prepareHand(self):
        """redecorate wall"""
        GameScene.prepareHand(self)
        if self.scoringDialog:
            self.scoringDialog.clearLastTileCombo()

    def updateSceneGUI(self):
        """update some actions, all auxiliary windows and the statusbar"""
        if not isAlive(self):
            return
        GameScene.updateSceneGUI(self)
        game = self.game
        mainWindow = self.mainWindow
        for action in [mainWindow.actionScoreGame, mainWindow.actionPlayGame]:
            action.setEnabled(not bool(game))
        mainWindow.actionAbortGame.setEnabled(bool(game))
        self.selectorBoard.setVisible(bool(game))
        self.selectorBoard.setEnabled(bool(game))

    def changeAngle(self):
        """now that no animation is running, really change"""
        self.selectorBoard.lightSource = self.newLightSource()
        GameScene.changeAngle(self)

    def computeLastTile(self):
        """compile hand info into a string as needed by the scoring engine"""
        if self.scoringDialog:
            # is None while ScoringGame is created
            return self.scoringDialog.computeLastTile()

    def computeLastMeld(self):
        """compile hand info into a string as needed by the scoring engine"""
        if self.scoringDialog:
            # is None while ScoringGame is created
            cbLastMeld = self.scoringDialog.cbLastMeld
            idx = cbLastMeld.currentIndex()
            if idx >= 0:
                return Meld(cbLastMeld.itemData(idx))
        return Meld()
Exemplo n.º 2
0
Arquivo: scene.py Projeto: KDE/kajongg
class ScoringScene(GameScene):

    """a scoring game"""
    # pylint: disable=too-many-instance-attributes

    def __init__(self, parent=None):
        self.scoringDialog = None
        super(ScoringScene, self).__init__(parent)
        self.selectorBoard.hasFocus = True

    @GameScene.game.setter
    def game(self, value):  # pylint: disable=arguments-differ
        game = self._game
        changing = value != game
        GameScene.game.fset(self, value)
        if changing:
            if value is not None:
                self.scoringDialog = ScoringDialog(scene=self)
            else:
                self.scoringDialog.hide()
                self.scoringDialog = None

    def handSelectorChanged(self, handBoard):
        """update all relevant dialogs"""
        GameScene.handSelectorChanged(self, handBoard)
        if self.scoringDialog:
            self.scoringDialog.slotInputChanged()

    def setupUi(self):
        """create all other widgets"""
        GameScene.setupUi(self)
        self.setObjectName("ScoringScene")
        self.selectorBoard = SelectorBoard()
        self.addItem(self.selectorBoard)
        QMetaObject.connectSlotsByName(self)

    def abort(self):
        """abort current game"""
        def answered(result):
            """got answer"""
            if result:
                self.game = None
            return result
        if Debug.quit:
            logDebug(u'ScoringScene.abort invoked')
        if not self.game:
            return succeed(True)
        elif self.game.finished():
            self.game = None
            return succeed(True)
        else:
            return QuestionYesNo(m18n("Do you really want to abort this game?"), always=True).addCallback(answered)

    def __moveTile(self, uiTile, wind, lowerHalf):
        """the user pressed a wind letter or X for center, wanting to move a uiTile there"""
        # this tells the receiving board that this is keyboard, not mouse navigation>
        # needed for useful placement of the popup menu
        currentBoard = uiTile.board
        if wind == 'X':
            receiver = self.selectorBoard
        else:
            receiver = self.game.players[wind].handBoard
        if receiver != currentBoard or bool(lowerHalf) != bool(uiTile.yoffset):
            movingLastMeld = uiTile.tile in self.computeLastMeld()
            if movingLastMeld:
                self.scoringDialog.clearLastTileCombo()
            receiver.dropTile(uiTile, lowerHalf)
            if movingLastMeld and receiver == currentBoard:
                self.scoringDialog.fillLastTileCombo()

    def __navigateScoringGame(self, event):
        """keyboard navigation in a scoring game"""
        mod = event.modifiers()
        key = event.key()
        wind = chr(key % 128)
        windsX = ''.join(x.char for x in Wind.all)
        moveCommands = m18nc('kajongg:keyboard commands for moving tiles to the players '
                             'with wind ESWN or to the central tile selector (X)', windsX)
        uiTile = self.focusItem()
        if wind in moveCommands:
            # translate i18n wind key to ESWN:
            wind = windsX[moveCommands.index(wind)]
            self.__moveTile(uiTile, wind, bool(mod & Qt.ShiftModifier))
            return True
        if key == Qt.Key_Tab and self.game:
            tabItems = [self.selectorBoard]
            tabItems.extend(
                list(p.handBoard for p in self.game.players if p.handBoard.uiTiles))
            tabItems.append(tabItems[0])
            currentBoard = uiTile.board
            currIdx = 0
            while tabItems[currIdx] != currentBoard and currIdx < len(tabItems) - 2:
                currIdx += 1
            tabItems[currIdx + 1].hasFocus = True
            return True

    def keyPressEvent(self, event):
        """navigate in the selectorboard"""
        mod = event.modifiers()
        if mod in (Qt.NoModifier, Qt.ShiftModifier):
            if self.game:
                if self.__navigateScoringGame(event):
                    return
        GameScene.keyPressEvent(self, event)

    def adjustView(self):
        """adjust the view such that exactly the wanted things are displayed
        without having to scroll"""
        if self.game:
            with MoveImmediate():
                self.selectorBoard.maximize()
        GameScene.adjustView(self)

    def prepareHand(self):
        """redecorate wall"""
        GameScene.prepareHand(self)
        if self.scoringDialog:
            self.scoringDialog.clearLastTileCombo()

    def updateSceneGUI(self):
        """update some actions, all auxiliary windows and the statusbar"""
        if not isAlive(self):
            return
        GameScene.updateSceneGUI(self)
        game = self.game
        mainWindow = self.mainWindow
        for action in [mainWindow.actionScoreGame, mainWindow.actionPlayGame]:
            action.setEnabled(not bool(game))
        mainWindow.actionAbortGame.setEnabled(bool(game))
        self.selectorBoard.setVisible(bool(game))
        self.selectorBoard.setEnabled(bool(game))

    def changeAngle(self):
        """now that no animation is running, really change"""
        self.selectorBoard.lightSource = self.newLightSource()
        GameScene.changeAngle(self)

    def computeLastTile(self):
        """compile hand info into a string as needed by the scoring engine"""
        if self.scoringDialog:
            # is None while ScoringGame is created
            return self.scoringDialog.computeLastTile()

    def computeLastMeld(self):
        """compile hand info into a string as needed by the scoring engine"""
        if self.scoringDialog:
            # is None while ScoringGame is created
            cbLastMeld = self.scoringDialog.cbLastMeld
            idx = cbLastMeld.currentIndex()
            if idx >= 0:
                return Meld(nativeString(
                    variantValue(cbLastMeld.itemData(idx))))
        return Meld()
Exemplo n.º 3
0
class PlayField(KXmlGuiWindow):
    """the main window"""
    # pylint: disable=R0902
    # pylint we need more than 10 instance attributes

    def __init__(self):
        # see http://lists.kde.org/?l=kde-games-devel&m=120071267328984&w=2
        Internal.field = self
        self.game = None
        self.__startingGame = False
        self.ignoreResizing = 1
        super(PlayField, self).__init__()
        self.background = None
        self.showShadows = None
        self._clientDialog = None

        self.playerWindow = None
        self.rulesetWindow = None
        self.scoreTable = None
        self.explainView = None
        self.scoringDialog = None
        self.confDialog = None
        self.setupUi()
        KStandardAction.preferences(self.showSettings, self.actionCollection())
        self.applySettings()
        self.setupGUI()
        self.retranslateUi()
        for action in self.toolBar().actions():
            if 'onfigure' in action.text():
                action.setPriority(QAction.LowPriority)
        if Options.host:
            self.playGame()

    @property
    def clientDialog(self):
        """wrapper: hide dialog when it is set to None"""
        return self._clientDialog

    @clientDialog.setter
    def clientDialog(self, value):
        """wrapper: hide dialog when it is set to None"""
        if isAlive(self._clientDialog) and not value:
            self._clientDialog.timer.stop()
            self._clientDialog.hide()
        self._clientDialog = value

    def sizeHint(self):
        """give the main window a sensible default size"""
        result = KXmlGuiWindow.sizeHint(self)
        result.setWidth(result.height() * 3 // 2) # we want space to the right for the buttons
        # the default is too small. Use at least 2/3 of screen height and 1/2 of screen width:
        available = KApplication.kApplication().desktop().availableGeometry()
        height = max(result.height(), available.height() * 2 // 3)
        width = max(result.width(), available.width() // 2)
        result.setHeight(height)
        result.setWidth(width)
        return result

    def resizeEvent(self, event):
        """Use this hook to determine if we want to ignore one more resize
        event happening for maximized / almost maximized windows.
        this misses a few cases where the window is almost maximized because at
        this point the window has no border yet: event.size, self.geometry() and
        self.frameGeometry are all the same. So we cannot check if the bordered
        window would fit into availableGeometry.
        """
        available = KApplication.kApplication().desktop().availableGeometry()
        if self.ignoreResizing == 1: # at startup
            if available.width() <= event.size().width() \
            or available.height() <= event.size().height():
                self.ignoreResizing += 1
        KXmlGuiWindow.resizeEvent(self, event)
        if self.clientDialog:
            self.clientDialog.placeInField()


    def showEvent(self, event):
        """force a resize which calculates the correct background image size"""
        self.centralView.resizeEvent(True)
        KXmlGuiWindow.showEvent(self, event)

    def handSelectorChanged(self, handBoard):
        """update all relevant dialogs"""
        if self.scoringDialog:
            self.scoringDialog.slotInputChanged()
        if self.game and not self.game.finished():
            self.game.wall.decoratePlayer(handBoard.player) # pylint: disable=E1101
        # first decorate walls - that will compute player.handBoard for explainView
        if self.explainView:
            self.explainView.refresh(self.game)

    def __kajonggAction(self, name, icon, slot=None, shortcut=None, actionData=None):
        """simplify defining actions"""
        res = KAction(self)
        res.setIcon(KIcon(icon))
        if slot:
            res.triggered.connect(slot)
        self.actionCollection().addAction(name, res)
        if shortcut:
            res.setShortcut( Qt.CTRL + shortcut)
            res.setShortcutContext(Qt.ApplicationShortcut)
        if PYQT_VERSION_STR != '4.5.2' or actionData is not None:
            res.setData(QVariant(actionData))
        return res

    def __kajonggToggleAction(self, name, icon, shortcut=None, actionData=None):
        """a checkable action"""
        res = self.__kajonggAction(name, icon, shortcut=shortcut, actionData=actionData)
        res.setCheckable(True)
        res.toggled.connect(self.__toggleWidget)
        return res

    def setupUi(self):
        """create all other widgets
        we could make the scene view the central widget but I did
        not figure out how to correctly draw the background with
        QGraphicsView/QGraphicsScene.
        QGraphicsView.drawBackground always wants a pixmap
        for a huge rect like 4000x3000 where my screen only has
        1920x1200"""
        # pylint: disable=R0915
        self.setObjectName("MainWindow")
        centralWidget = QWidget()
        scene = MJScene()
        self.centralScene = scene
        self.centralView = FittingView()
        layout = QGridLayout(centralWidget)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.centralView)
        self.tileset = None # just for pylint
        self.background = None # just for pylint
        self.tilesetName = Preferences.tilesetName
        self.windTileset = Tileset(Preferences.windTilesetName)

        self.discardBoard = DiscardBoard()
        self.discardBoard.setVisible(False)
        scene.addItem(self.discardBoard)

        self.selectorBoard = SelectorBoard()
        self.selectorBoard.setVisible(False)
        scene.addItem(self.selectorBoard)

        self.setCentralWidget(centralWidget)
        self.centralView.setScene(scene)
        self.centralView.setFocusPolicy(Qt.StrongFocus)
        self.adjustView()
        self.actionScoreGame = self.__kajonggAction("scoreGame", "draw-freehand", self.scoreGame, Qt.Key_C)
        self.actionPlayGame = self.__kajonggAction("play", "arrow-right", self.playGame, Qt.Key_N)
        self.actionAbortGame = self.__kajonggAction("abort", "dialog-close", self.abortAction, Qt.Key_W)
        self.actionAbortGame.setEnabled(False)
        self.actionQuit = self.__kajonggAction("quit", "application-exit", self.close, Qt.Key_Q)
        self.actionPlayers = self.__kajonggAction("players", "im-user", self.slotPlayers)
        self.actionRulesets = self.__kajonggAction("rulesets", "games-kajongg-law", self.slotRulesets)
        self.actionChat = self.__kajonggToggleAction("chat", "call-start",
            shortcut=Qt.Key_H, actionData=ChatWindow)
        game = self.game
        self.actionChat.setEnabled(bool(game) and bool(game.client) and not game.client.hasLocalServer())
        self.actionChat.setChecked(bool(game) and bool(game.client) and bool(game.client.table.chatWindow))
        self.actionScoring = self.__kajonggToggleAction("scoring", "draw-freehand",
            shortcut=Qt.Key_S, actionData=ScoringDialog)
        self.actionScoring.setEnabled(False)
        self.actionAngle = self.__kajonggAction("angle", "object-rotate-left", self.changeAngle, Qt.Key_G)
        self.actionAngle.setEnabled(False)
        self.actionFullscreen = KToggleFullScreenAction(self.actionCollection())
        self.actionFullscreen.setShortcut(Qt.CTRL + Qt.Key_F)
        self.actionFullscreen.setShortcutContext(Qt.ApplicationShortcut)
        self.actionFullscreen.setWindow(self)
        self.actionCollection().addAction("fullscreen", self.actionFullscreen)
        self.actionFullscreen.toggled.connect(self.fullScreen)
        self.actionScoreTable = self.__kajonggToggleAction("scoreTable", "format-list-ordered",
            Qt.Key_T, actionData=ScoreTable)
        self.actionExplain = self.__kajonggToggleAction("explain", "applications-education",
            Qt.Key_E, actionData=ExplainView)
        self.actionAutoPlay = self.__kajonggAction("demoMode", "arrow-right-double", None, Qt.Key_D)
        self.actionAutoPlay.setCheckable(True)
        self.actionAutoPlay.toggled.connect(self.__toggleDemoMode)
        self.actionAutoPlay.setChecked(Internal.autoPlay)
        QMetaObject.connectSlotsByName(self)

    def showWall(self):
        """shows the wall according to the game rules (lenght may vary)"""
        UIWall(self.game)   # sets self.game.wall
        if self.discardBoard:
            # scale it such that it uses the place within the wall optimally.
            # we need to redo this because the wall length can vary between games.
            self.discardBoard.maximize()

    def genPlayers(self):
        """generate four default VisiblePlayers"""
        return Players([VisiblePlayer(self.game, idx) for idx in range(4)])

    def fullScreen(self, toggle):
        """toggle between full screen and normal view"""
        self.actionFullscreen.setFullScreen(self, toggle)

    def abortAction(self):
        """abort current game"""
        def doNotQuit(dummy):
            """ignore failure to abort"""
        self.abort().addErrback(doNotQuit)

    def abort(self):
        """abort current game"""
        def gotAnswer(result, autoPlaying):
            """user answered"""
            if result:
                return self.abortGame()
            else:
                self.actionAutoPlay.setChecked(autoPlaying)
                return fail(Exception('no abort'))
        def gotError(result):
            """abortGame failed"""
            logDebug('abortGame error:%s/%s ' % (str(result), result.getErrorMessage()))
        if not self.game:
            self.startingGame = False
            return succeed(None)
        autoPlaying = self.actionAutoPlay.isChecked()
        self.actionAutoPlay.setChecked(False)
        if self.game.finished():
            return self.abortGame()
        else:
            return QuestionYesNo(m18n("Do you really want to abort this game?"), always=True).addCallback(
                gotAnswer, autoPlaying).addErrback(gotError)

    def abortGame(self):
        """if a game is active, abort it"""
        if self.game is None: # meanwhile somebody else might have aborted
            return succeed(None)
        game = self.game
        self.game = None
        return game.close()

    def closeEvent(self, event):
        """somebody wants us to close, maybe ALT-F4 or so"""
        event.ignore()
        def doNotQuit(dummy):
            """ignore failure to abort"""
        self.abort().addCallback(HumanClient.shutdownHumanClients).addCallbacks(Client.quitProgram, doNotQuit)

    def __moveTile(self, tile, wind, lowerHalf):
        """the user pressed a wind letter or X for center, wanting to move a tile there"""
        # this tells the receiving board that this is keyboard, not mouse navigation>
        # needed for useful placement of the popup menu
        assert self.game.isScoringGame()
        assert isinstance(tile, Tile), (tile, str(tile))
        currentBoard = tile.board
        dragTile, dragMeld = currentBoard.dragObject(tile)
        if wind == 'X':
            receiver = self.selectorBoard
        else:
            receiver = self.game.players[wind].handBoard
        if receiver != currentBoard or bool(lowerHalf) != bool(tile.yoffset):
            movingLastMeld = tile.element in self.computeLastMeld().pairs
            if movingLastMeld:
                self.scoringDialog.clearLastTileCombo()
            receiver.dropHere(dragTile, dragMeld, lowerHalf)
            if movingLastMeld and receiver == currentBoard:
                self.scoringDialog.fillLastTileCombo()

    def __navigateScoringGame(self, event):
        """keyboard navigation in a scoring game"""
        mod = event.modifiers()
        key = event.key()
        wind = chr(key%128)
        moveCommands = m18nc('kajongg:keyboard commands for moving tiles to the players ' \
            'with wind ESWN or to the central tile selector (X)', 'ESWNX')
        tile = self.centralScene.focusItem().tile
        if wind in moveCommands:
            # translate i18n wind key to ESWN:
            wind = 'ESWNX'[moveCommands.index(wind)]
            self.__moveTile(tile, wind, mod &Qt.ShiftModifier)
            return True
        if key == Qt.Key_Tab and self.game:
            tabItems = [self.selectorBoard]
            tabItems.extend(list(p.handBoard for p in self.game.players if p.handBoard.tiles))
            tabItems.append(tabItems[0])
            currentBoard = tile.board if isinstance(tile, Tile) else None
            currIdx = 0
            while tabItems[currIdx] != currentBoard and currIdx < len(tabItems) -2:
                currIdx += 1
            tabItems[currIdx+1].hasFocus = True
            return True

    def keyPressEvent(self, event):
        """navigate in the selectorboard"""
        mod = event.modifiers()
        if mod in (Qt.NoModifier, Qt.ShiftModifier):
            if self.game and self.game.isScoringGame():
                if self.__navigateScoringGame(event):
                    return
            if self.clientDialog:
                self.clientDialog.keyPressEvent(event)
        KXmlGuiWindow.keyPressEvent(self, event)

    def retranslateUi(self):
        """retranslate"""
        self.actionScoreGame.setText(m18nc('@action:inmenu', "&Score Manual Game"))
        self.actionScoreGame.setIconText(m18nc('@action:intoolbar', 'Manual Game'))
        self.actionScoreGame.setHelpText(m18nc('kajongg @info:tooltip', '&Score a manual game.'))

        self.actionPlayGame.setText(m18nc('@action:intoolbar', "&Play"))
        self.actionPlayGame.setPriority(QAction.LowPriority)
        self.actionPlayGame.setHelpText(m18nc('kajongg @info:tooltip', 'Start a new game.'))

        self.actionAbortGame.setText(m18nc('@action:inmenu', "&Abort Game"))
        self.actionAbortGame.setPriority(QAction.LowPriority)
        self.actionAbortGame.setHelpText(m18nc('kajongg @info:tooltip', 'Abort the current game.'))

        self.actionQuit.setText(m18nc('@action:inmenu', "&Quit Kajongg"))
        self.actionQuit.setPriority(QAction.LowPriority)

        self.actionPlayers.setText(m18nc('@action:intoolbar', "&Players"))
        self.actionPlayers.setHelpText(m18nc('kajongg @info:tooltip', 'define your players.'))

        self.actionRulesets.setText(m18nc('@action:intoolbar', "&Rulesets"))
        self.actionRulesets.setHelpText(m18nc('kajongg @info:tooltip', 'customize rulesets.'))

        self.actionAngle.setText(m18nc('@action:inmenu', "&Change Visual Angle"))
        self.actionAngle.setIconText(m18nc('@action:intoolbar', "Angle"))
        self.actionAngle.setHelpText(m18nc('kajongg @info:tooltip', "Change the visual appearance of the tiles."))

        self.actionScoring.setText(m18nc('@action:inmenu', "&Show Scoring Editor"))
        self.actionScoring.setIconText(m18nc('@action:intoolbar', "&Scoring"))
        self.actionScoring.setHelpText(m18nc('kajongg @info:tooltip',
                "Show or hide the scoring editor for a manual game."))

        self.actionScoreTable.setText(m18nc('kajongg @action:inmenu', "&Score Table"))
        self.actionScoreTable.setIconText(m18nc('kajongg @action:intoolbar', "&Scores"))
        self.actionScoreTable.setHelpText(m18nc('kajongg @info:tooltip',
                "Show or hide the score table for the current game."))

        self.actionExplain.setText(m18nc('@action:inmenu', "&Explain Scores"))
        self.actionExplain.setIconText(m18nc('@action:intoolbar', "&Explain"))
        self.actionExplain.setHelpText(m18nc('kajongg @info:tooltip',
                'Explain the scoring for all players in the current game.'))

        self.actionAutoPlay.setText(m18nc('@action:inmenu', "&Demo Mode"))
        self.actionAutoPlay.setPriority(QAction.LowPriority)
        self.actionAutoPlay.setHelpText(m18nc('kajongg @info:tooltip',
                'Let the computer take over for you. Start a new local game if needed.'))

        self.actionChat.setText(m18n("C&hat"))
        self.actionChat.setHelpText(m18nc('kajongg @info:tooltip', 'Chat with the other players.'))

    def changeEvent(self, event):
        """when the applicationwide language changes, recreate GUI"""
        if event.type() == QEvent.LanguageChange:
            self.setupGUI()
            self.retranslateUi()

    def slotPlayers(self):
        """show the player list"""
        if not self.playerWindow:
            self.playerWindow = PlayerList(self)
        self.playerWindow.show()

    def slotRulesets(self):
        """show the player list"""
        if not self.rulesetWindow:
            self.rulesetWindow = RulesetSelector()
        self.rulesetWindow.show()

    def selectScoringGame(self):
        """show all games, select an existing game or create a new game"""
        Players.load()
        if len(Players.humanNames) < 4:
            logWarning(m18n('Please define four players in <interface>Settings|Players</interface>'))
            return False
        gameSelector = Games(self)
        if gameSelector.exec_():
            selected = gameSelector.selectedGame
            if selected is not None:
                ScoringGame.loadFromDB(selected)
            else:
                self.newGame()
            if self.game:
                self.game.throwDices()
        gameSelector.close()
        self.updateGUI()
        return bool(self.game)

    def scoreGame(self):
        """score a local game"""
        if self.selectScoringGame():
            self.actionScoring.setChecked(True)

    def playGame(self):
        """play a remote game: log into a server and show its tables"""
        self.startingGame = True
        HumanClient()

    def adjustView(self):
        """adjust the view such that exactly the wanted things are displayed
        without having to scroll"""
        if not Internal.scaleScene:
            return
        if self.game:
            with Animated(False):
                self.game.wall.decorate()
                if self.discardBoard:
                    self.discardBoard.maximize()
                if self.selectorBoard:
                    self.selectorBoard.maximize()
                for tile in self.game.wall.tiles:
                    if tile.board:
                        tile.board.placeTile(tile)
        view, scene = self.centralView, self.centralScene
        oldRect = view.sceneRect()
        view.setSceneRect(scene.itemsBoundingRect())
        newRect = view.sceneRect()
        if oldRect != newRect:
            view.fitInView(scene.itemsBoundingRect(), Qt.KeepAspectRatio)

    @property
    def startingGame(self):
        """are we trying to start a game?"""
        return self.__startingGame

    @startingGame.setter
    def startingGame(self, value):
        """are we trying to start a game?"""
        if value != self.__startingGame:
            self.__startingGame = value
            self.updateGUI()

    @property
    def tilesetName(self):
        """the name of the current tileset"""
        return self.tileset.desktopFileName

    @tilesetName.setter
    def tilesetName(self, name):
        """the name of the current tileset"""
        self.tileset = Tileset(name)

    @property
    def backgroundName(self):
        """setting this also actually changes the background"""
        return self.background.desktopFileName if self.background else ''

    @backgroundName.setter
    def backgroundName(self, name):
        """setter for backgroundName"""
        self.background = Background(name)
        self.background.setPalette(self.centralWidget())
        self.centralWidget().setAutoFillBackground(True)

    def applySettings(self):
        """apply preferences"""
        # pylint: disable=R0912
        # too many branches
        self.actionAngle.setEnabled(bool(self.game) and Preferences.showShadows)
        animate() # drain the queue
        afterCurrentAnimationDo(self.__applySettings2)

    def __applySettings2(self, dummyResults):
        """now no animation is running"""
        with Animated(False):
            if self.tilesetName != Preferences.tilesetName:
                self.tilesetName = Preferences.tilesetName
                if self.game:
                    self.game.wall.tileset = self.tileset
                for item in self.centralScene.nonTiles():
                    try:
                        item.tileset = self.tileset
                    except AttributeError:
                        continue
                # change players last because we need the wall already to be repositioned
                self.adjustView() # the new tiles might be larger
            if self.game:
                for player in self.game.players:
                    if player.handBoard:
                        player.handBoard.rearrangeMelds = Preferences.rearrangeMelds
            if self.backgroundName != Preferences.backgroundName:
                self.backgroundName = Preferences.backgroundName
            if self.showShadows is None or self.showShadows != Preferences.showShadows:
                self.showShadows = Preferences.showShadows
                if self.game:
                    wall = self.game.wall
                    wall.showShadows = self.showShadows
                self.selectorBoard.showShadows = self.showShadows
                if self.discardBoard:
                    self.discardBoard.showShadows = self.showShadows
                for tile in self.centralScene.graphicsTileItems():
                    tile.setClippingFlags()
                self.adjustView()
        Sound.enabled = Preferences.useSounds
        self.centralScene.placeFocusRect()

    def showSettings(self):
        """show preferences dialog. If it already is visible, do nothing"""
        if KConfigDialog.showDialog("settings"):
            return
        # if an animation is running, Qt segfaults somewhere deep
        # in the SVG renderer rendering the wind tiles for the tile
        # preview
        afterCurrentAnimationDo(self.__showSettings2)

    def __showSettings2(self, dummyResult):
        """now that no animation is running, show settings dialog"""
        self.confDialog = ConfigDialog(self, "settings")
        self.confDialog.settingsChanged.connect(self.applySettings)
        self.confDialog.show()

    def newGame(self):
        """asks user for players and ruleset for a new game and returns that new game"""
        Players.load() # we want to make sure we have the current definitions
        selectDialog = SelectPlayers(self.game)
        if not selectDialog.exec_():
            return
        return ScoringGame(selectDialog.names, selectDialog.cbRuleset.current)

    def __toggleWidget(self, checked):
        """user has toggled widget visibility with an action"""
        action = self.sender()
        actionData = action.data().toPyObject()
        if checked:
            if isinstance(actionData, type):
                actionData = actionData(game=self.game)
                action.setData(QVariant(actionData))
                if isinstance(actionData, ScoringDialog):
                    self.scoringDialog = actionData
                    actionData.btnSave.clicked.connect(self.nextScoringHand)
                    actionData.scoringClosed.connect(self.__scoringClosed)
                elif isinstance(actionData, ExplainView):
                    self.explainView = actionData
                elif isinstance(actionData, ScoreTable):
                    self.scoreTable = actionData
            actionData.show()
            actionData.raise_()
        else:
            assert actionData
            actionData.hide()

    def __toggleDemoMode(self, checked):
        """switch on / off for autoPlay"""
        if self.game:
            self.centralScene.placeFocusRect() # show/hide it
            self.game.autoPlay = checked
            if checked and self.clientDialog:
                self.clientDialog.proposeAction() # an illegal action might have focus
                self.clientDialog.selectButton() # select default, abort timeout
        else:
            Internal.autoPlay = checked
            if checked:
                # TODO: use the last used ruleset. Right now it always takes the first of the list.
                self.playGame()

    def __scoringClosed(self):
        """the scoring window has been closed with ALT-F4 or similar"""
        self.actionScoring.setChecked(False)

    def nextScoringHand(self):
        """save hand to database, update score table and balance in status line, prepare next hand"""
        if self.game.winner:
            for player in self.game.players:
                player.usedDangerousFrom = None
                for ruleBox in player.manualRuleBoxes:
                    rule = ruleBox.rule
                    if rule.name == 'Dangerous Game' and ruleBox.isChecked():
                        self.game.winner.usedDangerousFrom = player
        self.game.saveHand()
        self.game.maybeRotateWinds()
        self.game.prepareHand()
        self.game.initHand()

    def prepareHand(self):
        """redecorate wall"""
        self.updateGUI()
        if self.game:
            self.game.wall.decorate()
        if self.scoringDialog:
            self.scoringDialog.clearLastTileCombo()

    def updateGUI(self):
        """update some actions, all auxiliary windows and the statusbar"""
        if not isAlive(self):
            return
        title = ''
        connections = list(x.connection for x in HumanClient.humanClients if x.connection)
        game = self.game
        if not game:
            title = ', '.join('{name}/{url}'.format(name=x.username, url=x.url) for x in connections)
            if title:
                self.setWindowTitle('%s - Kajongg' % title)
        for action in [self.actionScoreGame, self.actionPlayGame]:
            action.setEnabled(not bool(game))
        self.actionAbortGame.setEnabled(bool(game))
        self.actionAngle.setEnabled(bool(game) and self.showShadows)
        scoring = bool(game and game.isScoringGame())
        self.selectorBoard.setVisible(scoring)
        self.selectorBoard.setEnabled(scoring)
        self.discardBoard.setVisible(bool(game) and not scoring)
        self.actionScoring.setEnabled(scoring and not game.finished())
        self.actionAutoPlay.setEnabled(not self.startingGame and not scoring)
        self.actionChat.setEnabled(bool(game) and bool(game.client)
            and not game.client.hasLocalServer() and not self.startingGame)
            # chatting on tables before game started works with chat button per table
        self.actionChat.setChecked(self.actionChat.isEnabled() and bool(game.client.table.chatWindow))
        if self.actionScoring.isChecked():
            self.actionScoring.setChecked(scoring and not game.finished())
        for view in [self.scoringDialog, self.explainView, self.scoreTable]:
            if view:
                view.refresh(game)
        self.__showBalance()

    def changeAngle(self):
        """change the lightSource"""
        if self.game:
            afterCurrentAnimationDo(self.__changeAngle2)

    def __changeAngle2(self, dummyResult):
        """now that no animation is running, really change"""
        if self.game: # might be finished meanwhile
            with Animated(False):
                wall = self.game.wall
                oldIdx = LIGHTSOURCES.index(wall.lightSource) # pylint: disable=E1101
                newLightSource = LIGHTSOURCES[(oldIdx + 1) % 4]
                wall.lightSource = newLightSource
                self.selectorBoard.lightSource = newLightSource
                self.discardBoard.lightSource = newLightSource
                self.adjustView()
                scoringDialog = self.actionScoring.data().toPyObject()
                if isinstance(scoringDialog, ScoringDialog):
                    scoringDialog.computeScores()
                self.centralScene.placeFocusRect()

    def __showBalance(self):
        """show the player balances in the status bar"""
        sBar = self.statusBar()
        if self.game:
            for idx, player in enumerate(self.game.players):
                sbMessage = player.localName + ': ' + str(player.balance)
                if sBar.hasItem(idx):
                    sBar.changeItem(sbMessage, idx)
                else:
                    sBar.insertItem(sbMessage, idx, 1)
                    sBar.setItemAlignment(idx, Qt.AlignLeft)
        else:
            for idx in range(5):
                if sBar.hasItem(idx):
                    sBar.removeItem(idx)

    def computeLastTile(self):
        """compile hand info into a string as needed by the scoring engine"""
        if self.scoringDialog:
            return self.scoringDialog.computeLastTile()

    def computeLastMeld(self):
        """compile hand info into a string as needed by the scoring engine"""
        if self.scoringDialog:
            cbLastMeld = self.scoringDialog.cbLastMeld
            idx = cbLastMeld.currentIndex()
            if idx >= 0:
                return Meld(str(cbLastMeld.itemData(idx).toString()))
        return Meld()

    @staticmethod
    def askSwap(swappers):
        """use this as a proxy such that module game does not have to import playfield.
        Game should also run on a server without KDE being installed"""
        return SwapDialog(swappers).exec_()
Exemplo n.º 4
0
class PlayField(KXmlGuiWindow):
    """the main window"""

    # pylint: disable=R0902
    # pylint we need more than 10 instance attributes

    def __init__(self):
        # see http://lists.kde.org/?l=kde-games-devel&m=120071267328984&w=2
        Internal.field = self
        self.game = None
        self.__startingGame = False
        self.ignoreResizing = 1
        super(PlayField, self).__init__()
        self.background = None
        self.showShadows = None
        self._clientDialog = None

        self.playerWindow = None
        self.rulesetWindow = None
        self.scoreTable = None
        self.explainView = None
        self.scoringDialog = None
        self.confDialog = None
        self.setupUi()
        KStandardAction.preferences(self.showSettings, self.actionCollection())
        self.applySettings()
        self.setupGUI()
        self.retranslateUi()
        for action in self.toolBar().actions():
            if 'onfigure' in action.text():
                action.setPriority(QAction.LowPriority)
        if Options.host:
            self.playGame()

    @property
    def clientDialog(self):
        """wrapper: hide dialog when it is set to None"""
        return self._clientDialog

    @clientDialog.setter
    def clientDialog(self, value):
        """wrapper: hide dialog when it is set to None"""
        if isAlive(self._clientDialog) and not value:
            self._clientDialog.timer.stop()
            self._clientDialog.hide()
        self._clientDialog = value

    def sizeHint(self):
        """give the main window a sensible default size"""
        result = KXmlGuiWindow.sizeHint(self)
        result.setWidth(result.height() * 3 //
                        2)  # we want space to the right for the buttons
        # the default is too small. Use at least 2/3 of screen height and 1/2 of screen width:
        available = KApplication.kApplication().desktop().availableGeometry()
        height = max(result.height(), available.height() * 2 // 3)
        width = max(result.width(), available.width() // 2)
        result.setHeight(height)
        result.setWidth(width)
        return result

    def resizeEvent(self, event):
        """Use this hook to determine if we want to ignore one more resize
        event happening for maximized / almost maximized windows.
        this misses a few cases where the window is almost maximized because at
        this point the window has no border yet: event.size, self.geometry() and
        self.frameGeometry are all the same. So we cannot check if the bordered
        window would fit into availableGeometry.
        """
        available = KApplication.kApplication().desktop().availableGeometry()
        if self.ignoreResizing == 1:  # at startup
            if available.width() <= event.size().width() \
            or available.height() <= event.size().height():
                self.ignoreResizing += 1
        KXmlGuiWindow.resizeEvent(self, event)
        if self.clientDialog:
            self.clientDialog.placeInField()

    def showEvent(self, event):
        """force a resize which calculates the correct background image size"""
        self.centralView.resizeEvent(True)
        KXmlGuiWindow.showEvent(self, event)

    def handSelectorChanged(self, handBoard):
        """update all relevant dialogs"""
        if self.scoringDialog:
            self.scoringDialog.slotInputChanged()
        if self.game and not self.game.finished():
            self.game.wall.decoratePlayer(handBoard.player)  # pylint: disable=E1101
        # first decorate walls - that will compute player.handBoard for explainView
        if self.explainView:
            self.explainView.refresh(self.game)

    def __kajonggAction(self,
                        name,
                        icon,
                        slot=None,
                        shortcut=None,
                        actionData=None):
        """simplify defining actions"""
        res = KAction(self)
        res.setIcon(KIcon(icon))
        if slot:
            res.triggered.connect(slot)
        self.actionCollection().addAction(name, res)
        if shortcut:
            res.setShortcut(Qt.CTRL + shortcut)
            res.setShortcutContext(Qt.ApplicationShortcut)
        if PYQT_VERSION_STR != '4.5.2' or actionData is not None:
            res.setData(QVariant(actionData))
        return res

    def __kajonggToggleAction(self,
                              name,
                              icon,
                              shortcut=None,
                              actionData=None):
        """a checkable action"""
        res = self.__kajonggAction(name,
                                   icon,
                                   shortcut=shortcut,
                                   actionData=actionData)
        res.setCheckable(True)
        res.toggled.connect(self.__toggleWidget)
        return res

    def setupUi(self):
        """create all other widgets
        we could make the scene view the central widget but I did
        not figure out how to correctly draw the background with
        QGraphicsView/QGraphicsScene.
        QGraphicsView.drawBackground always wants a pixmap
        for a huge rect like 4000x3000 where my screen only has
        1920x1200"""
        # pylint: disable=R0915
        self.setObjectName("MainWindow")
        centralWidget = QWidget()
        scene = MJScene()
        self.centralScene = scene
        self.centralView = FittingView()
        layout = QGridLayout(centralWidget)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.centralView)
        self.tileset = None  # just for pylint
        self.background = None  # just for pylint
        self.tilesetName = Preferences.tilesetName
        self.windTileset = Tileset(Preferences.windTilesetName)

        self.discardBoard = DiscardBoard()
        self.discardBoard.setVisible(False)
        scene.addItem(self.discardBoard)

        self.selectorBoard = SelectorBoard()
        self.selectorBoard.setVisible(False)
        scene.addItem(self.selectorBoard)

        self.setCentralWidget(centralWidget)
        self.centralView.setScene(scene)
        self.centralView.setFocusPolicy(Qt.StrongFocus)
        self.adjustView()
        self.actionScoreGame = self.__kajonggAction("scoreGame",
                                                    "draw-freehand",
                                                    self.scoreGame, Qt.Key_C)
        self.actionPlayGame = self.__kajonggAction("play", "arrow-right",
                                                   self.playGame, Qt.Key_N)
        self.actionAbortGame = self.__kajonggAction("abort", "dialog-close",
                                                    self.abortAction, Qt.Key_W)
        self.actionAbortGame.setEnabled(False)
        self.actionQuit = self.__kajonggAction("quit", "application-exit",
                                               self.close, Qt.Key_Q)
        self.actionPlayers = self.__kajonggAction("players", "im-user",
                                                  self.slotPlayers)
        self.actionRulesets = self.__kajonggAction("rulesets",
                                                   "games-kajongg-law",
                                                   self.slotRulesets)
        self.actionChat = self.__kajonggToggleAction("chat",
                                                     "call-start",
                                                     shortcut=Qt.Key_H,
                                                     actionData=ChatWindow)
        game = self.game
        self.actionChat.setEnabled(
            bool(game) and bool(game.client)
            and not game.client.hasLocalServer())
        self.actionChat.setChecked(
            bool(game) and bool(game.client)
            and bool(game.client.table.chatWindow))
        self.actionScoring = self.__kajonggToggleAction(
            "scoring",
            "draw-freehand",
            shortcut=Qt.Key_S,
            actionData=ScoringDialog)
        self.actionScoring.setEnabled(False)
        self.actionAngle = self.__kajonggAction("angle", "object-rotate-left",
                                                self.changeAngle, Qt.Key_G)
        self.actionAngle.setEnabled(False)
        self.actionFullscreen = KToggleFullScreenAction(
            self.actionCollection())
        self.actionFullscreen.setShortcut(Qt.CTRL + Qt.Key_F)
        self.actionFullscreen.setShortcutContext(Qt.ApplicationShortcut)
        self.actionFullscreen.setWindow(self)
        self.actionCollection().addAction("fullscreen", self.actionFullscreen)
        self.actionFullscreen.toggled.connect(self.fullScreen)
        self.actionScoreTable = self.__kajonggToggleAction(
            "scoreTable",
            "format-list-ordered",
            Qt.Key_T,
            actionData=ScoreTable)
        self.actionExplain = self.__kajonggToggleAction(
            "explain",
            "applications-education",
            Qt.Key_E,
            actionData=ExplainView)
        self.actionAutoPlay = self.__kajonggAction("demoMode",
                                                   "arrow-right-double", None,
                                                   Qt.Key_D)
        self.actionAutoPlay.setCheckable(True)
        self.actionAutoPlay.toggled.connect(self.__toggleDemoMode)
        self.actionAutoPlay.setChecked(Internal.autoPlay)
        QMetaObject.connectSlotsByName(self)

    def showWall(self):
        """shows the wall according to the game rules (lenght may vary)"""
        UIWall(self.game)  # sets self.game.wall
        if self.discardBoard:
            # scale it such that it uses the place within the wall optimally.
            # we need to redo this because the wall length can vary between games.
            self.discardBoard.maximize()

    def genPlayers(self):
        """generate four default VisiblePlayers"""
        return Players([VisiblePlayer(self.game, idx) for idx in range(4)])

    def fullScreen(self, toggle):
        """toggle between full screen and normal view"""
        self.actionFullscreen.setFullScreen(self, toggle)

    def abortAction(self):
        """abort current game"""
        def doNotQuit(dummy):
            """ignore failure to abort"""

        self.abort().addErrback(doNotQuit)

    def abort(self):
        """abort current game"""
        def gotAnswer(result, autoPlaying):
            """user answered"""
            if result:
                return self.abortGame()
            else:
                self.actionAutoPlay.setChecked(autoPlaying)
                return fail(Exception('no abort'))

        def gotError(result):
            """abortGame failed"""
            logDebug('abortGame error:%s/%s ' %
                     (str(result), result.getErrorMessage()))

        if not self.game:
            self.startingGame = False
            return succeed(None)
        autoPlaying = self.actionAutoPlay.isChecked()
        self.actionAutoPlay.setChecked(False)
        if self.game.finished():
            return self.abortGame()
        else:
            return QuestionYesNo(
                m18n("Do you really want to abort this game?"),
                always=True).addCallback(gotAnswer,
                                         autoPlaying).addErrback(gotError)

    def abortGame(self):
        """if a game is active, abort it"""
        if self.game is None:  # meanwhile somebody else might have aborted
            return succeed(None)
        game = self.game
        self.game = None
        return game.close()

    def closeEvent(self, event):
        """somebody wants us to close, maybe ALT-F4 or so"""
        event.ignore()

        def doNotQuit(dummy):
            """ignore failure to abort"""

        self.abort().addCallback(
            HumanClient.shutdownHumanClients).addCallbacks(
                Client.quitProgram, doNotQuit)

    def __moveTile(self, tile, wind, lowerHalf):
        """the user pressed a wind letter or X for center, wanting to move a tile there"""
        # this tells the receiving board that this is keyboard, not mouse navigation>
        # needed for useful placement of the popup menu
        assert self.game.isScoringGame()
        assert isinstance(tile, Tile), (tile, str(tile))
        currentBoard = tile.board
        dragTile, dragMeld = currentBoard.dragObject(tile)
        if wind == 'X':
            receiver = self.selectorBoard
        else:
            receiver = self.game.players[wind].handBoard
        if receiver != currentBoard or bool(lowerHalf) != bool(tile.yoffset):
            movingLastMeld = tile.element in self.computeLastMeld().pairs
            if movingLastMeld:
                self.scoringDialog.clearLastTileCombo()
            receiver.dropHere(dragTile, dragMeld, lowerHalf)
            if movingLastMeld and receiver == currentBoard:
                self.scoringDialog.fillLastTileCombo()

    def __navigateScoringGame(self, event):
        """keyboard navigation in a scoring game"""
        mod = event.modifiers()
        key = event.key()
        wind = chr(key % 128)
        moveCommands = m18nc('kajongg:keyboard commands for moving tiles to the players ' \
            'with wind ESWN or to the central tile selector (X)', 'ESWNX')
        tile = self.centralScene.focusItem().tile
        if wind in moveCommands:
            # translate i18n wind key to ESWN:
            wind = 'ESWNX'[moveCommands.index(wind)]
            self.__moveTile(tile, wind, mod & Qt.ShiftModifier)
            return True
        if key == Qt.Key_Tab and self.game:
            tabItems = [self.selectorBoard]
            tabItems.extend(
                list(p.handBoard for p in self.game.players
                     if p.handBoard.tiles))
            tabItems.append(tabItems[0])
            currentBoard = tile.board if isinstance(tile, Tile) else None
            currIdx = 0
            while tabItems[currIdx] != currentBoard and currIdx < len(
                    tabItems) - 2:
                currIdx += 1
            tabItems[currIdx + 1].hasFocus = True
            return True

    def keyPressEvent(self, event):
        """navigate in the selectorboard"""
        mod = event.modifiers()
        if mod in (Qt.NoModifier, Qt.ShiftModifier):
            if self.game and self.game.isScoringGame():
                if self.__navigateScoringGame(event):
                    return
            if self.clientDialog:
                self.clientDialog.keyPressEvent(event)
        KXmlGuiWindow.keyPressEvent(self, event)

    def retranslateUi(self):
        """retranslate"""
        self.actionScoreGame.setText(
            m18nc('@action:inmenu', "&Score Manual Game"))
        self.actionScoreGame.setIconText(
            m18nc('@action:intoolbar', 'Manual Game'))
        self.actionScoreGame.setHelpText(
            m18nc('kajongg @info:tooltip', '&Score a manual game.'))

        self.actionPlayGame.setText(m18nc('@action:intoolbar', "&Play"))
        self.actionPlayGame.setPriority(QAction.LowPriority)
        self.actionPlayGame.setHelpText(
            m18nc('kajongg @info:tooltip', 'Start a new game.'))

        self.actionAbortGame.setText(m18nc('@action:inmenu', "&Abort Game"))
        self.actionAbortGame.setPriority(QAction.LowPriority)
        self.actionAbortGame.setHelpText(
            m18nc('kajongg @info:tooltip', 'Abort the current game.'))

        self.actionQuit.setText(m18nc('@action:inmenu', "&Quit Kajongg"))
        self.actionQuit.setPriority(QAction.LowPriority)

        self.actionPlayers.setText(m18nc('@action:intoolbar', "&Players"))
        self.actionPlayers.setHelpText(
            m18nc('kajongg @info:tooltip', 'define your players.'))

        self.actionRulesets.setText(m18nc('@action:intoolbar', "&Rulesets"))
        self.actionRulesets.setHelpText(
            m18nc('kajongg @info:tooltip', 'customize rulesets.'))

        self.actionAngle.setText(
            m18nc('@action:inmenu', "&Change Visual Angle"))
        self.actionAngle.setIconText(m18nc('@action:intoolbar', "Angle"))
        self.actionAngle.setHelpText(
            m18nc('kajongg @info:tooltip',
                  "Change the visual appearance of the tiles."))

        self.actionScoring.setText(
            m18nc('@action:inmenu', "&Show Scoring Editor"))
        self.actionScoring.setIconText(m18nc('@action:intoolbar', "&Scoring"))
        self.actionScoring.setHelpText(
            m18nc('kajongg @info:tooltip',
                  "Show or hide the scoring editor for a manual game."))

        self.actionScoreTable.setText(
            m18nc('kajongg @action:inmenu', "&Score Table"))
        self.actionScoreTable.setIconText(
            m18nc('kajongg @action:intoolbar', "&Scores"))
        self.actionScoreTable.setHelpText(
            m18nc('kajongg @info:tooltip',
                  "Show or hide the score table for the current game."))

        self.actionExplain.setText(m18nc('@action:inmenu', "&Explain Scores"))
        self.actionExplain.setIconText(m18nc('@action:intoolbar', "&Explain"))
        self.actionExplain.setHelpText(
            m18nc('kajongg @info:tooltip',
                  'Explain the scoring for all players in the current game.'))

        self.actionAutoPlay.setText(m18nc('@action:inmenu', "&Demo Mode"))
        self.actionAutoPlay.setPriority(QAction.LowPriority)
        self.actionAutoPlay.setHelpText(
            m18nc(
                'kajongg @info:tooltip',
                'Let the computer take over for you. Start a new local game if needed.'
            ))

        self.actionChat.setText(m18n("C&hat"))
        self.actionChat.setHelpText(
            m18nc('kajongg @info:tooltip', 'Chat with the other players.'))

    def changeEvent(self, event):
        """when the applicationwide language changes, recreate GUI"""
        if event.type() == QEvent.LanguageChange:
            self.setupGUI()
            self.retranslateUi()

    def slotPlayers(self):
        """show the player list"""
        if not self.playerWindow:
            self.playerWindow = PlayerList(self)
        self.playerWindow.show()

    def slotRulesets(self):
        """show the player list"""
        if not self.rulesetWindow:
            self.rulesetWindow = RulesetSelector()
        self.rulesetWindow.show()

    def selectScoringGame(self):
        """show all games, select an existing game or create a new game"""
        Players.load()
        if len(Players.humanNames) < 4:
            logWarning(
                m18n(
                    'Please define four players in <interface>Settings|Players</interface>'
                ))
            return False
        gameSelector = Games(self)
        if gameSelector.exec_():
            selected = gameSelector.selectedGame
            if selected is not None:
                ScoringGame.loadFromDB(selected)
            else:
                self.newGame()
            if self.game:
                self.game.throwDices()
        gameSelector.close()
        self.updateGUI()
        return bool(self.game)

    def scoreGame(self):
        """score a local game"""
        if self.selectScoringGame():
            self.actionScoring.setChecked(True)

    def playGame(self):
        """play a remote game: log into a server and show its tables"""
        self.startingGame = True
        HumanClient()

    def adjustView(self):
        """adjust the view such that exactly the wanted things are displayed
        without having to scroll"""
        if not Internal.scaleScene:
            return
        if self.game:
            with Animated(False):
                self.game.wall.decorate()
                if self.discardBoard:
                    self.discardBoard.maximize()
                if self.selectorBoard:
                    self.selectorBoard.maximize()
                for tile in self.game.wall.tiles:
                    if tile.board:
                        tile.board.placeTile(tile)
        view, scene = self.centralView, self.centralScene
        oldRect = view.sceneRect()
        view.setSceneRect(scene.itemsBoundingRect())
        newRect = view.sceneRect()
        if oldRect != newRect:
            view.fitInView(scene.itemsBoundingRect(), Qt.KeepAspectRatio)

    @property
    def startingGame(self):
        """are we trying to start a game?"""
        return self.__startingGame

    @startingGame.setter
    def startingGame(self, value):
        """are we trying to start a game?"""
        if value != self.__startingGame:
            self.__startingGame = value
            self.updateGUI()

    @property
    def tilesetName(self):
        """the name of the current tileset"""
        return self.tileset.desktopFileName

    @tilesetName.setter
    def tilesetName(self, name):
        """the name of the current tileset"""
        self.tileset = Tileset(name)

    @property
    def backgroundName(self):
        """setting this also actually changes the background"""
        return self.background.desktopFileName if self.background else ''

    @backgroundName.setter
    def backgroundName(self, name):
        """setter for backgroundName"""
        self.background = Background(name)
        self.background.setPalette(self.centralWidget())
        self.centralWidget().setAutoFillBackground(True)

    def applySettings(self):
        """apply preferences"""
        # pylint: disable=R0912
        # too many branches
        self.actionAngle.setEnabled(
            bool(self.game) and Preferences.showShadows)
        animate()  # drain the queue
        afterCurrentAnimationDo(self.__applySettings2)

    def __applySettings2(self, dummyResults):
        """now no animation is running"""
        with Animated(False):
            if self.tilesetName != Preferences.tilesetName:
                self.tilesetName = Preferences.tilesetName
                if self.game:
                    self.game.wall.tileset = self.tileset
                for item in self.centralScene.nonTiles():
                    try:
                        item.tileset = self.tileset
                    except AttributeError:
                        continue
                # change players last because we need the wall already to be repositioned
                self.adjustView()  # the new tiles might be larger
            if self.game:
                for player in self.game.players:
                    if player.handBoard:
                        player.handBoard.rearrangeMelds = Preferences.rearrangeMelds
            if self.backgroundName != Preferences.backgroundName:
                self.backgroundName = Preferences.backgroundName
            if self.showShadows is None or self.showShadows != Preferences.showShadows:
                self.showShadows = Preferences.showShadows
                if self.game:
                    wall = self.game.wall
                    wall.showShadows = self.showShadows
                self.selectorBoard.showShadows = self.showShadows
                if self.discardBoard:
                    self.discardBoard.showShadows = self.showShadows
                for tile in self.centralScene.graphicsTileItems():
                    tile.setClippingFlags()
                self.adjustView()
        Sound.enabled = Preferences.useSounds
        self.centralScene.placeFocusRect()

    def showSettings(self):
        """show preferences dialog. If it already is visible, do nothing"""
        if KConfigDialog.showDialog("settings"):
            return
        # if an animation is running, Qt segfaults somewhere deep
        # in the SVG renderer rendering the wind tiles for the tile
        # preview
        afterCurrentAnimationDo(self.__showSettings2)

    def __showSettings2(self, dummyResult):
        """now that no animation is running, show settings dialog"""
        self.confDialog = ConfigDialog(self, "settings")
        self.confDialog.settingsChanged.connect(self.applySettings)
        self.confDialog.show()

    def newGame(self):
        """asks user for players and ruleset for a new game and returns that new game"""
        Players.load()  # we want to make sure we have the current definitions
        selectDialog = SelectPlayers(self.game)
        if not selectDialog.exec_():
            return
        return ScoringGame(selectDialog.names, selectDialog.cbRuleset.current)

    def __toggleWidget(self, checked):
        """user has toggled widget visibility with an action"""
        action = self.sender()
        actionData = action.data().toPyObject()
        if checked:
            if isinstance(actionData, type):
                actionData = actionData(game=self.game)
                action.setData(QVariant(actionData))
                if isinstance(actionData, ScoringDialog):
                    self.scoringDialog = actionData
                    actionData.btnSave.clicked.connect(self.nextScoringHand)
                    actionData.scoringClosed.connect(self.__scoringClosed)
                elif isinstance(actionData, ExplainView):
                    self.explainView = actionData
                elif isinstance(actionData, ScoreTable):
                    self.scoreTable = actionData
            actionData.show()
            actionData.raise_()
        else:
            assert actionData
            actionData.hide()

    def __toggleDemoMode(self, checked):
        """switch on / off for autoPlay"""
        if self.game:
            self.centralScene.placeFocusRect()  # show/hide it
            self.game.autoPlay = checked
            if checked and self.clientDialog:
                self.clientDialog.proposeAction(
                )  # an illegal action might have focus
                self.clientDialog.selectButton(
                )  # select default, abort timeout
        else:
            Internal.autoPlay = checked
            if checked:
                # TODO: use the last used ruleset. Right now it always takes the first of the list.
                self.playGame()

    def __scoringClosed(self):
        """the scoring window has been closed with ALT-F4 or similar"""
        self.actionScoring.setChecked(False)

    def nextScoringHand(self):
        """save hand to database, update score table and balance in status line, prepare next hand"""
        if self.game.winner:
            for player in self.game.players:
                player.usedDangerousFrom = None
                for ruleBox in player.manualRuleBoxes:
                    rule = ruleBox.rule
                    if rule.name == 'Dangerous Game' and ruleBox.isChecked():
                        self.game.winner.usedDangerousFrom = player
        self.game.saveHand()
        self.game.maybeRotateWinds()
        self.game.prepareHand()
        self.game.initHand()

    def prepareHand(self):
        """redecorate wall"""
        self.updateGUI()
        if self.game:
            self.game.wall.decorate()
        if self.scoringDialog:
            self.scoringDialog.clearLastTileCombo()

    def updateGUI(self):
        """update some actions, all auxiliary windows and the statusbar"""
        if not isAlive(self):
            return
        title = ''
        connections = list(x.connection for x in HumanClient.humanClients
                           if x.connection)
        game = self.game
        if not game:
            title = ', '.join('{name}/{url}'.format(name=x.username, url=x.url)
                              for x in connections)
            if title:
                self.setWindowTitle('%s - Kajongg' % title)
        for action in [self.actionScoreGame, self.actionPlayGame]:
            action.setEnabled(not bool(game))
        self.actionAbortGame.setEnabled(bool(game))
        self.actionAngle.setEnabled(bool(game) and self.showShadows)
        scoring = bool(game and game.isScoringGame())
        self.selectorBoard.setVisible(scoring)
        self.selectorBoard.setEnabled(scoring)
        self.discardBoard.setVisible(bool(game) and not scoring)
        self.actionScoring.setEnabled(scoring and not game.finished())
        self.actionAutoPlay.setEnabled(not self.startingGame and not scoring)
        self.actionChat.setEnabled(
            bool(game) and bool(game.client)
            and not game.client.hasLocalServer() and not self.startingGame)
        # chatting on tables before game started works with chat button per table
        self.actionChat.setChecked(self.actionChat.isEnabled()
                                   and bool(game.client.table.chatWindow))
        if self.actionScoring.isChecked():
            self.actionScoring.setChecked(scoring and not game.finished())
        for view in [self.scoringDialog, self.explainView, self.scoreTable]:
            if view:
                view.refresh(game)
        self.__showBalance()

    def changeAngle(self):
        """change the lightSource"""
        if self.game:
            afterCurrentAnimationDo(self.__changeAngle2)

    def __changeAngle2(self, dummyResult):
        """now that no animation is running, really change"""
        if self.game:  # might be finished meanwhile
            with Animated(False):
                wall = self.game.wall
                oldIdx = LIGHTSOURCES.index(wall.lightSource)  # pylint: disable=E1101
                newLightSource = LIGHTSOURCES[(oldIdx + 1) % 4]
                wall.lightSource = newLightSource
                self.selectorBoard.lightSource = newLightSource
                self.discardBoard.lightSource = newLightSource
                self.adjustView()
                scoringDialog = self.actionScoring.data().toPyObject()
                if isinstance(scoringDialog, ScoringDialog):
                    scoringDialog.computeScores()
                self.centralScene.placeFocusRect()

    def __showBalance(self):
        """show the player balances in the status bar"""
        sBar = self.statusBar()
        if self.game:
            for idx, player in enumerate(self.game.players):
                sbMessage = player.localName + ': ' + str(player.balance)
                if sBar.hasItem(idx):
                    sBar.changeItem(sbMessage, idx)
                else:
                    sBar.insertItem(sbMessage, idx, 1)
                    sBar.setItemAlignment(idx, Qt.AlignLeft)
        else:
            for idx in range(5):
                if sBar.hasItem(idx):
                    sBar.removeItem(idx)

    def computeLastTile(self):
        """compile hand info into a string as needed by the scoring engine"""
        if self.scoringDialog:
            return self.scoringDialog.computeLastTile()

    def computeLastMeld(self):
        """compile hand info into a string as needed by the scoring engine"""
        if self.scoringDialog:
            cbLastMeld = self.scoringDialog.cbLastMeld
            idx = cbLastMeld.currentIndex()
            if idx >= 0:
                return Meld(str(cbLastMeld.itemData(idx).toString()))
        return Meld()

    @staticmethod
    def askSwap(swappers):
        """use this as a proxy such that module game does not have to import playfield.
        Game should also run on a server without KDE being installed"""
        return SwapDialog(swappers).exec_()