from twisted.internet import reactor
from pyscrabble.net.client import *
from pyscrabble import gtkconstants
from pyscrabble import gtkutil
from pyscrabble.gui.info import InfoWindow
from pyscrabble.gui import message
from pyscrabble import util
from pyscrabble import manager
from pyscrabble.lookup import *
import pygtk
import gtk
import pango
import gobject
class ChatFrame(gtk.Frame):
'''
ChatFrame.
This class displays the Chat window where all users on a server congregate.
'''
def __init__(self, client, main):
'''
Initialize the ChatFrame
@param client: ScrabbleClient instance
@param main: MainWindow instance
@see: L{pyscrabble.net.client.ScrabbleClient}
@see: L{pyscrabble.gui.main.MainWindow}
'''
gtk.Frame.__init__(self)
self.client = client
self.client.setChatWindow( self )
self.mainwindow = main
self.connect("realize", self.onRealize_cb)
main = gtk.VBox( False, 10)
self.messageWindowOpen = False
self.serverInfoWindow = None
main.pack_start( self.createChatWindow(), True, True, 0 )
main.pack_start( self.createEntryWindow(), False, False, 0 )
self.tips = gtk.Tooltips()
self.set_border_width( 10 )
self.add( main )
self.show_all()
self.client.checkMessages()
def onRealize_cb(self, widget):
'''
Realize the window
@param: widget
'''
self.entry.grab_focus()
self.set_focus_chain([self.entry])
def createChatWindow(self):
'''
Create the chat TextView and User view
@return: gtk.HBox containg main chat window and user treeview
'''
sizer = gtk.HBox( False, 10 )
self.chat = gtkutil.TaggableTextView(buffer=None)
self.chat.set_editable( False )
self.chat.set_cursor_visible( False )
self.chat.set_wrap_mode( gtk.WRAP_WORD )
window = gtk.ScrolledWindow()
window.add( self.chat )
window.set_policy( gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC )
sizer.pack_start( window, True, True, 0 )
sizer.pack_start( self.createUsersWindow(), False, False, 0 )
sizer.pack_start(self.createGamesWindow(), False, False, 0)
return sizer
def createEntryWindow(self):
'''
Create the chat entry window
@return: gtk.HBox with chat entry window.
'''
sizer = gtk.HBox( False, 10 )
self.entry = gtk.Entry()
self.entry.connect("key-press-event", self.submitChat)
self.entry.set_flags ( gtk.CAN_FOCUS )
self.entry.grab_focus()
sizer.pack_start(self.entry, True, True, 0)
sizer.set_focus_child(self.entry)
return sizer
def createUsersWindow(self):
'''
Create TreeView of all users on the server
@return: gtk.VBox containing TreeView of all users on the server.
'''
vbox = gtk.VBox(False, 1)
self.userList = gtk.ListStore(str)
self.client.getUserList()
self.userView = gtk.TreeView( gtk.TreeModelSort(self.userList) )
self.userView.connect("button-release-event", self.mainwindow.userListClicked_cb)
self.userView.set_headers_clickable(True)
col = gtk.TreeViewColumn(_('Users'))
cell = gtk.CellRendererText()
col.pack_start(cell, True)
col.add_attribute(cell, 'text', 0)
col.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
col.set_sort_column_id(0)
header = gtk.Label()
s = _('All Users')
header.set_markup("%s:" % s)
header.set_justify( gtk.JUSTIFY_LEFT )
self.userView.append_column( col )
window = gtk.ScrolledWindow()
window.set_policy( gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC )
window.add( self.userView )
vbox.pack_start(header, False, False, 1)
vbox.pack_start(window, True, True, 0)
return vbox
def createGamesWindow(self):
'''
Create TreeView of all Games on the server.
@return: gtk.VBox containing a TreeView of all Games on the server.
'''
vbox = gtk.VBox(False, 1)
self.gameList = gtk.TreeStore(str, str, str)
self.client.getGameList()
self.gameView = gtk.TreeView( gtk.TreeModelSort(self.gameList) )
self.gameView.set_headers_clickable(True)
self.gameView.connect("button-press-event", self.gameListClicked_cb)
title = _('Game')
col1 = gtk.TreeViewColumn('%s' % title)
col1.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
cell1 = gtk.CellRendererText()
col1.pack_start(cell1, True)
col1.add_attribute(cell1, 'text', 0)
col1.set_sort_column_id(0)
#cell1.set_property('wrap-width', 50)
#cell1.set_property('wrap-mode', pango.WRAP_WORD)
title = _('Players')
col2 = gtk.TreeViewColumn('# %s' % title)
cell2 = gtk.CellRendererText()
col2.pack_start(cell2, True)
col2.add_attribute(cell2, 'text', 1)
col2.set_sort_column_id(1)
title = _('Status')
col3 = gtk.TreeViewColumn('%s' % title)
cell3 = gtk.CellRendererText()
col3.pack_start(cell3, True)
col3.add_attribute(cell3, 'text', 2)
col3.set_sort_column_id(2)
self.gameView.append_column( col1 )
self.gameView.append_column( col2 )
self.gameView.append_column( col3 )
header = gtk.Label()
title = _('Game Listing')
header.set_markup("%s:" % title)
header.set_justify( gtk.JUSTIFY_LEFT )
bbox = gtk.HButtonBox()
self.joinButton = gtk.Button(label=_("Join Game"))
self.spectateButton = gtk.Button(label=_("Watch Game"))
self.spectateButton.connect("clicked", self.spectateGame)
self.joinButton.connect("clicked", self.joinGame_cb)
bbox.add(self.joinButton)
bbox.add(self.spectateButton)
window = gtk.ScrolledWindow()
window.set_policy( gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC )
window.add( self.gameView )
vbox.pack_start(header, False, False, 1)
vbox.pack_start(window, True, True, 0)
vbox.pack_start(bbox, False, False, 0)
return vbox
def error(self, data, enableButtons=False):
'''
Show error dialog.
@param data: ErrorMessage data
@see: L{util.ErrorMessage}
'''
if enableButtons:
self.setGameButtonsState(True)
e = _("Error")
self.dialog = gtk.MessageDialog(parent=self.mainwindow, type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_OK, message_format="")
self.dialog.set_markup("%s: %s" % (e, data.getErrorMessage()))
self.dialog.connect("response", lambda w,e: self.dialog.destroy())
self.dialog.show()
self.dialog.run()
def submitChat(self, widget, event, data=None):
'''
Submit chat message to server
@param widget:
@param event:
@param data:
'''
if (event.keyval == gtk.keysyms.Return):
if (self.entry.get_text() != None and len(self.entry.get_text()) > 0):
self.client.postChatMessage( self.entry.get_text() )
self.entry.set_text( '' )
return True
return False
def refreshUserList(self, users):
'''
Callback from ScrabbleClient.
Refresh the list of users in the user TreeView
@param users: List of users currently on the server
'''
self.userList.clear()
for player in users:
self.userList.append( [player.getUsername()] )
def userJoinChat(self, user):
'''
Callback from ScrabbleClient when another user joins the chat room
@param user: User joining chat
'''
self.userList.append( [user] )
def receiveChatMessage(self, msg):
'''
Callback from ScrabbleClient when a chat message is posted.
@param msg: Chat text to post in buffer
'''
txt,server = msg # Server will be true if its a Server message
if server:
buf = self.chat.get_buffer()
t = buf.create_tag(weight=pango.WEIGHT_BOLD)
self.chat.insert_text_with_tags(txt, t)
else:
self.chat.insert_text(txt)
self.mainwindow.notifyChatMessage()
# Join a game
def joinGame_cb(self, button):
'''
Callback when 'Join Game' button is clicked. Send request to the server to join the game.
@param button: Button that was clicked to call this handler.
'''
self.setGameButtonsState(False)
sel = self.gameView.get_selection()
model, iter = sel.get_selected()
if (iter == None):
self.error(util.ErrorMessage(_("Please select a game to join.")), True)
return
# If the user clicks on one of the sub items, we need to get the root iter, which is the gameId
parent = model.iter_parent(iter)
if parent is not None:
iter = parent
gameName = model.get(iter, 0)[0]
self.joinGame( gameName )
def joinGame(self, gameName):
'''
Attempt to join a game
@param gameName: Game name
'''
if (self.mainwindow.hasJoinedGame(gameName)):
self.error(util.ErrorMessage(_("You have already joined that game.")), True)
return
else:
self.client.joinGame(gameName)
# Show dialog to create a new game
def createGameWindow(self, button = None):
'''
Show the dialog to create a new game
@param button: Widget that was clicked to activate this handler.
'''
s = _("Create New Game")
self.gamedialog = gtk.Dialog(title="%s" %s , flags=gtk.DIALOG_MODAL)
self.gamedialog.vbox.set_spacing( 10 )
self.gamedialog.vbox.set_border_width( 5 )
self.gamedialog.vbox.set_homogeneous( False )
#self.gamedialog.set_default_size(500,400)
main = gtk.VBox(False, 5)
header = gtk.Label()
header.set_markup("%s:" %s)
main.pack_start( header )
s = _("Game Name")
self.createGameEntry = gtkutil.EntryWithLabel(label="%s: " % s)
main.pack_start( self.createGameEntry )
s = _("Options")
main.pack_start( gtk.Label("%s:" % s) )
centerOption = gtk.CheckButton(_("Center square is double word score"))
centerOption.set_active(True)
rankedOption = gtk.CheckButton(_("Official Game"))
rankedOption.set_active(True)
showCountOption = gtk.CheckButton(_("Show letter distribution"))
showCountOption.set_active(True)
timeBox = gtk.HBox(False, 2)
limitBox = gtk.HBox(False, 2)
moveTimeBox = gtk.HBox(False, 2)
limitOption = gtk.CheckButton( _('Optional Overtime Limit') )
untimedOption = gtk.RadioButton(None, _('Untimed') )
timedOption = gtk.RadioButton(untimedOption, _('Timed Game') )
moveTimeOption = gtk.RadioButton(untimedOption, _('Timed Moves') )
a = gtk.Adjustment(value=25, lower=1, upper=100, step_incr=1, page_incr=1, page_size=1)
timeControl = gtk.SpinButton(a, climb_rate=1, digits=0)
timeBox.pack_start(timedOption, False, False, 0)
timeBox.pack_start(timeControl, False, False, 5)
timedOption.set_active(False)
timedOption.connect("toggled", lambda w, a, b: [x.set_sensitive(w.get_active()) for x in (a,b)], timeControl, limitBox)
timedOption.connect("toggled", lambda w, a: a.set_active(w.get_active()), limitOption)
timeControl.set_sensitive(False)
a = gtk.Adjustment(value=1, lower=1, upper=100, step_incr=1, page_incr=1, page_size=1)
limitControl = gtk.SpinButton(a, climb_rate=1, digits=0)
limitBox.pack_start(limitOption, False, False, 20)
limitBox.pack_start(limitControl, False, False, 5)
limitOption.connect("toggled", lambda w, a: a.set_sensitive(w.get_active()), limitControl)
limitBox.set_sensitive(False)
self.tips.set_tip(rankedOption, _('If this game is Official, the outcome will be marked in your statistics'))
self.tips.set_tip(limitOption, _('If you set the optional overtime limit, a player will be allowed to go over the normal game time, but the player will be penalized 10 points for every overtime minute'))
self.tips.set_tip(timedOption, _('Select this option if you wish to limit the overall time each player has for the entire game'))
self.tips.set_tip(moveTimeOption, _('Select this option if you wish to limit the time a player has for each move'))
self.tips.set_tip(showCountOption, _('Select this option if you wish to show a count of the letters remaining in the bag during the game'))
a = gtk.Adjustment(value=3, lower=1, upper=100, step_incr=1, page_incr=1, page_size=1)
moveTimeControl = gtk.SpinButton(a, climb_rate=1, digits=0)
moveTimeBox.pack_start(moveTimeOption, False, False, 0)
moveTimeBox.pack_start(moveTimeControl, False, False, 5)
moveTimeOption.connect("toggled", lambda w, a: a.set_sensitive(w.get_active()), moveTimeControl)
moveTimeControl.set_sensitive(False)
box = gtk.VBox(False, 5)
box.pack_start(untimedOption, False, False, 0)
box.pack_start(timeBox, False, False, 0)
box.pack_start(limitBox, False, False, 0)
box.pack_start(moveTimeBox, False, False, 0)
exp = gtk.Expander( _('Timing Options') )
exp.add(box)
exp.set_spacing(10)
main.pack_start( centerOption, False, False, 3 )
main.pack_start( rankedOption, False, False, 3 )
main.pack_start( showCountOption, False, False, 3 )
box = gtk.HBox(False, 3)
box.pack_start(gtk.Label(_('Rules:')), False, False, 3)
model = gtk.ListStore(str,str)
l = manager.LocaleManager()
locales = l.getAvailableLocales()
for locale in locales:
model.append( [ _(l.getLocaleDescription(locale)), locale] )
cell = gtk.CellRendererText()
combo = gtk.ComboBox(model)
combo.pack_start(cell)
combo.add_attribute(cell, 'text', 0)
cur = None
for locale in locales:
if l.locale == locale:
cur = locale
if cur is not None:
i = model.get_iter_first()
while i:
if model.get_value(i, 1) == cur:
combo.set_active_iter(i)
break
i = model.iter_next(i)
else:
combo.set_active(0)
box.pack_start(combo, False, False, 0)
main.pack_start( box, False, False, 3 )
okbutton = gtk.Button(_("Create"))
cancelbutton = gtk.Button(_("Cancel"))
self.gamedialog.vbox.pack_start(main, False, False, 0)
self.gamedialog.vbox.pack_start(exp, False, False, 0)
self.gamedialog.action_area.pack_start(okbutton)
self.gamedialog.action_area.pack_start(cancelbutton)
okbutton.connect("clicked", self.createGame, centerOption, rankedOption, showCountOption, combo, timedOption, timeControl, limitOption, limitControl, moveTimeOption, moveTimeControl)
cancelbutton.connect("clicked", lambda b: self.gamedialog.destroy() )
self.gamedialog.show_all()
self.gamedialog.run()
# Create a game
def createGame(self, button, centerOption, rankedOption, showCountOption, combo, timedOption, timeControl, limitOption, limitControl, moveTimeOption, moveTimeControl):
'''
Create a game
@param button: Widget that was clicked to activate this handler.
@param centerOption: Option widget
@param rankedOption: Option widget
param showCountOption: Option widget
@param combo: Rules widget
@param timedOption: Time option
@param timeControl: Time control widget
@param limitOption: Time limit option
@param limitControl: Time limit control
@param moveTimeOption: Move time option
@param moveTimeControl: Move time control
'''
gameId = self.createGameEntry.get_text()
if len(gameId) == 0:
self.error(util.ErrorMessage(_("Please enter a Game ID of at least one character.")))
return
model = combo.get_model()
iter = combo.get_active_iter()
opt = model.get_value(iter, 1)
options = {}
options[OPTION_CENTER_TILE] = centerOption.get_active()
options[OPTION_SHOW_COUNT] = showCountOption.get_active()
options[OPTION_RANKED] = rankedOption.get_active()
options[OPTION_RULES] = opt
if timedOption.get_active():
options[OPTION_TIMED_GAME] = long(timeControl.get_value_as_int())
if limitOption.get_active():
options[OPTION_TIMED_LIMIT] = long(limitControl.get_value_as_int())
if moveTimeOption.get_active():
options[OPTION_MOVE_TIME] = long(moveTimeControl.get_value_as_int())
self.gamedialog.destroy()
self.client.createGame( gameId, options )
# Show/Refresh game list
def showGameList(self, games):
'''
Callback from Scrabble Client when a game is added or removed from the Game Listing
@param games: List of ScrabbleGameInfo
@see: L{pyscrabble.game.game.ScrabbleGameInfo}
'''
self.gameList.clear()
for game in games:
iter = self.gameList.append(None, (game.getName(), str(game.getNumberOfPlayers()), game.getStatus()) )
for key,value in game.options:
if not isinstance(value,str):
value = repr(value)
self.gameList.append(iter, (repr(key), value, ""))
# Set the current game ID
def newGame(self, gameId, spectating, options):
'''
Notify the main window to create a new game tab
@param gameId: Game ID
@param spectating: True if the user is watching the game
@param options: Game options dict
'''
self.setGameButtonsState(True)
self.mainwindow.newGame( gameId, spectating, options )
# Change password dialog
def changePasswordDialog(self, button = None):
'''
Show dialog to change password
@param button: Widget that was clicked to activate this handler.
'''
s = _("Change Password")
changepasswordDialog = gtk.Dialog(title="%s" % s, flags=gtk.DIALOG_MODAL)
changepasswordDialog.vbox.set_border_width( 5 )
header = gtk.Label()
header.set_markup("%s:" % s)
changepasswordDialog.vbox.pack_start(header)
s = _("Old Password")
oldpassword = gtkutil.EntryWithLabel(label="%s: " % s, visibility=False)
changepasswordDialog.vbox.pack_start( oldpassword )
s = _("Password")
password1 = gtkutil.EntryWithLabel(label="%s: " % s, visibility=False)
changepasswordDialog.vbox.pack_start( password1 )
s = _("Confirm Password")
password2 = gtkutil.EntryWithLabel(label="%s: " % s, visibility=False)
changepasswordDialog.vbox.pack_start( password2 )
okbutton = gtk.Button(_("Change"))
cancelbutton = gtk.Button(_("Cancel"))
changepasswordDialog.action_area.pack_start(okbutton)
changepasswordDialog.action_area.pack_start(cancelbutton)
okbutton.connect("clicked", self.changePassword, oldpassword, password1, password2, changepasswordDialog)
cancelbutton.connect("clicked", lambda b: changepasswordDialog.destroy() )
changepasswordDialog.show_all()
# Change password
def changePassword(self, button, oldpassword, password1, password2, dialog):
'''
Ask server to change password
@param button: Widget that was clicked to activate this handler
@param oldpassword: Old password widget
@param password1: New Password widget
@param password2: New Password Confirmation widget
@param dialog: Change Password dialog widget
'''
if password1.get_text() != password2.get_text():
self.error(util.ErrorMessage(_("Passwords don't match.")))
return
self.client.changePassword(util.hashPassword(oldpassword.get_text()), util.hashPassword(password1.get_text()))
dialog.destroy()
def sendPrivateMessage(self, username, data):
self.mainwindow.sendPrivateMessage(widget=None, username=username, data=data)
def spectateGame(self, button):
'''
Callback when 'Watch Game' button is clicked. Send request to the server to watch the game.
@param button: Button that was clicked to call this handler.
'''
self.setGameButtonsState(False)
sel = self.gameView.get_selection()
model, iter = sel.get_selected()
if (iter == None):
self.error(util.ErrorMessage(_("Please select a game to join.")),True)
return
gameName = model.get(iter, 0)[0]
if (self.mainwindow.hasJoinedGame(gameName)):
self.error(util.ErrorMessage(_("You have already joined that game.")),True)
return
else:
self.client.spectateGame(gameName)
def hasFocus(self, widget=None, event=None):
'''
Callback that the window has focus
Give focus to the chat entry
'''
self.entry.grab_focus()
def infoWindow(self, data):
'''
Show Information dialog
@param data: Information text
'''
s = _("Info")
self.dialog = gtk.MessageDialog(parent=None, type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_OK, message_format="")
self.dialog.set_title("%s" % s)
self.dialog.set_markup("%s: %s" % (s,data))
self.dialog.connect("response", lambda w,e: self.dialog.destroy())
self.dialog.show()
self.dialog.run()
def requestUserInfo(self, widget, username):
'''
Request user information
@param widget: Widget that activated this callback
@param username: Username
'''
self.client.requestUserInfo(username)
def showUserInfo(self, user):
'''
Show user information
@param user: User object
'''
data = []
data.append( (_("Wins"), str(user.getNumericStat(STAT_WINS))) )
data.append( (_("Losses"), str(user.getNumericStat(STAT_LOSSES))) )
data.append( (_("Ties"), str(user.getNumericStat(STAT_TIES))) )
data.append( (_("Rank"), user.rankName) )
data.append( (_("Member since"), str(user.getCreatedDate())) )
data.append( (_('Last login'), str(user.getLastLoginDate())) )
data.append( (_('Status'), str(user.status)) )
s = _('User Information')
x = InfoWindow(title='%s - %s' % (s, user.getUsername()), header=s, main=None)
x.initialize()
x.appendPage(gtk.Label(user.getUsername()), [_('Name'),_('Value')],data,visible=False)
x.show_all()
def requestServerStats(self, widget):
'''
Request server stats
@param widget: Widget that activated this callback
'''
if self.serverInfoWindow is None:
s = _('Server Information')
self.serverInfoWindow = InfoWindow(title=s, header=s, main=self)
reactor.callLater(0.5, self.client.requestServerStats)
def showServerStats(self, stats):
'''
Show server stats
@param stats: Stats
'''
stats,users,ranks = stats
cols = [_('Name'), _('Value')]
stts = []
for key,val in stats:
stts.append( (repr(key), val) ) #key is a ServerMessage, call repr to display val
self.serverInfoWindow.initialize()
self.serverInfoWindow.appendPage(gtk.Label(_("Statistics")), cols, stts, visible=False)
cols = [_('Name'),_('Wins'),_('Losses'),_('Ties'),_('Rank')]
data = []
for user in users:
data.append( (user.getUsername(), int(user.getNumericStat(STAT_WINS)), int(user.getNumericStat(STAT_LOSSES)), int(user.getNumericStat(STAT_TIES)), user.rankName) )
self.serverInfoWindow.appendPage(gtk.Label(_("Users")), cols, data, visible=True, sortable=True, signals={ "button-release-event" : self.mainwindow.userListClicked_cb })
self.serverInfoWindow.appendPage(gtk.Label(_("Rankings")), [_('Rank'),_('Wins Required')], ranks, visible=True)
self.serverInfoWindow.show_all()
def serverInfoClosed_cb(self):
'''
Callback that the server info window has been closed
'''
self.serverInfoWindow = None
def showOfflineMessages(self, messages):
'''
Show offline messages
@param messages: List of PrivateMessages
'''
if len(messages) > 0:
self.messageWindowOpen = True
x = message.OfflineMessageWindow(self,messages)
else:
s = _('You do not have any messages')
self.infoWindow(s)
def messageWindowClosed(self):
'''
Notify this widget that the message window is closed
'''
self.messageWindowOpen = False
def getMessages_cb(self, widget):
'''
Check offline messages
@param widget: Widget that activated this callback
'''
if not self.messageWindowOpen:
self.client.getMessages()
def deleteMessage(self, id):
'''
Delete message
@param id: Message ID
'''
self.client.deleteMessage(id)
def setGameButtonsState(self, flag):
'''
Set game buttons to be sensitive
@param flag: Sensitive flag
'''
self.joinButton.set_sensitive(flag)
self.spectateButton.set_sensitive(flag)
def gameListClicked_cb(self, widget, event):
'''
Callback for when the gamelist is clicked. If its a double click, attempt to join the selected game
@param widget:
@param event:
'''
gameName = gtkutil.getSelectedItem(widget, 0)
if event.type == gtk.gdk._2BUTTON_PRESS and gameName is not None:
self.joinGame( gameName )