From kde-commits Fri Mar 23 08:05:53 2012 From: Wolfgang Rohdewald Date: Fri, 23 Mar 2012 08:05:53 +0000 To: kde-commits Subject: KDE/kdegames Message-Id: <20120323080553.7DB7FAC899 () svn ! kde ! org> X-MARC-Message: https://marc.info/?l=kde-commits&m=133249003421273 SVN commit 1286780 by wrohdewald: players can now chat with each other FEATURE: AM doc/kajongg/chat.png M +31 -0 doc/kajongg/index.docbook M +1 -0 kajongg/CMakeLists.txt A kajongg/src/chat.py M +1 -0 kajongg/src/client.py M +1 -0 kajongg/src/common.py M +27 -1 kajongg/src/humanclient.py M +1 -0 kajongg/src/kajonggui.rc M +43 -1 kajongg/src/message.py M +12 -0 kajongg/src/playfield.py M +25 -1 kajongg/src/server.py M +27 -0 kajongg/src/tables.py --- trunk/KDE/kdegames/doc/kajongg/index.docbook #1286779:1286780 @@ -35,6 +35,8 @@ ]> + + The &kajongg; Handbook @@ -475,6 +477,26 @@ Explains how the score for the current hand was computed. + + + View + Chat + + Chat + + Chat Window + + + + + + Chat Window + + + + Lets you chat with the other players. + + @@ -882,6 +904,15 @@ +&Ctrl;H + + +Show a chat window. + + + + + &Ctrl;G --- trunk/KDE/kdegames/kajongg/CMakeLists.txt #1286779:1286780 @@ -24,6 +24,7 @@ src/background.py src/backgroundselector.py src/board.py +src/chat.py src/handboard.py src/message.py src/client.py --- trunk/KDE/kdegames/kajongg/src/client.py #1286779:1286780 @@ -56,6 +56,7 @@ if myRuleset == self.ruleset: self.myRuleset = myRuleset break + self.chatWindow = None def __str__(self): return 'Table %d %s gameid=%s rules %s players %s online %s' % (self.tableid or 0, --- trunk/KDE/kdegames/kajongg/src/common.py #1286779:1286780 @@ -45,6 +45,7 @@ robbingKong = False mahJongg = False sound = False + chat = False argString = None def __init__(self): --- trunk/KDE/kdegames/kajongg/src/humanclient.py #1286779:1286780 @@ -36,7 +36,8 @@ from util import m18n, m18nc, logWarning, logException, socketName, english, \ appdataDir, logInfo, logDebug, removeIfExists from util import SERVERMARK, isAlive -from message import Message +from message import Message, ChatMessage +from chat import ChatWindow from common import InternalParameters, PREF, Debug from game import Players from query import Transaction, Query @@ -752,6 +753,24 @@ Client.remote_tablesChanged(self, tables) self.tableList.loadTables(self.tables) + def remote_chat(self, data): + """others chat to me""" + chatLine = ChatMessage(data) + if Debug.chat: + logDebug('got chatLine: %s' % chatLine) + if self.table: + table = self.table + else: + table = None + for _ in self.tableList.view.model().tables: + if _.tableid == chatLine.tableid: + table = _ + assert table.tableid == chatLine.tableid + if not chatLine.isStatusMessage: + ChatWindow.createFor(table) + if table.chatWindow: + table.chatWindow.receiveLine(chatLine) + def readyForGameStart(self, tableid, gameid, wantedGame, playerNames, shouldSave=True): """playerNames are in wind order ESWN""" self.tableList.hideForever = True @@ -1095,6 +1114,9 @@ self.readyHandQuestion.hide() if field.clientDialog: field.clientDialog.hide() + if self.table.chatWindow: + self.table.chatWindow.hide() + self.table.chatWindow = None def callServer(self, *args): """if we are online, call server""" @@ -1112,3 +1134,7 @@ self.perspective = None logWarning(m18n('The connection to the server %1 broke, please try again later.', self.url)) + + def sendChat(self, chatLine): + """send chat message to server""" + self.callServer('chat', chatLine.serialize()) --- trunk/KDE/kdegames/kajongg/src/kajonggui.rc #1286779:1286780 @@ -14,6 +14,7 @@ + --- trunk/KDE/kdegames/kajongg/src/message.py #1286779:1286780 @@ -18,7 +18,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ -from util import m18n, m18nc, m18ncE, logWarning, logException, logDebug +import datetime + +from util import m18n, m18nc, m18ncE, logWarning, logException, logDebug, SERVERMARK from sound import Voice, Sound from meld import Meld from common import InternalParameters, Debug @@ -559,4 +561,44 @@ msg = glob() type.__setattr__(Message, msg.name.replace(' ', ''), msg) +class ChatMessage: + """holds relevant info about a chat message""" + def __init__(self, tableid, fromUser=None, message=None, isStatusMessage=False): + if isinstance(tableid, basestring) and SERVERMARK in tableid: + parts = tableid.split(SERVERMARK) + self.tableid = int(parts[0]) + self.timestamp = datetime.time(hour=int(parts[1]), minute=int(parts[2]), second=int(parts[3])) + self.fromUser = parts[4] + self.message = parts[5] + self.isStatusMessage = bool(int(parts[6])) + else: + self.tableid = tableid + self.fromUser = fromUser + self.message = message + self.isStatusMessage = isStatusMessage + self.timestamp = datetime.datetime.utcnow().time() + + def __unicode__(self): + return 'statusMessage=%s %02d:%02d:%02d %s: %s' % ( + str(self.isStatusMessage), + self.timestamp.hour, + self.timestamp.minute, + self.timestamp.second, + self.fromUser, + self.message) + + def __repr__(self): + return unicode(self) + + def serialize(self): + """encode me in a string for network transfer""" + return SERVERMARK.join([ + str(self.tableid), + str(self.timestamp.hour), + str(self.timestamp.minute), + str(self.timestamp.second), + self.fromUser, + self.message, + str(int(self.isStatusMessage))]) + __scanSelf() --- trunk/KDE/kdegames/kajongg/src/playfield.py #1286779:1286780 @@ -81,6 +81,7 @@ from animation import animate, afterCurrentAnimationDo, Animated from player import Player, Players from game import Game, ScoringGame + from chat import ChatWindow except ImportError, e: NOTFOUND.append('kajongg is not correctly installed: modules: %s' % e) @@ -548,6 +549,11 @@ self.actionAbortGame.setEnabled(False) self.actionQuit = self.kajonggAction("quit", "application-exit", self.quit, Qt.Key_Q) self.actionPlayers = self.kajonggAction("players", "im-user", self.slotPlayers) + 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)) + 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) @@ -671,6 +677,7 @@ self.actionScoreTable.setText(m18nc('kajongg', "&Score Table")) self.actionExplain.setText(m18n("&Explain Scores")) self.actionAutoPlay.setText(m18n("&Demo Mode")) + self.actionChat.setText(m18n("C&hat")) def changeEvent(self, event): """when the applicationwide language changes, recreate GUI""" @@ -827,6 +834,9 @@ actionData = action.data().toPyObject() if checked: if isinstance(actionData, type): + if actionData == ChatWindow: + actionData = ChatWindow.createFor(self.game.client.table) + else: actionData = actionData(self.game) action.setData(QVariant(actionData)) if isinstance(actionData, ScoringDialog): @@ -891,6 +901,8 @@ self.discardBoard.setVisible(bool(game) and not scoring) self.actionScoring.setEnabled(scoring and not game.finished()) self.actionAutoPlay.setEnabled(not self.startingGame) + self.actionChat.setEnabled(bool(game) and not self.startingGame) + self.actionChat.setChecked(bool(game) and bool(game.client) 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]: --- trunk/KDE/kdegames/kajongg/src/server.py #1286779:1286780 @@ -54,7 +54,7 @@ from scoringengine import Ruleset from util import m18n, m18nE, m18ncE, logInfo, logDebug, logWarning, SERVERMARK, \ Duration, socketName, logError -from message import Message +from message import Message, ChatMessage from common import elements, Debug from sound import Voice from deferredutil import DeferredBlock @@ -126,6 +126,8 @@ """a table on the game server""" # pylint: disable=R0902 # pylint we need more than 10 instance attributes + # pylint: disable=R0904 + # pylint we have too many public methods def __init__(self, server, owner, rulesetStr, playOpen, autoPlay, wantedGame): self.server = server @@ -176,6 +178,13 @@ result -= sum (x.name.startswith('ROBOT') for x in self.preparedGame.players) return result + def sendChatMessage(self, chatLine): + """sends a chat messages to all clients""" + if Debug.chat: + logDebug('server sends chat msg %s' % chatLine) + for other in self.users: + self.server.callRemote(other, 'chat', chatLine.serialize()) + def addUser(self, user): """add user to this table""" if user.name in list(x.name for x in self.users): @@ -183,6 +192,9 @@ if len(self.users) == self.maxSeats(): raise srvError(pb.Error, m18nE('All seats are already taken')) self.users.append(user) + self.sendChatMessage(ChatMessage(self.tableid, user.name, + m18nE('takes a seat'), isStatusMessage=True)) + if len(self.users) == self.maxSeats(): self.readyForGameStart(self.owner) @@ -191,6 +203,8 @@ if user in self.users: self.game = None self.users.remove(user) + self.sendChatMessage(ChatMessage(self.tableid, user.name, + m18nE('leaves the table'), isStatusMessage=True)) if user is self.owner: # silently pass ownership if self.users: @@ -743,6 +757,13 @@ self.users = list() Players.load() + def chat(self, chatString): + """a client sent us a chat message""" + chatLine = ChatMessage(chatString) + if Debug.chat: + logDebug('server got chat message %s' % chatLine) + self.tables[chatLine.tableid].sendChatMessage(chatLine) + def login(self, user): """accept a new user""" if not user in self.users: @@ -988,6 +1009,9 @@ def perspective_logout(self): """perspective_* methods are to be called remotely""" self.detached(None) + def perspective_chat(self, chatString): + """perspective_* methods are to be called remotely""" + return self.server.chat(chatString) def __str__(self): return '%d:%s' % (id(self) % 10000, self.name) --- trunk/KDE/kdegames/kajongg/src/tables.py #1286779:1286780 @@ -41,6 +41,7 @@ from sound import Voice from common import InternalParameters, Debug, PREF from client import ClientTable +from chat import ChatWindow from modeltest import ModelTest class TablesModel(QAbstractTableModel): @@ -182,6 +183,9 @@ self.compareButton.clicked.connect(self.compareRuleset) self.compareButton.setIcon(KIcon("preferences-plugin-script")) self.compareButton.setToolTip(m18n('Compare the rules of this table with my own rulesets')) + self.chatButton = buttonBox.addButton(m18n('&Chat'), QDialogButtonBox.AcceptRole) + self.chatButton.setIcon(KIcon("call-start")) + self.chatButton.clicked.connect(self.chat) self.startButton = buttonBox.addButton(m18n('&Start'), QDialogButtonBox.AcceptRole) self.startButton.clicked.connect(self.startGame) self.startButton.setIcon(KIcon("arrow-right")) @@ -199,6 +203,10 @@ StateSaver(self, self.view.horizontalHeader()) self.login() + def chat(self): + """chat""" + ChatWindow.createFor(self.selectedTable()) + @apply def hideForever(): # pylint: disable=E0202 """we never want to see this table list for local games, @@ -313,6 +321,12 @@ self.startButton.setEnabled(not suspendedLocalGame and hasTable \ and self.client.username == table.playerNames[0]) self.compareButton.setEnabled(hasTable and table.myRuleset is None) + self.chatButton.setVisible(not self.client.hasLocalServer()) + self.chatButton.setEnabled(self.leaveButton.isEnabled()) + if self.chatButton.isEnabled(): + self.chatButton.setToolTip(m18n("Chat with others on this table")) + else: + self.chatButton.setToolTip(m18n("For chatting with others on this table, please first take a seat")) def selectionChanged(self, selected, dummyDeselected): """update button states according to selection""" @@ -405,6 +419,18 @@ """leave a table""" self.client.callServer('leaveTable', self.selectedTable().tableid) + def __keepChatWindows(self, tables): + """copy chatWindows from the old table list which will be thrown away""" + if self.view.model(): + chatWindows = dict((x.tableid, x.chatWindow) for x in self.view.model().tables) + unusedWindows = set(x.chatWindow for x in self.view.model().tables) + for table in tables: + table.chatWindow = chatWindows.get(table.tableid, None) + unusedWindows -= set([table.chatWindow]) + for unusedWindow in unusedWindows: + if unusedWindow: + unusedWindow.hide() + def loadTables(self, tables): """build and use a model around the tables. Show all new tables (no gameid given yet) and all suspended @@ -418,6 +444,7 @@ elif not table.gameExistsLocally(): logDebug('%s does not exist locally' % table) tables = [x for x in tables if not x.gameid or x.gameExistsLocally()] + self.__keepChatWindows(tables) model = TablesModel(tables) self.view.setModel(model) if Debug.modelTest: