diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py
index bd806f9fd..29b27e479 100644
--- a/src/common/connection_handlers.py
+++ b/src/common/connection_handlers.py
@@ -1593,7 +1593,6 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
self.dispatch('FAILED_DECRYPT', (frm, tim))
msgtxt = msg.getBody()
- msghtml = msg.getXHTML()
subject = msg.getSubject() # if not there, it's None
tim = msg.getTimestamp()
@@ -1612,19 +1611,15 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
frm = address.getAttr('jid')
jid = gajim.get_jid_without_resource(frm)
- encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED)
# invitations
invite = None
+ encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED)
if not encTag:
invite = msg.getTag('x', namespace = common.xmpp.NS_MUC_USER)
if invite and not invite.getTag('invite'):
invite = None
- delayed = msg.getTag('x', namespace = common.xmpp.NS_DELAY) != None
- msg_id = None
- composing_xep = None
-
# FIXME: Msn transport (CMSN1.2.1 and PyMSN0.10) do NOT RECOMMENDED
# invitation
# stanza (MUC XEP) remove in 2007, as we do not do NOT RECOMMENDED
@@ -1639,37 +1634,6 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
is_continued))
return
- form_node = None
- for xtag in xtags:
- if xtag.getNamespace() == common.xmpp.NS_DATA:
- form_node = xtag
- break
-
- chatstate = None
-
- # chatstates - look for chatstate tags in a message if not delayed
- if not delayed:
- composing_xep = False
- children = msg.getChildren()
- for child in children:
- if child.getNamespace() == 'http://jabber.org/protocol/chatstates':
- chatstate = child.getName()
- composing_xep = 'XEP-0085'
- break
- # No XEP-0085 support, fallback to XEP-0022
- if not chatstate:
- chatstate_child = msg.getTag('x', namespace = common.xmpp.NS_EVENT)
- if chatstate_child:
- chatstate = 'active'
- composing_xep = 'XEP-0022'
- if not msgtxt and chatstate_child.getTag('composing'):
- chatstate = 'composing'
-
- # XEP-0172 User Nickname
- user_nick = msg.getTagData('nick')
- if not user_nick:
- user_nick = ''
-
if encTag and self.USE_GPG:
encmsg = encTag.getData()
@@ -1683,44 +1647,16 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
if mtype == 'error':
self.dispatch_error_message(msg, msgtxt, session, frm, tim, subject)
-
- return
elif mtype == 'groupchat':
- self.dispatch_gc_message(msg, subject, frm, msgtxt, jid, tim, msghtml)
-
- return
+ self.dispatch_gc_message(msg, subject, frm, msgtxt, jid, tim)
elif invite is not None:
self.dispatch_invite_message(invite, frm)
-
- return
- elif mtype == 'chat':
- if not msg.getTag('body') and chatstate is None: # no
- return
-
- log_type = 'chat_msg_recv'
- else: # it's a single message
- log_type = 'single_msg_recv'
-
- mtype = 'normal'
-
- if session.is_loggable() and msgtxt:
- try:
- msg_id = gajim.logger.write(log_type, frm, msgtxt,
- tim = tim, subject = subject)
- except exceptions.PysqliteOperationalError, e:
- self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
-
- treat_as = gajim.config.get('treat_incoming_messages')
-
- if treat_as:
- mtype = treat_as
-
- # XXX horrible hack
- if isinstance(session, ChatControlSession):
- session.received(frm, msgtxt, tim, encrypted, mtype, subject, chatstate,
- msg_id, composing_xep, user_nick, msghtml, form_node)
else:
- session.received(msg)
+ # XXX horrible hack
+ if isinstance(session, ChatControlSession):
+ session.received(frm, msgtxt, tim, encrypted, subject, msg)
+ else:
+ session.received(msg)
# END messageCB
# process and dispatch an error message
@@ -1741,7 +1677,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
self.dispatch('MSGERROR', (frm, msg.getErrorCode(), error_msg, msgtxt, tim))
# process and dispatch a groupchat message
- def dispatch_gc_message(self, msg, subject, frm, msgtxt, jid, tim, msghtml):
+ def dispatch_gc_message(self, msg, subject, frm, msgtxt, jid, tim):
has_timestamp = bool(msg.timestamp)
if subject != None:
@@ -1761,7 +1697,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
if not self.last_history_line.has_key(jid):
return
- self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp, msghtml,
+ self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp, msg.getXHTML(),
statusCode))
no_log_for = gajim.config.get_per('accounts', self.name,
diff --git a/src/session.py b/src/session.py
index 643c7eaf7..ea38ee682 100644
--- a/src/session.py
+++ b/src/session.py
@@ -1,9 +1,12 @@
from common import helpers
+from common import exceptions
from common import gajim
from common import stanza_session
from common import contacts
+import common.xmpp
+
import dialogs
import message_control
@@ -16,8 +19,70 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
self.control = None
+ # extracts chatstate from a stanza
+ def get_chatstate(self, msg, msgtxt):
+ composing_xep = None
+ chatstate = None
+
+ # chatstates - look for chatstate tags in a message if not delayed
+ delayed = msg.getTag('x', namespace=common.xmpp.NS_DELAY) != None
+ if not delayed:
+ composing_xep = False
+ children = msg.getChildren()
+ for child in children:
+ if child.getNamespace() == 'http://jabber.org/protocol/chatstates':
+ chatstate = child.getName()
+ composing_xep = 'XEP-0085'
+ break
+ # No XEP-0085 support, fallback to XEP-0022
+ if not chatstate:
+ chatstate_child = msg.getTag('x', namespace = common.xmpp.NS_EVENT)
+ if chatstate_child:
+ chatstate = 'active'
+ composing_xep = 'XEP-0022'
+ if not msgtxt and chatstate_child.getTag('composing'):
+ chatstate = 'composing'
+
+ return (composing_xep, chatstate)
+
# dispatch a received stanza
- def received(self, full_jid_with_resource, message, tim, encrypted, msg_type, subject, chatstate, msg_id, composing_xep, user_nick, xhtml, form_node):
+ def received(self, full_jid_with_resource, msgtxt, tim, encrypted, subject, msg):
+ msg_type = msg.getType()
+ msg_id = None
+
+ # XEP-0172 User Nickname
+ user_nick = msg.getTagData('nick')
+ if not user_nick:
+ user_nick =''
+
+ form_node = None
+ for xtag in msg.getTags('x'):
+ if xtag.getNamespace() == common.xmpp.NS_DATA:
+ form_node = xtag
+ break
+
+ composing_xep, chatstate = self.get_chatstate(msg, msgtxt)
+
+ xhtml = msg.getXHTML()
+
+ if msg_type == 'chat':
+ if not msg.getTag('body') and chatstate is None:
+ return
+
+ log_type = 'chat_msg_recv'
+ else:
+ log_type = 'single_msg_recv'
+
+ if self.is_loggable() and msgtxt:
+ try:
+ msg_id = gajim.logger.write(log_type, full_jid_with_resource, msgtxt,
+ tim=tim, subject=subject)
+ except exceptions.PysqliteOperationalError, e:
+ gajim.dispatch('ERROR', (_('Disk WriteError'), str(e)))
+
+ treat_as = gajim.config.get('treat_incoming_messages')
+ if treat_as:
+ msg_type = treat_as
jid = gajim.get_jid_without_resource(full_jid_with_resource)
resource = gajim.get_resource_from_jid(full_jid_with_resource)
@@ -65,7 +130,7 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
# THIS MUST BE AFTER chatstates handling
# AND BEFORE playsound (else we ear sounding on chatstates!)
- if not message: # empty message text
+ if not msgtxt: # empty message text
return
if gajim.config.get('ignore_unknown_contacts') and \
@@ -86,16 +151,16 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
if pm:
nickname = resource
- groupchat_control.on_private_message(nickname, message, array[2],
+ groupchat_control.on_private_message(nickname, msgtxt, array[2],
xhtml, session, msg_id)
else:
- self.roster_message(jid, message, tim, encrypted, msg_type,
+ self.roster_message(jid, msgtxt, tim, encrypted, msg_type,
subject, resource, msg_id, user_nick, advanced_notif_num,
xhtml=xhtml, form_node=form_node)
nickname = gajim.get_name_from_jid(self.conn.name, jid)
# Check and do wanted notifications
- msg = message
+ msg = msgtxt
if subject:
msg = _('Subject: %s') % subject + '\n' + msg
focused = False
@@ -111,7 +176,7 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
if gajim.interface.remote_ctrl:
gajim.interface.remote_ctrl.raise_signal('NewMessage',
- (self.conn.name, [full_jid_with_resource, message, tim,
+ (self.conn.name, [full_jid_with_resource, msgtxt, tim,
encrypted, msg_type, subject, chatstate, msg_id,
composing_xep, user_nick, xhtml, form_node]))
diff --git a/src/tictactoe.py b/src/tictactoe.py
index 4477b1b10..5bb20ea1b 100644
--- a/src/tictactoe.py
+++ b/src/tictactoe.py
@@ -9,6 +9,8 @@ import cairo
# implements
+games_ns = 'http://jabber.org/protocol/games'
+
class InvalidMove(Exception):
pass
@@ -24,13 +26,19 @@ class TicTacToeSession(stanza_session.StanzaSession):
else:
self.role_o = 'x'
+ self.send_invitation()
+
+ self.next_move_id = 1
+ self.received = self.wait_for_invite_response
+
+ def send_invitation(self):
msg = xmpp.Message()
invite = msg.NT.invite
- invite.setNamespace('http://jabber.org/protocol/games')
+ invite.setNamespace(games_ns)
game = invite.NT.game
- game.setAttr('var', 'http://jabber.org/protocol/games/tictactoe')
+ game.setAttr('var', games_ns + '/tictactoe')
x = xmpp.DataForm(typ='submit')
@@ -38,12 +46,8 @@ class TicTacToeSession(stanza_session.StanzaSession):
self.send(msg)
- self.next_move_id = 1
- self.state = 'sent_invite'
-
- # received an invitation
- def invited(self, msg):
- invite = msg.getTag('invite', namespace='http://jabber.org/protocol/games')
+ def read_invitation(self, msg):
+ invite = msg.getTag('invite', namespace=games_ns)
game = invite.getTag('game')
x = game.getTag('x', namespace='jabber:x:data')
@@ -51,37 +55,47 @@ class TicTacToeSession(stanza_session.StanzaSession):
if form.getField('role'):
self.role_o = form.getField('role').getValues()[0]
+ else:
+ self.role_o = 'x'
if form.getField('rows'):
self.rows = int(form.getField('rows').getValues()[0])
+ else:
+ self.rows = 3
if form.getField('cols'):
self.cols = int(form.getField('cols').getValues()[0])
-
- # XXX 'strike'
-
- if not hasattr(self, 'rows'):
- self.rows = 3
-
- if not hasattr(self, 'cols'):
+ else:
self.cols = 3
+ if form.getField('strike'):
+ self.strike = int(form.getField('strike').getValues()[0])
+ else:
+ self.strike = 3
+
+ # received an invitation
+ def invited(self, msg):
+ self.read_invitation(msg)
+
+ # XXX prompt user
+ # "accept, reject, ignore"
+
# the number of the move about to be made
self.next_move_id = 1
+ # display the board
self.board = TicTacToeBoard(self, self.rows, self.cols)
# accept the invitation, join the game
response = xmpp.Message()
join = response.NT.join
- join.setNamespace('http://jabber.org/protocol/games')
+ join.setNamespace(games_ns)
self.send(response)
- if not hasattr(self, 'role_o') or self.role_o == 'x':
+ if self.role_o == 'x':
self.role_s = 'o'
- self.role_o = 'x'
self.their_turn()
else:
@@ -91,27 +105,28 @@ class TicTacToeSession(stanza_session.StanzaSession):
self.our_turn()
def is_my_turn(self):
- return self.state == 'get_input'
+ # XXX not great semantics
+ return self.received == self.ignore
- def received(self, msg):
- # just sent an invitation, expecting a reply
- if self.state == 'sent_invite':
- if msg.getTag('join', namespace='http://jabber.org/protocol/games'):
- self.board = TicTacToeBoard(self, self.rows, self.cols)
+ # just sent an invitation, expecting a reply
+ def wait_for_invite_response(self, msg):
+ if msg.getTag('join', namespace=games_ns):
+ self.board = TicTacToeBoard(self, self.rows, self.cols)
- if self.role_s == 'x':
- self.our_turn()
- else:
- self.their_turn()
+ if self.role_s == 'x':
+ self.our_turn()
+ else:
+ self.their_turn()
- return
+ elif msg.getTag('decline', namespace=games_ns):
+ self.XXX()
- # ignore messages unless we're expecting a move
- if self.state != 'waiting':
- return
-
- turn = msg.getTag('turn', namespace='http://jabber.org/protocol/games')
+ # silently ignores any received messages
+ def ignore(self, msg):
+ pass
+ def wait_for_move(self, msg):
+ turn = msg.getTag('turn', namespace=games_ns)
move = turn.getTag('move', namespace='http://jabber.org/protocol/games/tictactoe')
row = int(move.getAttr('row'))
@@ -128,40 +143,54 @@ class TicTacToeSession(stanza_session.StanzaSession):
print 'received invalid move'
return
- # XXX check win conditions
+ # check win conditions
+ if self.board.check_for_strike(self.role_o, row, col, self.strike):
+ self.lost()
+ elif self.board.full():
+ self.drawn()
+ else:
+ self.next_move_id += 1
- self.next_move_id += 1
-
- self.our_turn()
+ self.our_turn()
def our_turn(self):
- self.state = 'get_input'
+ # ignore messages until we've made our move
+ self.received = self.ignore
self.board.win.set_title(self.board.title + ': your turn')
def their_turn(self):
- self.state = 'waiting'
+ self.received = self.wait_for_move
self.board.win.set_title(self.board.title + ': their turn')
# called when the board receives input
- def move(self, row, column):
+ def move(self, row, col):
try:
- self.board.mark(row, column, self.role_s)
+ self.board.mark(row, col, self.role_s)
except InvalidMove, e:
- print 'invalid move'
+ print 'you made an invalid move'
return
- self.send_move(row, column)
+ self.send_move(row, col)
- # XXX check win conditions
+ # check win conditions
+ if self.board.check_for_strike(self.role_s, row, col,self.strike):
+ self.won()
+ elif self.board.full():
+ self.drawn()
+ else:
+ self.next_move_id += 1
+
+ self.their_turn()
def send_move(self, row, column):
msg = xmpp.Message()
+ msg.setType('chat')
turn = msg.NT.turn
- turn.setNamespace('http://jabber.org/protocol/games')
+ turn.setNamespace(games_ns)
move = turn.NT.move
- move.setNamespace('http://jabber.org/protocol/games/tictactoe')
+ move.setNamespace(games_ns+'/tictactoe')
move.setAttr('row', str(row))
move.setAttr('col', str(column))
@@ -169,11 +198,49 @@ class TicTacToeSession(stanza_session.StanzaSession):
self.send(msg)
- self.next_move_id += 1
-
- self.their_turn()
-
class TicTacToeBoard:
+ def check_for_strike(self, p, r, c, strike):
+ # up and down, left and right
+ tallyI = 0
+ tally_ = 0
+
+ # right triangles: L\ , F/
+ tallyL = 0
+ tallyF = 0
+
+ # convert real columns to internal columns
+ r -= 1
+ c -= 1
+
+ for d in xrange(-strike, strike):
+ # vertical check
+ try:
+ tallyI = tallyI + 1 if self.board[r+d][c] == p else 0
+ except IndexError:
+ pass
+
+ # horizontal check
+ try:
+ tally_ = tally_ + 1 if self.board[r][c+d] == p else 0
+ except IndexError:
+ pass
+
+ # diagonal checks
+ try:
+ tallyL = tallyL + 1 if self.board[r+d][c+d] == p else 0
+ except IndexError:
+ pass
+
+ try:
+ tallyF = tallyF + 1 if self.board[r+d][c-d] == p else 0
+ except IndexError:
+ pass
+
+ if any([t == strike for t in (tallyL, tallyF, tallyI, tally_)]):
+ return True
+
+ return False
+
def __init__(self, session, rows, cols):
self.session = session
@@ -184,6 +251,15 @@ class TicTacToeBoard:
self.setup_window()
+ # is the board full?
+ def full(self):
+ for r in xrange(self.rows):
+ for c in xrange(self.cols):
+ if self.board[r][c] == None:
+ return False
+
+ return True
+
def setup_window(self):
self.win = gtk.Window()
@@ -214,6 +290,7 @@ class TicTacToeBoard:
self.session.move(row, column)
+ # this actually draws the board
def expose(self, widget, event):
win = widget.window