def decorateWindow(window, name=None): """standard Kajongg window title and icon""" if name: window.setWindowTitle('{} – {}'.format(name, i18n('Kajongg'))) else: window.setWindowTitle(i18n('Kajongg')) window.setWindowIcon(KIcon('kajongg'))
def showMoveHelper(self, visible=None): """show help text In empty HandBoards""" if visible is None: visible = not self.uiTiles if self.__moveHelper and not isAlive(self.__moveHelper): return if visible: if not self.__moveHelper: splitter = QGraphicsRectItem(self) hbCenter = self.rect().center() splitter.setRect(hbCenter.x() * 0.5, hbCenter.y(), hbCenter.x() * 1, 1) helpItems = [splitter] for name, yFactor in [(i18n('Move Exposed Tiles Here'), 0.5), (i18n('Move Concealed Tiles Here'), 1.5) ]: helper = QGraphicsSimpleTextItem(name, self) helper.setScale(3) nameRect = QRectF() nameRect.setSize( helper.mapToParent( helper.boundingRect()).boundingRect().size()) center = QPointF(hbCenter) center.setY(center.y() * yFactor) helper.setPos(center - nameRect.center()) if sceneRotation(self) == 180: rotateCenter(helper, 180) helpItems.append(helper) self.__moveHelper = self.scene().createItemGroup(helpItems) self.__moveHelper.setVisible(True) else: if self.__moveHelper: self.__moveHelper.setVisible(False)
def __init__(self, swappers): QMessageBox.__init__(self) decorateWindow(self, i18n("Swap Seats")) self.setText( i18n("By the rules, %1 and %2 should now exchange their seats. ", swappers[0].name, swappers[1].name)) self.yesAnswer = QPushButton(i18n("&Exchange")) self.addButton(self.yesAnswer, QMessageBox.YesRole) self.noAnswer = QPushButton(i18n("&Keep seat")) self.addButton(self.noAnswer, QMessageBox.NoRole)
def __computeMd5sum(self): """update md5sum file. If it changed, return True. If unchanged or no ogg files exist, remove archive and md5sum and return False. If ogg files exist but no archive, return True.""" if self.__md5sum: # we already checked return md5FileName = os.path.join(self.directory, 'md5sum') archiveExists = os.path.exists(self.archiveName()) ogg = self.oggFiles() if not ogg: removeIfExists(self.archiveName()) removeIfExists(md5FileName) self.__md5sum = None logDebug('no ogg files in %s' % self) return md5sum = md5() for oggFile in ogg: md5sum.update( open(os.path.join(self.directory, oggFile), 'rb').read()) # the md5 stamp goes into the old archive directory 'username' self.__md5sum = md5sum.hexdigest() existingMd5sum = self.savedmd5Sum() md5Name = self.md5FileName() if self.__md5sum != existingMd5sum: if Debug.sound: if not os.path.exists(md5Name): logDebug('creating new %s' % md5Name) else: logDebug('md5sum %s changed, rewriting %s with %s' % (existingMd5sum, md5Name, self.__md5sum)) try: open(md5Name, 'w').write('%s\n' % self.__md5sum) except BaseException as exception: logException('\n'.join([ i18n('cannot write <filename>%1</filename>: %2', md5Name, str(exception)), i18n( 'The voice files have changed, their checksum has changed.' ), i18n( 'Please reinstall kajongg or do, with sufficient permissions:' ), 'cd {} ; cat *.ogg | md5sum > md5sum'.format( self.directory) ])) if archiveExists: archiveIsOlder = os.path.getmtime(md5Name) > os.path.getmtime( self.archiveName()) if self.__md5sum != existingMd5sum or archiveIsOlder: os.remove(self.archiveName())
def tableChanged(self, table): """update table list""" oldTable, newTable = Client.tableChanged(self, table) if oldTable and oldTable == self.table: # this happens if a table has more than one human player and # one of them leaves the table. In that case, the other players # need this code. self.table = newTable if len(newTable.playerNames) == 3: # only tell about the first player leaving, because the # others will then automatically leave too for name in oldTable.playerNames: if name != self.name and not newTable.isOnline(name): def sorried(dummy): """user ack""" game = self.game if game: self.game = None return game.close() if self.beginQuestion: self.beginQuestion.cancel() Sorry(i18n('Player %1 has left the table', name)).addCallback( sorried).addCallback(self.showTableList) break self.__updateTableList()
def _saveScores(self): """save computed values to database, update score table and balance in status line""" scoretime = datetime.datetime.now().replace(microsecond=0).isoformat() logMessage = '' for player in self.players: if player.hand: manualrules = '||'.join(x.rule.name for x in player.hand.usedRules) else: manualrules = i18n('Score computed manually') Query( "INSERT INTO SCORE " "(game,hand,data,manualrules,player,scoretime,won,prevailing," "wind,points,payments, balance,rotated,notrotated) " "VALUES(%d,%d,?,?,%d,'%s',%d,'%s','%s',%d,%d,%d,%d,%d)" % (self.gameid, self.handctr, player.nameid, scoretime, int(player == self.__winner), self.roundWind.char, player.wind, player.handTotal, player.payment, player.balance, self.rotated, self.notRotated), (player.hand.string, manualrules)) logMessage += '{player:<12} {hand:>4} {total:>5} {won} | '.format( player=str(player)[:12], hand=player.handTotal, total=player.balance, won='WON' if player == self.winner else ' ') for usedRule in player.hand.usedRules: rule = usedRule.rule if rule.score.limits: self.addCsvTag(rule.name.replace(' ', '')) if Debug.scores: self.debug(logMessage)
def remote_tableRemoved(self, tableid, message, *args): """update table list""" Client.remote_tableRemoved(self, tableid, message, *args) self.__updateTableList() if message: if self.name not in args or not message.endswith('has logged out'): logWarning(i18n(message, *args))
def pixmap(self, size): """returns a background pixmap or None for isPlain""" self.__pmap = QBrush() if not self.isPlain: width = size.width() height = size.height() if self.tiled: width = self.imageWidth height = self.imageHeight cachekey = '{name}W{width}H{height}'.format(name=self.name, width=width, height=height) self.__pmap = QPixmapCache.find(cachekey) if not self.__pmap: renderer = QSvgRenderer(self.graphicsPath) if not renderer.isValid(): logException( i18n( 'file <filename>%1</filename> contains no valid SVG', self.graphicsPath)) self.__pmap = QPixmap(width, height) self.__pmap.fill(Qt.transparent) painter = QPainter(self.__pmap) renderer.render(painter) QPixmapCache.insert(cachekey, self.__pmap) return self.__pmap
def __getName(playerid): """get name for playerid """ try: return Players.allNames[playerid] except KeyError: return i18n('Player %1 not known', playerid)
def __init__(self): SelectRuleset.__init__(self) Players.load() decorateWindow(self, i18n('Select four players')) self.names = None self.nameWidgets = [] for idx, wind in enumerate(Wind.all4): cbName = QComboBox() cbName.manualSelect = False # increase width, we want to see the full window title cbName.setMinimumWidth(350) # is this good for all platforms? cbName.addItems(list(Players.humanNames.values())) self.grid.addWidget(cbName, idx + 1, 1) self.nameWidgets.append(cbName) self.grid.addWidget(WindLabel(wind), idx + 1, 0) cbName.currentIndexChanged.connect(self.slotValidate) query = Query( "select p0,p1,p2,p3 from game where seed is null and game.id = (select max(id) from game)" ) if len(query.records): with BlockSignals(self.nameWidgets): for cbName, playerId in zip(self.nameWidgets, query.records[0]): try: playerName = Players.humanNames[playerId] playerIdx = cbName.findText(playerName) if playerIdx >= 0: cbName.setCurrentIndex(playerIdx) except KeyError: logError( 'database is inconsistent: player with id %d is in game but not in player' % playerId) self.slotValidate()
def headerData(self, section, orientation, role=Qt.DisplayRole): # pylint: disable=no-self-use """show header""" if role == Qt.TextAlignmentRole: if orientation == Qt.Horizontal: if section == 1: return int(Qt.AlignRight) else: return int(Qt.AlignLeft) if orientation != Qt.Horizontal: return int(section + 1) if role != Qt.DisplayRole: return result = '' if section < self.columnCount(): result = [i18n('Time'), i18n('Player'), i18n('Message')][section] return result
def __init__(self, scene=None, table=None): super(ChatWindow, self).__init__(None) self.scene = scene self.table = table or scene.game.client.table self.table.chatWindow = self self.setObjectName('chatWindow') title = i18n('Chat on table %1 at %2', self.table.tableid, self.table.client.connection.url) decorateWindow(self, title) self.messageView = ChatView() self.messageView.setModel(ChatModel()) self.messageView.setFocusPolicy(Qt.NoFocus) self.messageView.setShowGrid(False) self.messageView.setWordWrap(False) self.messageView.setSelectionMode(QAbstractItemView.NoSelection) if Debug.modelTest: self.debugModelTest = ModelTest(self.messageView.model(), self.messageView) self.edit = QLineEdit() layout = QVBoxLayout() layout.addWidget(self.messageView) layout.addWidget(self.edit) self.setLayout(layout) self.edit.returnPressed.connect(self.sendLine) self.edit.setFocus() self.show() StateSaver(self)
def remote_abort(self, tableid, message: str, *args): """the server aborted this game""" if self.table and self.table.tableid == tableid: # translate Robot to Roboter: if self.game: args = self.game.players.translatePlayerNames(args) logWarning(i18n(message, *args)) if self.game: self.game.close()
def remote_gameOver(self, tableid, message, *args): """the game is over""" def yes(dummy): """now that the user clicked the 'game over' prompt away, clean up""" if self.game: self.game.rotateWinds() self.game.close().addCallback(Internal.mainWindow.close) assert self.table and self.table.tableid == tableid if Internal.scene: # update the balances in the status bar: Internal.scene.mainWindow.updateGUI() Information(i18n(message, *args)).addCallback(yes)
def readyForGameStart( self, tableid, gameid, wantedGame, playerNames, shouldSave=True, gameClass=None): """playerNames are in wind order ESWN""" if gameClass is None: if Options.gui: gameClass = VisiblePlayingGame else: gameClass = PlayingGame def clientReady(): """macro""" return Client.readyForGameStart( self, tableid, gameid, wantedGame, playerNames, shouldSave, gameClass) def answered(result): """callback, called after the client player said yes or no""" self.beginQuestion = None if self.connection and result: # still connected and yes, we are return clientReady() else: return Message.NoGameStart def cancelled(dummy): """the user does not want to start now. Back to table list""" if Debug.table: logDebug('%s: Readyforgamestart returns Message.NoGameStart for table %s' % ( self.name, self._tableById(tableid))) self.table = None self.beginQuestion = None if self.tableList: self.__updateTableList() self.tableList.show() return Message.NoGameStart if sum(not x[1].startswith('Robot ') for x in playerNames) == 1: # we play against 3 robots and we already told the server to start: # no need to ask again return clientReady() assert not self.table assert self.tables self.table = self._tableById(tableid) if not self.table: raise pb.Error( 'client.readyForGameStart: tableid %d unknown' % tableid) msg = i18n( "The game on table <numid>%1</numid> can begin. Are you ready to play now?", tableid) self.beginQuestion = QuestionYesNo(msg, modal=False, caption=self.name).addCallback( answered).addErrback(cancelled) return self.beginQuestion
def readyForHandStart(self, playerNames, rotateWinds): """playerNames are in wind order ESWN. Never called for first hand.""" def answered(dummy=None): """called after the client player said yes, I am ready""" if self.connection: return Client.readyForHandStart(self, playerNames, rotateWinds) if not self.connection: # disconnected meanwhile return if Options.gui: # update the balances in the status bar: Internal.mainWindow.updateGUI() assert not self.game.isFirstHand() return Information(i18n("Ready for next hand?"), modal=False).addCallback(answered)
def _endWallDangerous(self): """if end of living wall is reached, declare all invisible tiles as dangerous""" if len(self.wall.living) <= 5: allTiles = [ x for x in defaultdict.keys(elements.occurrence) if not x.isBonus ] for tile in allTiles: assert isinstance(tile, Tile), tile # see https://www.logilab.org/ticket/23986 invisibleTiles = set(x for x in allTiles if x not in self.visibleTiles) msg = i18n('Short living wall: Tile is invisible, hence dangerous') self.dangerousTiles = list(x for x in self.dangerousTiles if x[1] != msg) self.dangerousTiles.append((invisibleTiles, msg))
def __init__(self, client, parent=None): QDialog.__init__(self, parent) decorateWindow(self, i18n('Choose')) self.setObjectName('ClientDialog') self.client = client self.layout = QGridLayout(self) self.progressBar = QProgressBar() self.progressBar.setMinimumHeight(25) self.timer = QTimer() if not client.game.autoPlay: self.timer.timeout.connect(self.timeout) self.deferred = None self.buttons = [] self.setWindowFlags(Qt.SubWindow | Qt.WindowStaysOnTopHint) self.setModal(False) self.btnHeight = 0 self.answered = False self.move = None self.sorry = None
def __init__(self, kongs, deferred): KDialogIgnoringEscape.__init__(self) decorateWindow(self) self.setButtons(0) self.kongs = kongs self.selectedKong = None self.deferred = deferred layout = QVBoxLayout() label = QLabel(i18n('Which kong do you want to declare?')) layout.addWidget(label) layout.setAlignment(label, Qt.AlignHCenter) self.buttons = [] for kong in kongs: button = QRadioButton((kong[0].name()), self) self.buttons.append(button) layout.addWidget(button) button.toggled.connect(self.toggled) widget = QWidget(self) widget.setLayout(layout) self.setMainWidget(widget)
def scoreGame(): """show all games, select an existing game or create a new game""" Players.load() if len(Players.humanNames) < 4: logWarning( i18n( 'Please define four players in <interface>Settings|Players</interface>' )) return gameSelector = Games(Internal.mainWindow) selected = None if not gameSelector.exec_(): return selected = gameSelector.selectedGame gameSelector.close() if selected is not None: return ScoringGame.loadFromDB(selected) else: selectDialog = SelectPlayers() if not selectDialog.exec_(): return return ScoringGame(list(zip(Wind.all4, selectDialog.names)), selectDialog.cbRuleset.current)
def selectButton(self, button=None): """select default answer. button may also be of type Message.""" if self.answered: # sometimes we get this event twice return if button is None: button = self.focusWidget() if isinstance(button, Message): assert any(x.message == button for x in self.buttons) answer = button else: answer = button.message if not self.client.game.myself.sayable[answer]: self.proposeAction().setFocus() # go back to default action self.sorry = Sorry(i18n('You cannot say %1', answer.i18nName)) return self.timer.stop() self.answered = True if self.sorry: self.sorry.cancel() self.sorry = None Internal.scene.clientDialog = None self.deferred.callback(answer)
def findOggBinary(): """sets __oggBinary to exe name or an empty string""" if Sound.__oggBinary is None: if os.name == 'nt': Sound.__oggBinary = os.path.join('share', 'kajongg', 'voices', 'oggdec.exe') msg = '' # we bundle oggdec.exe with the kajongg installer, it must be there else: oggBinary = 'ogg123' msg = i18n( 'No voices will be heard because the program %1 is missing', oggBinary) if which(oggBinary): Sound.__oggBinary = oggBinary else: Sound.__oggBinary = '' Internal.Preferences.useSounds = False # checks again at next reenable if msg: logWarning(msg) if Debug.sound: logDebug('ogg123 found:' + Sound.__oggBinary) return Sound.__oggBinary
def data(self, index, role=Qt.DisplayRole): """score table""" result = None if role == Qt.TextAlignmentRole: if index.column() == 1: return int(Qt.AlignRight) else: return int(Qt.AlignLeft) if index.isValid() and (0 <= index.row() < len(self.chatLines)): chatLine = self.chatLines[index.row()] if role == Qt.DisplayRole and index.column() == 0: local = chatLine.localtimestamp() result = '%02d:%02d:%02d' % (local.hour, local.minute, local.second) elif role == Qt.DisplayRole and index.column() == 1: result = chatLine.fromUser elif role == Qt.DisplayRole and index.column() == 2: result = i18n(chatLine.message) elif role == Qt.ForegroundRole and index.column() == 2: palette = Internal.app.palette() # pylint: disable=no-member color = 'blue' if chatLine.isStatusMessage else palette.windowText( ) result = QColor(color) return result
def __init__(self, chows, propose, deferred): KDialogIgnoringEscape.__init__(self) decorateWindow(self) self.setButtons(KDialog.NoButton) self.chows = chows self.selectedChow = None self.deferred = deferred layout = QVBoxLayout() label = QLabel(i18n('Which chow do you want to expose?')) layout.addWidget(label) layout.setAlignment(label, Qt.AlignHCenter) self.buttons = [] for chow in chows: button = QRadioButton('{}-{}-{}'.format(*(x.value for x in chow))) self.buttons.append(button) layout.addWidget(button) layout.setAlignment(button, Qt.AlignHCenter) button.toggled.connect(self.toggled) widget = QWidget(self) widget.setLayout(layout) self.setMainWidget(widget) for idx, chow in enumerate(chows): if chow == propose: self.buttons[idx].setFocus()
def callServer(self, *args): """if we are online, call server""" if self.connection: if args[0] is None: args = args[1:] try: if Debug.traffic: self.__logCallServer(*args) def callServerError(result): """if serverDisconnected has been called meanwhile, just ignore msg about connection lost in a non-clean fashion""" if self.connection: return result return self.connection.perspective.callRemote(*args).addErrback(callServerError) except pb.DeadReferenceError: logWarning( i18n( 'The connection to the server %1 broke, please try again later.', self.connection.url)) self.remote_serverDisconnects() return succeed(None) else: return succeed(None)
def items(self, items): """combo box items""" self.clear() if items: for item in items: self.addItem(i18n(item.name), item)