PageRenderTime 30ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/progenitus/client/gui.py

https://github.com/TheGurke/Progenitus
Python | 1176 lines | 1168 code | 6 blank | 2 comment | 3 complexity | 19488fb1ea1109b4aefc63c557d1de51 MD5 | raw file
  1. # Written by TheGurke 2011
  2. """GUI for the client program"""
  3. import random
  4. import math
  5. import os
  6. import re
  7. import datetime
  8. from gettext import gettext as _
  9. import logging
  10. import gtk
  11. import glib
  12. from progenitus import *
  13. from progenitus.db import cards
  14. from progenitus.db import semantics
  15. from progenitus.editor import decks
  16. import muc
  17. import network
  18. import game
  19. import replay
  20. import players
  21. import desktop
  22. class Interface(uiloader.Interface):
  23. isfullscreen = False
  24. _deck_load_async_handle = None
  25. _browser_cardlist = None
  26. _last_untapped = []
  27. _entrybar_task = "" # current task for the entry bar
  28. my_player = None # this client's player
  29. network_manager = None # the network manager instance
  30. game = None # the currently joined game
  31. solitaire = False # playing in solitaire mode?
  32. replay = None # Replay currently watching
  33. replay_speed = 0 # current replay speed
  34. players = [] # all players
  35. users = dict() # all users that joined the chat room
  36. def __init__(self, solitaire):
  37. super(self.__class__, self).__init__()
  38. self.load(config.GTKBUILDER_CLIENT)
  39. self.main_win.set_title(config.APP_NAME_CLIENT)
  40. self.main_win.maximize()
  41. glib.idle_add(cards.connect)
  42. self.label_version.set_text(config.VERSION)
  43. # Insert a CairoDesktop
  44. self.cd = desktop.CairoDesktop(self, self.eventbox)
  45. self.eventbox.add(self.cd)
  46. self.hscale_zoom.set_value(2) # sync initial zoom level
  47. self.zoom_change()
  48. # Set CairoDesktop callbacks
  49. self.cd.prop_callback = self.call_properties
  50. self.cd.hover_callback = self.hover
  51. # Change entrybar color
  52. # style.bg[gtk.STATE_NORMAL] = gtk.gdk.Color(1, 0, 0)
  53. # for st in (gtk.STATE_NORMAL, gtk.STATE_INSENSITIVE,
  54. # gtk.STATE_PRELIGHT, gtk.STATE_SELECTED, gtk.STATE_ACTIVE):
  55. # color = gtk.gdk.Color(0, 34251, 0)
  56. # self.hbox_entrybar.modify_bg(st, color)
  57. # Check if running in solitaire mode
  58. self.solitaire = solitaire
  59. if solitaire:
  60. self.solitaire_mode()
  61. self.button_leave_game.set_sensitive(False)
  62. else:
  63. self.network_manager = network.NetworkManager()
  64. self.network_manager.logger.log_callback = (lambda text:
  65. self._add_to_log(self.logview_game, text))
  66. self.network_manager.exception_handler = self.handle_exception
  67. # Set default login entries
  68. self.entry_username.set_text(settings.username)
  69. self.entry_pwd.set_text(settings.userpwd)
  70. self.checkbutton_save_pwd.set_active(settings.userpwd != "")
  71. self.entry_server.set_text(settings.server)
  72. self.entry_gamename.set_text(settings.gamename)
  73. self.entry_gamepwd.set_text(settings.gamepwd)
  74. if settings.username == "":
  75. glib.idle_add(self.entry_username.grab_focus)
  76. elif settings.userpwd == "":
  77. glib.idle_add(self.entry_pwd.grab_focus)
  78. else:
  79. glib.idle_add(self.button_login.grab_focus)
  80. # Initialize tokens
  81. self.init_counters_autocomplete()
  82. glib.idle_add(self.init_token_autocomplete)
  83. def show_about(self, widget):
  84. """Display information about this program"""
  85. dialog = gtk.AboutDialog()
  86. dialog.set_name(config.APP_NAME_EDITOR)
  87. dialog.set_version(str(config.VERSION))
  88. dialog.set_copyright(_("Copyright by TheGurke 2011"))
  89. dialog.set_website(config.APP_WEBSITE)
  90. dialog.set_comments(_("This program is Free Software by the GPL3."))
  91. dialog.run()
  92. dialog.destroy()
  93. def init_token_autocomplete(self):
  94. """Load the tokens into the autocompleting combobox"""
  95. cards.load_tokens()
  96. for token in cards.tokens:
  97. if token.power != "":
  98. desc = "%s %s/%s (%s)" % (token.subtype, token.power,
  99. token.toughness, token.setname)
  100. else:
  101. desc = "%s (%s)" % (token.subtype, token.setname)
  102. self.liststore_tokens.append((token.id, desc, token.setname,
  103. token.releasedate))
  104. # Complete the entry_tokens widget
  105. completion = gtk.EntryCompletion()
  106. completion.set_model(self.liststore_tokens)
  107. completion.set_text_column(1)
  108. completion.set_inline_completion(True)
  109. completion.set_minimum_key_length(2)
  110. completion.connect("match-selected", self.token_autocomplete_pick)
  111. self.entry_tokens.set_completion(completion)
  112. def init_counters_autocomplete(self):
  113. completion = gtk.EntryCompletion()
  114. completion.set_model(self.liststore_counters)
  115. completion.set_text_column(0)
  116. completion.set_inline_completion(False)
  117. completion.connect("match-selected", self.update_counter_num)
  118. self.entry_counters.set_completion(completion)
  119. # Replay
  120. def _show_replay(self, filename):
  121. """Go into show replay mode"""
  122. try:
  123. self.replay = replay.read_from_file(filename)
  124. except Exception as e:
  125. logging.error((_("Error while loading %s: ") % filename) + str(e))
  126. self.show_exception(e)
  127. return
  128. self.label_gamename.set_text(
  129. self.replay.room[len(config.DEFAULT_GAME_PREFIX):])
  130. self.hbox_replay.show()
  131. self.notebook.set_current_page(2)
  132. self.hpaned_game.set_sensitive(True)
  133. self.entry_chat.hide()
  134. self.hscale_replay.get_adjustment().set_upper(
  135. self.replay.get_length().total_seconds())
  136. self.hscale_replay.set_value(0)
  137. self._update_clock()
  138. self.replay_speed = 0
  139. glib.timeout_add(30, self._replay_tick)
  140. self.replay.replay_cmds = self._incoming_cmds
  141. self.replay.replay_chat = self.add_chat_line
  142. def _replay_tick(self):
  143. """Regular update function for emulating movie-like replays"""
  144. if not hasattr(self, "_replay_seek_to"):
  145. self._replay_seek_to = self.replay.get_start_time()
  146. self._replay_last_tick = datetime.datetime.now()
  147. return True
  148. now = datetime.datetime.now()
  149. dt = now - self._replay_last_tick
  150. self._replay_last_tick = now
  151. self._replay_seek_to += self.replay_speed * dt
  152. self.replay.replay_to(self._replay_seek_to, self.players,
  153. self.create_player)
  154. self.hscale_replay.get_adjustment().set_value(
  155. self.replay.get_elapsed_time().total_seconds())
  156. self._update_clock()
  157. return True
  158. def _update_clock(self):
  159. """Update the elapsed time display"""
  160. t = int(self.hscale_replay.get_adjustment().get_value())
  161. if t <= 3600:
  162. self.label_replay.set_text(_("%d:%02d") % (t / 60, t % 60))
  163. else:
  164. self.label_replay.set_text(_("%d:%02d:%02d")
  165. % (t / 3600, (t % 3600) / 60, t % 60))
  166. def play_replay(self, *args):
  167. """Start playing the replay"""
  168. if self.replay_speed == 0:
  169. self.replay_speed = 1
  170. else:
  171. self.replay_speed *= 2
  172. if self.replay_speed >= 16:
  173. self.replay_speed = 1
  174. logging.info(_("Set replay speed to %dx.") % self.replay_speed)
  175. def pause_replay(self, *args):
  176. """Pause playing the replay"""
  177. self.replay_speed = 0
  178. logging.info(_("Replay paused."))
  179. def seek_replay(self, *args):
  180. """Seek the replay"""
  181. t = int(self.hscale_replay.get_adjustment().get_value())
  182. seek_to = self.replay.get_start_time() + datetime.timedelta(seconds=t)
  183. self.replay.replay_to(seek_to, self.players, self.create_player)
  184. self._update_clock()
  185. # Network methods
  186. def solitaire_mode(self, *args):
  187. """Go into solitaire mode"""
  188. self.solitaire = True
  189. self.label_gamename.set_text(_("Solitaire game"))
  190. self.hpaned_game.set_position(0)
  191. self.hpaned_game.set_property("position-set", True)
  192. self.notebook.set_current_page(2)
  193. self.hpaned_game.set_sensitive(True)
  194. self.my_player = self.create_player(None, "", config.VERSION)
  195. glib.idle_add(self.my_player.create_tray, None, (0.8, 0.8, 1.0))
  196. def show_replay(self, *args):
  197. """Open a replay file"""
  198. dialog = gtk.FileChooserDialog(_("Load a replay..."),
  199. self.main_win, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL,
  200. gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))
  201. dialog.set_default_response(gtk.RESPONSE_OK)
  202. dialog.set_current_folder(settings.replay_dir)
  203. # Set filename filters
  204. f = gtk.FileFilter()
  205. f.set_name(_("Replays"))
  206. f.add_pattern("*.replay")
  207. dialog.add_filter(f)
  208. f = gtk.FileFilter()
  209. f.set_name(_("All files"))
  210. f.add_pattern("*")
  211. dialog.add_filter(f)
  212. response = dialog.run()
  213. if response == gtk.RESPONSE_OK:
  214. self._show_replay(dialog.get_filename())
  215. dialog.destroy()
  216. def start_connecting(self, widget):
  217. """Login to the jabber account"""
  218. username = self.entry_username.get_text()
  219. pwd = self.entry_pwd.get_text()
  220. self.server = self.entry_server.get_text()
  221. # Save login details to settings
  222. settings.username = username
  223. settings.userpwd = pwd if self.checkbutton_save_pwd.get_active() else ""
  224. settings.server = self.server
  225. settings.save()
  226. # Set interface
  227. self.hbox_login_status.show()
  228. self.spinner_login.start()
  229. for widget in (self.entry_username, self.entry_pwd, self.entry_server,
  230. self.checkbutton_save_pwd, self.hbuttonbox_login
  231. ):
  232. widget.set_sensitive(False)
  233. self.label_servername.set_text(self.server)
  234. # Connect
  235. logging.info(_("Started connecting to '%s'."), self.server)
  236. self.network_manager.connect(username, pwd)
  237. self.network_manager.client.connection_established = self._join_lobby
  238. def _join_lobby(self):
  239. """Join the lobby room"""
  240. logging.info(_("Joining lobby on '%s'..."), self.server)
  241. nick = self.network_manager.get_my_jid().user
  242. room = "%s@conference.%s" % (config.LOBBY_ROOM, self.server)
  243. self.lobby = muc.Room(self.network_manager.client, room, None, nick)
  244. self.lobby.joined = self._show_lobby
  245. self.lobby.muc_message = self._incoming_lobby_chat
  246. self.lobby.user_joined = (lambda jid, role: self._add_to_log(
  247. self.logview_lobby, _("%s joined the lobby.") % jid.resource))
  248. self.lobby.user_left = (lambda jid: self._add_to_log(
  249. self.logview_lobby, _("%s left the lobby.") % jid.resource))
  250. glib.idle_add(self.lobby.join)
  251. glib.idle_add(self.refresh_game_list)
  252. def _show_lobby(self):
  253. self.hbox_login_status.hide()
  254. self.spinner_login.stop()
  255. self.notebook.set_current_page(1)
  256. self.hpaned_lobby.set_sensitive(True)
  257. logging.info(_("Connection established."))
  258. self.entry_chat_lobby.grab_focus()
  259. def join_game(self, widget):
  260. """Clicked on join game"""
  261. gamename = (config.DEFAULT_GAME_PREFIX + self.entry_gamename.get_text()
  262. + "@conference." + self.server)
  263. self.label_gamename.set_text("%s@%s" %
  264. (self.entry_gamename.get_text(), self.entry_server.get_text()))
  265. gamepwd = self.entry_gamepwd.get_text()
  266. self._join_game(gamename, gamepwd)
  267. def _join_game(self, gamename, gamepwd=""):
  268. """Join a game room"""
  269. settings.gamename = self.entry_gamename.get_text()
  270. settings.gamepwd = gamepwd
  271. settings.save()
  272. self.notebook.set_current_page(2)
  273. nick = self.network_manager.get_my_jid().user
  274. self.game = game.join(self.network_manager, gamename, gamepwd, nick)
  275. self.game.joined = self._game_joined
  276. self.game.incoming_commands = self._incoming_cmds
  277. self.game.incoming_chat = self.add_chat_line
  278. self.game.user_joined = self.user_joined
  279. self.game.user_left = self.user_left
  280. self.game.user_nick_changed = self.user_nick_changed
  281. def _game_joined(self):
  282. """A game room has sucessfully been joined"""
  283. logging.info(_("Game '%s' joined successfully."), self.game.jid)
  284. self.hpaned_game.set_sensitive(True)
  285. # Create player
  286. jid = self.game.get_my_jid()
  287. self.my_player = self.create_player(self.game, jid, config.VERSION)
  288. self.my_player.send_network_cmds([("hello", (config.VERSION,))])
  289. # Create tray
  290. glib.timeout_add(
  291. config.JOIN_DELAY,
  292. self.my_player.create_tray,
  293. None,
  294. (0.8, 0.8, 1.0)
  295. )
  296. def leave_game(self, *args):
  297. """Leave the current game"""
  298. # Clean up
  299. self.my_player = None
  300. self.players = []
  301. self.users = dict()
  302. self.cd.reset()
  303. self.entry_chat.set_text("")
  304. self.logview_game.get_buffer().set_text("")
  305. self.liststore_players.clear()
  306. self.hbox_entrybar.hide()
  307. self.hpaned_game.set_sensitive(False)
  308. if self.solitaire:
  309. self.solitaire = False
  310. self.hpaned_game.set_property("position-set", False)
  311. self.notebook.set_current_page(0)
  312. return
  313. if self.replay is not None:
  314. self.replay = None
  315. self.notebook.set_current_page(0)
  316. self.hbox_replay.hide()
  317. self.entry_chat.show()
  318. return
  319. if self.game is None:
  320. return
  321. logging.info(_("Leaving game '%s'."), self.game.jid)
  322. self.network_manager.leave_game(self.game)
  323. # Dump replay
  324. try:
  325. if not os.path.exists(settings.replay_dir):
  326. os.path.mkdir(settings.replay_dir)
  327. replayfile = os.path.join(settings.replay_dir, config.LATEST_REPLAY)
  328. self.game.recorder.dump_to_file(replayfile)
  329. logging.info("Saved replay to '%s'." % replayfile)
  330. except Exception as e:
  331. logging.error("Error while dumping replay file: " + str(e))
  332. self.game = None
  333. # Return to the lobby
  334. self.notebook.set_current_page(1)
  335. self.entry_chat_lobby.grab_focus()
  336. glib.idle_add(self.refresh_game_list)
  337. def logout(self, *args):
  338. """Log out from the current server"""
  339. if self.solitaire:
  340. self.leave_game()
  341. else:
  342. self.leave_game()
  343. self.network_manager.disconnect()
  344. self.notebook.set_current_page(0)
  345. for widget in (self.entry_username, self.entry_pwd, self.entry_server,
  346. self.checkbutton_save_pwd, self.hbuttonbox_login
  347. ):
  348. widget.set_sensitive(True)
  349. self.hpaned_lobby.set_sensitive(False)
  350. self.logview_lobby.get_buffer().set_text("")
  351. self.entry_chat_lobby.set_text("")
  352. self.liststore_games.clear()
  353. self.server = None
  354. def create_player(self, game, jid, version=""):
  355. """Create a player object for a user"""
  356. jid = muc.JID(jid)
  357. # Remove the player if it has been created before
  358. for player in self.players:
  359. if jid == player.jid:
  360. self.players.remove(player)
  361. if player.tray is not None:
  362. player.remove_tray()
  363. break
  364. player = players.Player(game, jid)
  365. player.version = version
  366. if self.game is not None and jid == self.game.get_my_jid():
  367. player.send_network_cmds = game.send_commands
  368. player.updated_hand = self.cd.repaint_hand
  369. player.new_item = self.new_item
  370. player.new_tray = self.new_tray
  371. player.delete_item = self.delete_item
  372. player.exception_handler = self.handle_exception
  373. self.players.append(player)
  374. # Update a user's version information
  375. userid = self.get_userid(jid)
  376. for i in range(len(self.liststore_players)):
  377. if self.liststore_players[i][0] == userid:
  378. self.liststore_players[i][3] = version
  379. return player
  380. def _incoming_lobby_chat(self, lobby, sender, message):
  381. assert(lobby is self.lobby)
  382. if sender == self.lobby.get_my_jid():
  383. text = _("You: %s") % message
  384. else:
  385. text = _("%s: %s") % (sender.resource, message)
  386. self._add_to_log(self.logview_lobby, text)
  387. def refresh_game_list(self, widget=None):
  388. """Refresh the list of avaible games in the lobby"""
  389. logging.info("Refreshing game list")
  390. rooms = self.network_manager.get_room_list(self.server)
  391. prefix_len = len(config.DEFAULT_GAME_PREFIX)
  392. games = [(jid, name) for (jid, bla, name) in rooms if
  393. jid[:prefix_len] == config.DEFAULT_GAME_PREFIX]
  394. self.liststore_games.clear()
  395. for (jid, name) in games:
  396. self.liststore_games.append((jid.split('@')[0][prefix_len:], jid))
  397. def send_lobby_chat(self, widget):
  398. """Send a chat message to the lobby"""
  399. if self.lobby is not None:
  400. self.lobby.send_message(self.entry_chat_lobby.get_text())
  401. self.entry_chat_lobby.set_text("")
  402. def select_game(self, *args):
  403. """Select a game in the game list"""
  404. model, it = self.treeview_games.get_selection().get_selected()
  405. if it is None:
  406. return # Nothing selected
  407. jid = model.get_value(it, 1)
  408. self._join_game(jid)
  409. def _incoming_cmds(self, game, sender, cmdlist):
  410. """Pass incoming network commands on to the player instances"""
  411. if len(cmdlist) == 0:
  412. return
  413. # Check if a new player entered
  414. cmd1, args1 = cmdlist[0]
  415. if cmd1 == "hello":
  416. player = self.create_player(game, sender, args1[0])
  417. if self.my_player is not None:
  418. self.my_player.handle_network_cmds(sender, cmdlist)
  419. player.has_been_welcomed = True
  420. if cmd1 == "welcome":
  421. for player in self.players:
  422. if player.jid == sender:
  423. break # user found
  424. else:
  425. player = self.create_player(game, sender, args1[0])
  426. # Pass on the commands
  427. for player in self.players:
  428. if player is not self.my_player:
  429. player.handle_network_cmds(sender, cmdlist)
  430. def get_userid(self, jid):
  431. """Get the id corresponding to a room user"""
  432. userid = None
  433. for i, u in self.users.items():
  434. if jid == u:
  435. userid = i
  436. break
  437. return userid
  438. def user_joined(self, jid, role):
  439. """A user joined the game room"""
  440. if jid in self.users.values():
  441. return # Don't add a user twice
  442. # Create new user id
  443. userid = 0
  444. while userid in self.users.keys():
  445. userid += 1
  446. self.users[userid] = jid
  447. self.liststore_players.append(
  448. (userid, jid.resource, jid.full, None, True))
  449. def user_left(self, jid):
  450. """A user left the game"""
  451. # Remove the user
  452. userid = self.get_userid(jid)
  453. del self.users[userid]
  454. for i in range(len(self.liststore_players)):
  455. if self.liststore_players[i][0] == userid:
  456. del self.liststore_players[i]
  457. break
  458. # Find the corresponding player
  459. player = None
  460. for pl in self.players:
  461. if pl.jid == jid:
  462. player = pl
  463. break
  464. if player is None:
  465. return # Player did not join the game
  466. self.players.remove(player)
  467. if player.tray is not None:
  468. player.remove_tray()
  469. def user_nick_changed(self, user):
  470. """A user changed their nick name"""
  471. # Update liststore_players
  472. userid = self.get_userid(user)
  473. for i in range(len(self.liststore_players)):
  474. if self.liststore_players[i][0] == userid:
  475. self.liststore_players[i][1] = user.user
  476. self.liststore_players[i][2] = unicode(user.full)
  477. def _add_to_log(self, logview, text):
  478. """Add a line of text to a logview and scroll to it"""
  479. assert(isinstance(logview, gtk.TextView))
  480. buf = logview.get_buffer()
  481. firstline = buf.get_end_iter().get_offset() == 0
  482. buf.insert(buf.get_end_iter(), ("" if firstline else "\n") + text)
  483. mark = buf.get_mark("insert")
  484. logview.scroll_to_mark(mark, 0)
  485. def add_chat_line(self, game, sender, message):
  486. """Recieved a chat message"""
  487. self._add_to_log(self.logview_game,
  488. _("%s: %s") % (sender.resource, message))
  489. def send_chat_message(self, widget):
  490. """Send a chat message"""
  491. text = self.entry_chat.get_text()
  492. if text == "":
  493. return
  494. if text[0] == "/":
  495. # Handle special commands
  496. match = re.match(r'/life\s+([-0-9]*)', text)
  497. if match is not None:
  498. self.my_player.set_life(int(match.groups()[0]))
  499. match = re.match(r'/draw\s+([0-9]+)', text)
  500. if match is not None:
  501. self.my_player.draw_x_cards(int(match.group(1)))
  502. if text[:5] == "/flip":
  503. # Flip a coin
  504. result = (_("heads"), _("tails"))[random.randint(0, 1)]
  505. msg = _("The coin came up %s.") % result
  506. self.game.send_chat(msg)
  507. self.add_log_line(msg)
  508. if text[:5] == "/roll":
  509. # Roll a die
  510. result = random.randint(1, 6)
  511. msg = _("Rolled a %d.") % result
  512. self.game.send_chat(msg)
  513. self.add_log_line(msg)
  514. else:
  515. if self.game is not None:
  516. self.game.send_chat(text)
  517. self._add_to_log(self.logview_game, _("You: %s") % text)
  518. self.entry_chat.set_text("")
  519. # Global methods
  520. def toggle_fullscreen(self, widget):
  521. """Change the fullscreen state"""
  522. if self.isfullscreen:
  523. self.main_win.unfullscreen()
  524. else:
  525. self.main_win.fullscreen()
  526. self.isfullscreen = not self.isfullscreen
  527. def keypress(self, widget, event):
  528. """Keypress on the main window"""
  529. if event.type == gtk.gdk.KEY_PRESS:
  530. # print("Key:", event.keyval, gtk.gdk.keyval_name(event.keyval))
  531. if event.keyval == 65480: # F11
  532. self.toggle_fullscreen(None)
  533. def untap_all(self, widget=None):
  534. """Untap all cards"""
  535. self._last_untapped = []
  536. for carditem in self.cd._items:
  537. if (isinstance(carditem, desktop.CardItem)
  538. and not carditem.does_not_untap and carditem.mine):
  539. if carditem.tapped:
  540. self._last_untapped.append(carditem)
  541. carditem.set_tapped(False)
  542. def load_deck(self, widget):
  543. """Let the user pick a deck to load"""
  544. dialog = gtk.FileChooserDialog(_("Load a deck..."),
  545. self.main_win, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL,
  546. gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))
  547. dialog.set_default_response(gtk.RESPONSE_OK)
  548. dialog.set_current_folder(settings.deck_dir)
  549. # Set filename filters
  550. f = gtk.FileFilter()
  551. f.set_name(_("Decks"))
  552. f.add_pattern("*.deck")
  553. dialog.add_filter(f)
  554. f = gtk.FileFilter()
  555. f.set_name(_("All files"))
  556. f.add_pattern("*")
  557. dialog.add_filter(f)
  558. response = dialog.run()
  559. if response == gtk.RESPONSE_OK:
  560. self._load_deck(dialog.get_filename())
  561. dialog.destroy()
  562. def _load_deck(self, filename):
  563. """Load a deck by filename"""
  564. logging.info(_("loading %s..."), filename)
  565. if self._deck_load_async_handle is not None:
  566. # Cancel the current loading process
  567. self._deck_load_async_handle.cancel()
  568. self.status_label.set_text(_("Loading deck..."))
  569. # return callback
  570. def finish_deckload(deck):
  571. self._deck_load_async_handle = None
  572. self.my_player.load_deck(deck)
  573. self.status_label.set_text(_("Deck load complete."))
  574. self._deck_load_async_handle = \
  575. async.start(decks.load(filename, None, finish_deckload))
  576. # Entry bar
  577. def reset_entrybar(self):
  578. self.spinbutton_life.hide()
  579. self.spinbutton_num.hide()
  580. self.spinbutton_num.set_value(0)
  581. self.entry.hide()
  582. self.button_accept.hide()
  583. self.label_entrybar2.hide()
  584. self.combobox_counters.hide()
  585. self.combobox_tokens.hide()
  586. def entrybar_size_allocation(self, widget, rect):
  587. self.cd.y_offset = rect.height
  588. def create_token(self, widget):
  589. self.reset_entrybar()
  590. self._entrybar_task = "token"
  591. self.combobox_tokens.show()
  592. self.label_entrybar.set_text(_("Choose a token:"))
  593. self.hbox_entrybar.show()
  594. self.entry_tokens.grab_focus()
  595. def token_pick(self, widget):
  596. i = self.combobox_tokens.get_active()
  597. if 0 <= i < len(cards.tokens):
  598. self.selected_token(cards.tokens[i].id)
  599. def token_autocomplete_pick(self, widget, model, it):
  600. """Picked a token from the autocompletion"""
  601. self.selected_token(model[it][0])
  602. def tokens_activate(self, widget):
  603. text = self.entry_tokens.get_text()
  604. for row in self.liststore_tokens:
  605. if text == row[1]:
  606. self.selected_token(row[0])
  607. break
  608. else:
  609. logging.info(_("Token '%s' is invalid."), text)
  610. def selected_token(self, tokenid):
  611. self.entrybar_unfocus()
  612. token = cards.get(tokenid)
  613. item = self.my_player.create_carditem(token.id, str(token))
  614. item.istoken = True
  615. def set_life(self, widget):
  616. self.reset_entrybar()
  617. self.spinbutton_life.set_value(self.my_player.life)
  618. self.spinbutton_life.show()
  619. self.label_entrybar.set_text(_("Set your life total to:"))
  620. self._entrybar_task = "life"
  621. self.hbox_entrybar.show()
  622. self.spinbutton_life.grab_focus()
  623. def ask_for_reset(self):
  624. self.reset_entrybar()
  625. self.button_accept.show()
  626. self._entrybar_task = "reset"
  627. self.hbox_entrybar.show()
  628. self.button_accept.grab_focus()
  629. self.label_entrybar.set_text(_("Reset all cards and life?"))
  630. def card_set_counters(self, widget):
  631. item = self._popup
  632. self.reset_entrybar()
  633. self.spinbutton_num.show()
  634. self.combobox_counters.show()
  635. self.label_entrybar.set_text(_("Set"))
  636. self.label_entrybar2.set_text(_("counters."))
  637. self.label_entrybar2.show()
  638. # Set default entry
  639. if len(item.counters) > 0:
  640. counter, num = item.counters.items()[0]
  641. self.entry_counters.set_text(counter)
  642. self.spinbutton_num.set_value(num)
  643. elif item.default_counters != []:
  644. counter = item.default_counters[0]
  645. self.entry_counters.set_text(counter)
  646. if counter in item.counters:
  647. self.spinbutton_num.set_value(item.counters[counter])
  648. else:
  649. self.entry_counters.set_text("")
  650. self.liststore_counters.clear()
  651. for counter in item.default_counters:
  652. self.liststore_counters.append((counter,))
  653. for counter in item.counters:
  654. for row in self.liststore_counters:
  655. if row[0] == counter:
  656. break
  657. else:
  658. self.liststore_counters.append((counter,))
  659. self._entrybar_task = "counters"
  660. self.hbox_entrybar.show()
  661. if len(item.counters) == 0:
  662. self.spinbutton_num.set_value(1)
  663. self.entry_counters.grab_focus()
  664. else:
  665. self.spinbutton_num.grab_focus()
  666. def counter_pick(self, widget):
  667. update_counter_num()
  668. def counter_autocomplete_pick(self, widget, model, it):
  669. update_counter_num()
  670. def counter_entry_change(self, widget):
  671. pass
  672. def update_counter_num(self, *args):
  673. counter = self.entry_counters.get_text()
  674. item = self._popup
  675. if counter in item.counters:
  676. self.spinbutton_num.set_value(item.counters[counter])
  677. else:
  678. self.spinbutton_num.set_value(1)
  679. def entrybar_unfocus(self, *args):
  680. if not self.hbox_entrybar.get_visible():
  681. return # entrybar wasn't shown
  682. self.hbox_entrybar.hide()
  683. self.cd.y_offset = 0
  684. if self._entrybar_task == "life":
  685. life = int(self.spinbutton_life.get_value())
  686. if life != self.my_player.life:
  687. self.my_player.set_life(life)
  688. elif self._entrybar_task == "counters":
  689. if self.entry_counters.get_text() != "":
  690. counter = self.entry_counters.get_text()
  691. num = int(self.spinbutton_num.get_value())
  692. self.my_player.set_counters(self._popup, num, counter)
  693. self._entrybar_task = ""
  694. def entrybar_accept(self, widget):
  695. if not self.hbox_entrybar.get_visible():
  696. return # entrybar wasn't shown
  697. if self._entrybar_task in ("reset", "spectate"):
  698. self.my_player.reset()
  699. if self._entrybar_task == "spectate":
  700. self.my_player.remove_tray()
  701. self.entrybar_unfocus()
  702. # Interface callbacks
  703. def zoom_in(self, widget):
  704. self.cd.zoom *= 1.2
  705. self.cd.queue_draw()
  706. def zoom_out(self, widget):
  707. self.cd.zoom *= 0.8
  708. self.cd.queue_draw()
  709. def reset_game(self, widget):
  710. if self.my_player.entered_play():
  711. self.ask_for_reset()
  712. else:
  713. self.my_player.reset()
  714. def spectate(self, widget):
  715. if self.my_player.entered_play():
  716. self.ask_for_reset()
  717. self._entrybar_task = "spectate"
  718. else:
  719. self.my_player.remove_tray()
  720. def shuffle_library(self, widget):
  721. self.my_player.shuffle_library()
  722. def draw_a_card(self, widget):
  723. self.my_player.draw_card()
  724. def draw_7_cards(self, widget):
  725. self.my_player.draw_x_cards(7)
  726. def lib_top_to_bottom(self, widget):
  727. self.my_player.library.insert(0, self.my_player.library.pop())
  728. def discard_this(self, widget):
  729. self.my_player.discard(self._popup)
  730. def exile_this(self, widget):
  731. pl = self.my_player
  732. pl.move_card(self._popup, pl.hand, pl.exile)
  733. def hand_to_library(self, widget):
  734. pl = self.my_player
  735. pl.move_card(self._popup, pl.hand, pl.library)
  736. def discard_random(self, widget):
  737. self.my_player.discard_random()
  738. def shuffle_hand(self, widget):
  739. self.my_player.shuffle_hand()
  740. def discard_all(self, widget):
  741. self.my_player.discard_all()
  742. def mulligan(self, widget):
  743. self.my_player.mulligan()
  744. def shuffle_graveyard_into_library(self, widget):
  745. self.my_player.shuffle_graveyard_into_library()
  746. def return_to_hand_from_graveyard(self, widget):
  747. self.my_player.graveyard_top_to_hand()
  748. def exile_from_graveyard(self, widget):
  749. pl = self.my_player
  750. pl.move_card(pl.graveyard[-1], pl.graveyard, pl.exile)
  751. def switch_sides(self, widget):
  752. self.cd.flip_y = not self.cd.flip_y
  753. self.cd.repaint()
  754. def card_to_hand(self, widget):
  755. pl = self.my_player
  756. pl.move_card(self._popup, pl.battlefield, pl.hand)
  757. def card_to_library(self, widget):
  758. pl = self.my_player
  759. pl.move_card(self._popup, pl.battlefield, pl.library)
  760. def card_to_graveyard(self, widget):
  761. pl = self.my_player
  762. pl.move_card(self._popup, pl.battlefield, pl.graveyard)
  763. def exile_card(self, widget):
  764. pl = self.my_player
  765. pl.move_card(self._popup, pl.battlefield, pl.exile)
  766. def clone_card(self, widget):
  767. pl = self.my_player
  768. item = self._popup
  769. name = str(item.token) if item.card is None else item.card.name
  770. x, y = item.x + 1, item.y + 1
  771. item2 = pl.create_carditem(item.cardid, name, None, x, y)
  772. item2.istoken = True
  773. item2.card = item.card
  774. item2.token = item.token
  775. def flip_card(self, widget):
  776. self._popup.set_flipped(self.menuitem_flipped.get_active())
  777. self._popup.repaint()
  778. def turn_card_over(self, widget):
  779. self._popup.set_faceup(not self.menuitem_faceup.get_active())
  780. self._popup.repaint()
  781. def card_set_no_untap(self, widget):
  782. self._popup.does_not_untap = self.menuitem_does_not_untap.get_active()
  783. def card_give_to(self, widget):
  784. pass # TODO
  785. def card_show_details(self, widget):
  786. pass # TODO
  787. def browse_exile(self, widget):
  788. self.show_cardbrowser(self.my_player.exile, None)
  789. # Hide useless buttons
  790. self.button_to_top.hide()
  791. self.button_to_bottom.hide()
  792. self.button_exile.hide()
  793. def browse_graveyard(self, widget):
  794. # Find the graveyard's owner
  795. player = self._popup.parent.player
  796. self.show_cardbrowser(player.graveyard, None, self.my_player is player)
  797. self.button_to_graveyard.hide()
  798. def browse_library(self, widget):
  799. self.show_cardbrowser(self.my_player.library, True)
  800. self.button_to_library.hide()
  801. def zoom_change(self, *args):
  802. value = self.hscale_zoom.get_value()
  803. self.cd.zoom = 35 / math.sqrt(value + 1)
  804. self.cd.queue_draw()
  805. # CarioDesktop's callbacks
  806. def hover(self, item):
  807. """The user hovers the mouse over an item or handcard"""
  808. if isinstance(item, desktop.CardItem):
  809. self.status_label.set_text(item.get_description())
  810. if isinstance(item, cards.Card):
  811. self.status_label.set_text(item.name)
  812. if isinstance(item, desktop.Graveyard):
  813. graveyard = item.parent.player.graveyard
  814. self.status_label.set_text(_("%s's graveyard: %d cards") %
  815. (item.parent.player.nick, len(graveyard)))
  816. if len(graveyard) > 0:
  817. self.cd.show_enlarged_card(graveyard[-1].id)
  818. if isinstance(item, desktop.Library):
  819. self.status_label.set_text(_("%s's library: %d cards") %
  820. (item.parent.player.nick, len(item.parent.player.library)))
  821. if isinstance(item, desktop.Tray):
  822. self.status_label.set_text(item.player.nick)
  823. def call_properties(self, item, event):
  824. """Display the popup menu for an item or handcard"""
  825. self._popup = item
  826. iplay = self.my_player is not None and self.my_player.tray is not None
  827. if item is None:
  828. self._popup = event.x, event.y
  829. if iplay:
  830. self.menuitem_browse_exile.set_sensitive(
  831. len(self.my_player.exile) > 0)
  832. self.menuitem_browse_exile.set_label(
  833. _("browse exile (%d)...") % len(self.my_player.exile))
  834. self.menu_desktop.popup(None, None, None, event.button,
  835. event.time)
  836. if isinstance(item, desktop.CardItem):
  837. self.menuitem_flipped.set_active(item.flipped)
  838. self.menuitem_faceup.set_active(not item.faceup)
  839. self.menuitem_does_not_untap.set_active(item.does_not_untap)
  840. self.menuitem_clone.set_visible(iplay)
  841. for widgetname in ("to_hand", "to_lib", "to_graveyard", "exile",
  842. "attack", "block", "use_effect", "cardsep2", "does_not_untap",
  843. "set_counters", "give_to"):
  844. getattr(self, "menuitem_" + widgetname).set_visible(item.mine)
  845. for widgetname in ("flipped", "faceup"):
  846. getattr(self, "menuitem_" + widgetname).set_sensitive(item.mine)
  847. self.menu_card.popup(None, None, None, event.button, event.time)
  848. if isinstance(item, desktop.Tray):
  849. if item.mine:
  850. self.menu_tray.popup(None, None, None, event.button, event.time)
  851. if isinstance(item, desktop.Library):
  852. if item.mine:
  853. self.menuitem_draw_7_from_lib.set_sensitive(
  854. len(self.my_player.library) >= 7)
  855. self.menuitem_draw_from_lib.set_sensitive(
  856. len(self.my_player.library) >= 1)
  857. self.menuitem_lib_top_to_bottom.set_sensitive(
  858. len(self.my_player.library) >= 1)
  859. self.menu_library.popup(None, None, None, event.button,
  860. event.time)
  861. if isinstance(item, desktop.Graveyard):
  862. self.menuitem_graveyard_to_hand.set_visible(item.mine)
  863. self.menuitem_graveyard_exile.set_visible(item.mine)
  864. self.menuitem_graveyard_shuffle_lib.set_visible(item.mine)
  865. self.menu_graveyard.popup(None, None, None, event.button,
  866. event.time)
  867. if isinstance(item, cards.Card):
  868. self.menu_hand.popup(None, None, None, event.button, event.time)
  869. # Player callbacks
  870. def new_item(self, cardortoken, player, x, y):
  871. """Create a new item from a cardid"""
  872. assert(cardortoken is not None)
  873. assert(player is not None)
  874. mine = player is self.my_player
  875. item = desktop.CardItem(cardortoken, player, mine)
  876. item.x = x
  877. item.y = y
  878. if player == self.my_player:
  879. glib.idle_add(semantics.init_carditem, item) # Parse card semantics
  880. self.cd.add_item(item)
  881. item.clamp_coords()
  882. item.repaint()
  883. return item
  884. def new_tray(self, player):
  885. """Create a new tray item"""
  886. mine = player is self.my_player
  887. tray = desktop.Tray(player, mine)
  888. tray.widget = self.cd
  889. if len(self.players) == 1:
  890. tray.x = -22
  891. tray.y = 8
  892. elif len(self.players) == 2:
  893. tray.x = 22 - tray.w
  894. tray.y = -8 - tray.h
  895. elif len(self.players) == 3:
  896. tray.x = 22 - tray.w
  897. tray.y = 8
  898. elif len(self.players) == 4:
  899. tray.x = -22
  900. tray.y = -8 - tray.h
  901. self.cd.add_item(tray, None if mine else 0)
  902. tray.repaint()
  903. # append redraw call-backs
  904. player.updated_library = tray.repaint
  905. player.updated_graveyard = tray.repaint
  906. player.updated_life = tray.repaint
  907. return tray
  908. def delete_item(self, item):
  909. """Delete a card item"""
  910. self.cd.remove_item(item)
  911. # Card browser
  912. def show_cardbrowser(self, cardlist, shuffle=True, mine=True):
  913. """Show the cardbrowser"""
  914. assert(cardlist is not None)
  915. if shuffle is None:
  916. self.checkbutton_shuffle.hide()
  917. self.checkbutton_shuffle.set_active(False)
  918. else:
  919. self.checkbutton_shuffle.show()
  920. self.checkbutton_shuffle.set_active(shuffle)
  921. # Show buttons
  922. for widget in [self.button_to_graveyard, self.button_to_library,
  923. self.button_exile, self.button_to_hand, self.button_to_top,
  924. self.button_to_bottom]:
  925. widget.show()
  926. for widget in [self.button_to_graveyard, self.button_to_library,
  927. self.button_exile, self.button_to_hand,
  928. self.checkbutton_shuffle]:
  929. widget.set_sensitive(mine)
  930. self._browser_cardlist = cardlist
  931. self.update_cardlist()
  932. self.win_browse.show()
  933. def update_cardlist(self):
  934. """Update the card browser list"""
  935. if len(self._browser_cardlist) == 0:
  936. self.hide_cardbrowser()
  937. return
  938. self.liststore_browse.clear()
  939. for i in range(len(self._browser_cardlist)):
  940. card = self._browser_cardlist[i]
  941. self.liststore_browse.append((i, card.name, card.manacost,
  942. card.get_composed_type(), card.power, card.toughness,
  943. card.text)
  944. )
  945. def hide_cardbrowser(self, *args):
  946. """Hide the card browser"""
  947. self.win_browse.hide()
  948. if self.checkbutton_shuffle.get_active():
  949. self.my_player.shuffle_library()
  950. self._browser_cardlist = None
  951. return True
  952. def browser_to_top(self, widget):
  953. """Move the selected card to the top of the list"""
  954. assert(self._browser_cardlist is not None)
  955. # TODO
  956. def browser_to_bottom(self, widget):
  957. """Move the selected card to the bottom of the list"""
  958. assert(self._browser_cardlist is not None)
  959. # TODO
  960. def browser_to_graveyard(self, widget):
  961. """Move the selected card to the graveyard"""
  962. assert(self._browser_cardlist is not None)
  963. model, it = self.treeview_browse.get_selection().get_selected()
  964. if it is None:
  965. return # Nothing selected
  966. card = self._browser_cardlist[model.get_value(it, 0)]
  967. self.my_player.move_card(card, self._browser_cardlist,
  968. self.my_player.graveyard)
  969. model.remove(it)
  970. self.update_cardlist()
  971. def browser_to_library(self, widget):
  972. """Move the selected card to the library"""
  973. assert(self._browser_cardlist is not None)
  974. model, it = self.treeview_browse.get_selection().get_selected()
  975. if it is None:
  976. return # Nothing selected
  977. card = self._browser_cardlist[model.get_value(it, 0)]
  978. self.my_player.move_card(card, self._browser_cardlist,
  979. self.my_player.library)
  980. model.remove(it)
  981. self.update_cardlist()
  982. def browser_exile(self, widget):
  983. """Move the selected card to the exile"""
  984. assert(self._browser_cardlist is not None)
  985. model, it = self.treeview_browse.get_selection().get_selected()
  986. if it is None:
  987. return # Nothing selected
  988. card = self._browser_cardlist[model.get_value(it, 0)]
  989. self.my_player.move_card(card, self._browser_cardlist,
  990. self.my_player.exile)
  991. model.remove(it)
  992. self.update_cardlist()
  993. def browser_to_hand(self, widget):
  994. """Move the selected card to the hand"""
  995. assert(self._browser_cardlist is not None)
  996. model, it = self.treeview_browse.get_selection().get_selected()
  997. if it is None:
  998. return # Nothing selected
  999. card = self._browser_cardlist[model.get_value(it, 0)]
  1000. self.my_player.move_card(card, self._browser_cardlist,
  1001. self.my_player.hand)
  1002. model.remove(it)
  1003. self.update_cardlist()
  1004. # Debug
  1005. def handle_exception(self, exception):
  1006. """Display a message to the user about the exception"""
  1007. self.show_dialog(self.main_win, str(exception), dialog_type="error")
  1008. def create_random_card(self, widget=None):
  1009. """Create a random card on the desktop"""
  1010. cardid = "tsp." + str(random.randint(1, 301))
  1011. w, h = self.cd.get_wh()
  1012. x = (random.random() - 0.5) * (w - 2.5)
  1013. y = random.random() * (h - 3.5) / 2
  1014. self.my_player.move_card(cards.get(cardid), None,
  1015. self.my_player.battlefield, x, y)