1#!/usr/bin/env python 2# -*- mode: python; coding: utf-8; -*- 3# ---------------------------------------------------------------------------## 4# 5# Copyright (C) 1998-2003 Markus Franz Xaver Johannes Oberhumer 6# Copyright (C) 2003 Mt. Hood Playing Card Co. 7# Copyright (C) 2005-2009 Skomoroh 8# 9# This program is free software: you can redistribute it and/or modify 10# it under the terms of the GNU General Public License as published by 11# the Free Software Foundation, either version 3 of the License, or 12# (at your option) any later version. 13# 14# This program is distributed in the hope that it will be useful, 15# but WITHOUT ANY WARRANTY; without even the implied warranty of 16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17# GNU General Public License for more details. 18# 19# You should have received a copy of the GNU General Public License 20# along with this program. If not, see <http://www.gnu.org/licenses/>. 21# 22# ---------------------------------------------------------------------------## 23 24import locale 25import os 26 27from pysollib.gamedb import GI 28from pysollib.help import help_about, help_html 29from pysollib.mfxutil import Struct, openURL 30from pysollib.mfxutil import USE_PIL, print_err 31from pysollib.mygettext import _ 32from pysollib.pysolrandom import construct_random 33from pysollib.pysoltk import AllGames_StatsDialog, SingleGame_StatsDialog 34from pysollib.pysoltk import ColorsDialog 35from pysollib.pysoltk import EditTextDialog 36from pysollib.pysoltk import FontsDialog 37from pysollib.pysoltk import FullLog_StatsDialog, SessionLog_StatsDialog 38from pysollib.pysoltk import GameInfoDialog 39from pysollib.pysoltk import MfxExceptionDialog 40from pysollib.pysoltk import MfxMessageDialog, MfxSimpleEntry 41from pysollib.pysoltk import PlayerOptionsDialog 42from pysollib.pysoltk import ProgressionDialog 43from pysollib.pysoltk import PysolMenubarTk, PysolToolbarTk 44from pysollib.pysoltk import Status_StatsDialog, Top_StatsDialog 45from pysollib.pysoltk import TimeoutsDialog 46from pysollib.pysoltk import create_find_card_dialog 47from pysollib.pysoltk import create_solver_dialog 48from pysollib.settings import DEBUG 49from pysollib.settings import PACKAGE_URL, TITLE 50from pysollib.settings import TOP_SIZE 51from pysollib.stats import FileStatsFormatter 52 53 54# ************************************************************************ 55# * menubar 56# ************************************************************************ 57 58class PysolMenubar(PysolMenubarTk): 59 def __init__(self, app, top, progress=None): 60 self.app = app 61 self.top = top 62 self.game = None 63 # enabled/disabled - this is set by updateMenuState() 64 self.menustate = Struct( 65 save=0, 66 save_as=0, 67 hold_and_quit=0, 68 undo=0, 69 redo=0, 70 restart=0, 71 deal=0, 72 hint=0, 73 autofaceup=0, 74 autodrop=0, 75 shuffle=0, 76 autodeal=0, 77 quickplay=0, 78 demo=0, 79 highlight_piles=0, 80 autoscale=0, 81 find_card=0, 82 rules=0, 83 pause=0, 84 custom_game=0, 85 ) 86 PysolMenubarTk.__init__(self, app, top, progress) 87 88 # 89 # delegation to Game 90 # 91 92 def _finishDrag(self): 93 return self.game is None or self.game._finishDrag() 94 95 def _cancelDrag(self, break_pause=True): 96 if self.game is None: 97 return True 98 ret = self.game._cancelDrag(break_pause=break_pause) 99 self._setPauseMenu(self.game.pause) 100 return ret 101 102 def changed(self, *args, **kw): 103 assert self.game is not None 104 return self.game.changed(*args, **kw) 105 106 # 107 # menu updates 108 # 109 110 def _clearMenuState(self): 111 ms = self.menustate 112 for k, v in ms.__dict__.items(): 113 if isinstance(v, list): 114 ms.__dict__[k] = [0] * len(v) 115 else: 116 ms.__dict__[k] = 0 117 118 # update self.menustate for menu items and toolbar 119 def _updateMenuState(self): 120 self._clearMenuState() 121 game = self.game 122 assert game is not None 123 opt = self.app.opt 124 ms = self.menustate 125 # 0 = DISABLED, 1 = ENABLED 126 ms.save_as = game.canSaveGame() 127 ms.hold_and_quit = ms.save_as 128 if game.filename and ms.save_as: 129 ms.save = 1 130 if opt.undo: 131 if game.canUndo() and game.moves.index > 0: 132 ms.undo = 1 133 if game.canRedo() and game.moves.index < len(game.moves.history): 134 ms.redo = 1 135 if game.moves.index > 0: 136 ms.restart = 1 137 if game.canDealCards(): 138 ms.deal = 1 139 if game.getHintClass() is not None: 140 if opt.hint: 141 ms.hint = 1 142 # if not game.demo: # if not already running 143 ms.demo = 1 144 autostacks = game.getAutoStacks() 145 if autostacks[0]: 146 ms.autofaceup = 1 147 if autostacks[1] and game.s.foundations: 148 ms.autodrop = 1 149 if game.s.waste: 150 ms.autodeal = 1 151 if autostacks[2]: 152 ms.quickplay = 1 153 if opt.highlight_piles and game.getHighlightPilesStacks(): 154 ms.highlight_piles = 1 155 if opt.auto_scale: 156 ms.autoscale = 1 157 if game.canFindCard(): 158 ms.find_card = 1 159 if game.app.getGameRulesFilename(game.id): # note: this may return "" 160 ms.rules = 1 161 if not game.finished: 162 ms.pause = 1 163 if game.gameinfo.si.game_type == GI.GT_CUSTOM: 164 ms.custom_game = 1 165 if game.canShuffle(): 166 if opt.shuffle: 167 ms.shuffle = 1 168 169 # update menu items and toolbar 170 def _updateMenus(self): 171 if self.game is None: 172 return 173 ms = self.menustate 174 # File menu 175 self.setMenuState(ms.save, "file.save") 176 self.setMenuState(ms.save_as, "file.saveas") 177 self.setMenuState(ms.hold_and_quit, "file.holdandquit") 178 # Edit menu 179 self.setMenuState(ms.undo, "edit.undo") 180 self.setMenuState(ms.redo, "edit.redo") 181 self.setMenuState(ms.redo, "edit.redoall") 182 self.updateBookmarkMenuState() 183 self.setMenuState(ms.restart, "edit.restart") 184 self.setMenuState(ms.custom_game, "edit.editcurrentgame") 185 self.setMenuState(ms.custom_game, "edit.deletecurrentgame") 186 # Game menu 187 self.setMenuState(ms.deal, "game.dealcards") 188 self.setMenuState(ms.autodrop, "game.autodrop") 189 self.setMenuState(ms.shuffle, "game.shuffletiles") 190 self.setMenuState(ms.pause, "game.pause") 191 # Assist menu 192 self.setMenuState(ms.hint, "assist.hint") 193 self.setMenuState(ms.highlight_piles, "assist.highlightpiles") 194 self.setMenuState(ms.find_card, "assist.findcard") 195 self.setMenuState(ms.demo, "assist.demo") 196 self.setMenuState(ms.demo, "assist.demoallgames") 197 # Options menu 198 self.setMenuState(ms.autofaceup, "options.automaticplay.autofaceup") 199 self.setMenuState(ms.autodrop, "options.automaticplay.autodrop") 200 self.setMenuState(ms.autodeal, "options.automaticplay.autodeal") 201 self.setMenuState(ms.quickplay, "options.automaticplay.quickplay") 202 if USE_PIL: 203 self.setMenuState(ms.autoscale, 204 "options.cardsize.preserveaspectratio") 205 self.setMenuState(not ms.autoscale, 206 "options.cardsize.increasethecardsize") 207 self.setMenuState(not ms.autoscale, 208 "options.cardsize.decreasethecardsize") 209 self.setMenuState(not ms.autoscale, 210 "options.cardsize.resetthecardsize") 211 # Help menu 212 self.setMenuState(ms.rules, "help.rulesforthisgame") 213 # Toolbar 214 self.setToolbarState(ms.restart, "restart") 215 self.setToolbarState(ms.save_as, "save") 216 self.setToolbarState(ms.undo, "undo") 217 self.setToolbarState(ms.redo, "redo") 218 self.setToolbarState(ms.autodrop, "autodrop") 219 self.setToolbarState(ms.shuffle, "shuffle") 220 self.setToolbarState(ms.pause, "pause") 221 self.setToolbarState(ms.rules, "rules") 222 223 # update menu items and toolbar 224 def updateMenus(self): 225 if self.game is None: 226 return 227 self._updateMenuState() 228 self._updateMenus() 229 230 # disable menu items and toolbar 231 def disableMenus(self): 232 if self.game is None: 233 return 234 self._clearMenuState() 235 self._updateMenus() 236 237 # 238 # File menu 239 # 240 241 def mNewGame(self, *args): 242 if self._cancelDrag(): 243 return 244 if self.changed(): 245 if not self.game.areYouSure(_("New game")): 246 return 247 if self.game.nextGameFlags(self.game.id) == 0: 248 self.game.endGame() 249 self.game.newGame() 250 else: 251 self.game.endGame() 252 self.game.quitGame(self.game.id) 253 254 def _mSelectGame(self, id, random=None, force=False): 255 if self._cancelDrag(): 256 return 257 if not force and self.game.id == id: 258 return 259 if self.changed(): 260 if not self.game.areYouSure(_("Select game")): 261 return 262 self.game.endGame() 263 self.game.quitGame(id, random=random) 264 265 def _mNewGameBySeed(self, seed, origin): 266 try: 267 random = construct_random(seed) 268 if random is None: 269 return 270 id = self.game.id 271 if not self.app.getGameInfo(id): 272 raise ValueError 273 except (ValueError, TypeError): 274 MfxMessageDialog(self.top, title=_("Invalid game number"), 275 text=_("Invalid game number\n") + str(seed), 276 bitmap="error") 277 return 278 f = self.game.nextGameFlags(id, random) 279 if f & 17 == 0: 280 return 281 random.origin = origin 282 if f & 15 == 0: 283 self.game.endGame() 284 self.game.newGame(random=random) 285 else: 286 self.game.endGame() 287 self.game.quitGame(id, random=random) 288 289 def mNewGameWithNextId(self, *args): 290 if self._cancelDrag(): 291 return 292 if self.changed(): 293 if not self.game.areYouSure(_("Select next game number")): 294 return 295 r = self.game.random 296 seed = r.increaseSeed(r.initial_seed) 297 seed = r.str(seed) 298 self._mNewGameBySeed(seed, self.game.random.ORIGIN_NEXT_GAME) 299 300 def mSelectGameById(self, *args): 301 if self._cancelDrag(break_pause=False): 302 return 303 f = self.game.getGameNumber(format=0) 304 d = MfxSimpleEntry(self.top, _("Select new game number"), 305 _("\n\nEnter new game number"), f, 306 strings=(_("&OK"), _("&Next number"), _("&Cancel")), 307 default=0, e_width=25) 308 if d.status != 0: 309 return 310 if d.button == 2: 311 return 312 if d.button == 1: 313 self.mNewGameWithNextId() 314 return 315 if self.changed(): 316 if not self.game.areYouSure(_("Select new game number")): 317 return 318 self._mNewGameBySeed(d.value, self.game.random.ORIGIN_SELECTED) 319 320 def mSelectRandomGame(self, type='all'): 321 if self._cancelDrag(): 322 return 323 if self.changed(): 324 if not self.game.areYouSure(_("Select random game")): 325 return 326 game_id = None 327 games = [] 328 for g in self.app.gdb.getGamesIdSortedById(): 329 gi = self.app.getGameInfo(g) 330 if 1 and gi.id == self.game.id: 331 # force change of game 332 continue 333 if 1 and gi.category != self.game.gameinfo.category: 334 # don't change game category 335 continue 336 won, lost = self.app.stats.getStats(self.app.opt.player, gi.id) 337 if type == 'all': 338 games.append(gi.id) 339 elif type == 'won' and won > 0: 340 games.append(gi.id) 341 elif type == 'not won' and won == 0 and lost > 0: 342 games.append(gi.id) 343 elif type == 'not played' and won+lost == 0: 344 games.append(gi.id) 345 if games: 346 game_id = self.app.chooseRandomOutOfGames(games) 347 if game_id and game_id != self.game.id: 348 self.game.endGame() 349 self.game.quitGame(game_id) 350 351 def _mSelectNextGameFromList(self, gl, step): 352 if self._cancelDrag(): 353 return 354 id = self.game.id 355 gl = list(gl) 356 if len(gl) < 2 or (id not in gl): 357 return 358 if self.changed(): 359 if not self.game.areYouSure(_("Select next game")): 360 return 361 index = (gl.index(id) + step) % len(gl) 362 self.game.endGame() 363 self.game.quitGame(gl[index]) 364 365 def mSelectNextGameById(self, *args): 366 self._mSelectNextGameFromList(self.app.gdb.getGamesIdSortedById(), 1) 367 368 def mSelectPrevGameById(self, *args): 369 self._mSelectNextGameFromList(self.app.gdb.getGamesIdSortedById(), -1) 370 371 def mSelectNextGameByName(self, *args): 372 self._mSelectNextGameFromList(self.app.gdb.getGamesIdSortedByName(), 1) 373 374 def mSelectPrevGameByName(self, *args): 375 self._mSelectNextGameFromList( 376 self.app.gdb.getGamesIdSortedByName(), -1) 377 378 def mSave(self, *args): 379 if self._cancelDrag(break_pause=False): 380 return 381 if self.menustate.save_as: 382 if self.game.filename: 383 self.game.saveGame(self.game.filename) 384 else: 385 self.mSaveAs() 386 387 def mHoldAndQuit(self, *args): 388 if self._cancelDrag(): 389 return 390 self.game.endGame(holdgame=1) 391 self.game.quitGame(holdgame=1) 392 393 def mQuit(self, *args): 394 if self._cancelDrag(): 395 return 396 if self.changed(): 397 if not self.game.areYouSure(_("Quit %s") % TITLE): 398 return 399 self.game.endGame() 400 self.game.quitGame() 401 402 # 403 # Edit menu 404 # 405 406 def mUndo(self, *args): 407 if self._cancelDrag(): 408 return 409 if self.menustate.undo: 410 self.game.playSample("undo") 411 self.game.undo() 412 413 def mRedo(self, *args): 414 if self._cancelDrag(): 415 return 416 if self.menustate.redo: 417 self.game.playSample("redo") 418 self.game.redo() 419 self.game.checkForWin() 420 421 def mRedoAll(self, *args): 422 if self._cancelDrag(): 423 return 424 if self.menustate.redo: 425 self.app.top.busyUpdate() 426 self.game.playSample("redo", loop=1) 427 while self.game.moves.index < len(self.game.moves.history): 428 self.game.redo() 429 if self.game.checkForWin(): 430 break 431 self.game.stopSamples() 432 433 def mSetBookmark(self, n, confirm=1): 434 if self._cancelDrag(): 435 return 436 if not self.app.opt.bookmarks: 437 return 438 if not (0 <= n <= 8): 439 return 440 self.game.setBookmark(n, confirm=confirm) 441 self.game.updateMenus() 442 443 def mGotoBookmark(self, n, confirm=-1): 444 if self._cancelDrag(): 445 return 446 if not self.app.opt.bookmarks: 447 return 448 if not (0 <= n <= 8): 449 return 450 self.game.gotoBookmark(n, confirm=confirm) 451 self.game.updateMenus() 452 453 def mClearBookmarks(self, *args): 454 if self._cancelDrag(): 455 return 456 if not self.app.opt.bookmarks: 457 return 458 if not self.game.gsaveinfo.bookmarks: 459 return 460 if not self.game.areYouSure(_("Clear bookmarks"), 461 _("Clear all bookmarks?")): 462 return 463 self.game.gsaveinfo.bookmarks = {} 464 self.game.updateMenus() 465 466 def mRestart(self, *args): 467 if self._cancelDrag(): 468 return 469 if self.game.moves.index == 0: 470 return 471 if self.changed(restart=1): 472 if not self.game.areYouSure(_("Restart game"), 473 _("Restart this game?")): 474 return 475 self.game.restartGame() 476 477 # 478 # Game menu 479 # 480 481 def mDeal(self, *args): 482 if self._cancelDrag(): 483 return 484 self.game.dealCards() 485 486 def mDrop(self, *args): 487 if self._cancelDrag(): 488 return 489 # self.game.autoPlay(autofaceup=-1, autodrop=1) 490 self.game.autoDrop(autofaceup=-1) 491 492 def mDrop1(self, *args): 493 if self._cancelDrag(): 494 return 495 # self.game.autoPlay(autofaceup=1, autodrop=1) 496 self.game.autoDrop(autofaceup=1) 497 498 def mShuffle(self, *args): 499 if self._cancelDrag(): 500 return 501 if self.menustate.shuffle: 502 if self.game.canShuffle(): 503 self.game._mahjonggShuffle() 504 505 def mFindCard(self, *args): 506 if self.game.canFindCard(): 507 create_find_card_dialog(self.game.top, self.game, 508 self.app.getFindCardImagesDir()) 509 510 def mSolver(self, *args): 511 create_solver_dialog(self.game.top, self.app) 512 513 def mEditGameComment(self, *args): 514 if self._cancelDrag(break_pause=False): 515 return 516 game, gi = self.game, self.game.gameinfo 517 kw = {'game': gi.name, 518 'id': game.getGameNumber(format=1)} 519 cc = _("Comments for %(game)s %(id)s:\n\n") % kw 520 c = game.gsaveinfo.comment or cc 521 d = EditTextDialog(game.top, _("Comments for %(id)s") % kw, text=c) 522 if d.status == 0 and d.button == 0: 523 text = d.text 524 if text.strip() == cc.strip(): 525 game.gsaveinfo.comment = "" 526 else: 527 game.gsaveinfo.comment = d.text 528 # save to file 529 fn = os.path.join(self.app.dn.config, "comments.txt") 530 fn = os.path.normpath(fn) 531 if not text.endswith(os.linesep): 532 text += os.linesep 533 enc = locale.getpreferredencoding() 534 try: 535 with open(fn, 'at') as fh: 536 fh.write(text.encode(enc, 'replace')) 537 except Exception as err: 538 d = MfxExceptionDialog( 539 self.top, err, 540 text=_("Error while writing to file")) 541 else: 542 d = MfxMessageDialog( 543 self.top, title=_("%s Info") % TITLE, bitmap="info", 544 text=_("Comments were appended to\n\n%(filename)s") 545 % {'filename': fn}) 546 self._setCommentMenu(bool(game.gsaveinfo.comment)) 547 548 # 549 # Game menu - statistics 550 # 551 552 def _mStatsSave(self, player, filename, write_method): 553 if player is None: 554 text = _("Demo statistics were appended to\n\n%(filename)s") 555 filename = filename + "_demo" 556 else: 557 text = _("Your statistics were appended to\n\n%(filename)s") 558 filename = os.path.join(self.app.dn.config, filename + ".txt") 559 filename = os.path.normpath(filename) 560 try: 561 a = FileStatsFormatter(self.app, open(filename, "a")) 562 write_method(a, player) 563 except EnvironmentError as ex: 564 MfxExceptionDialog(self.top, ex, 565 text=_("Error while writing to file")) 566 else: 567 MfxMessageDialog( 568 self.top, title=_("%s Info") % TITLE, bitmap="info", 569 text=text % {'filename': filename}) 570 571 def mPlayerStats(self, *args, **kw): 572 wasPaused = False 573 if not self.game.pause: 574 self.game.doPause() 575 wasPaused = True 576 mode = kw.get("mode", 101) 577 demo = 0 578 gameid = None 579 while mode > 0: 580 if mode > 1000: 581 demo = not demo 582 mode = mode % 1000 583 # 584 d = Struct(status=-1, button=-1) 585 if demo: 586 player = None 587 else: 588 player = self.app.opt.player 589 n = self.game.gameinfo.name 590 # translation keywords 591 transkw = {'app': TITLE, 592 'player': player, 593 'game': n, 594 'tops': TOP_SIZE} 595 # 596 if mode == 100: 597 d = Status_StatsDialog(self.top, game=self.game) 598 elif mode == 101: 599 header = (_("%(app)s Demo Statistics for %(game)s") if demo 600 else _("Statistics for %(game)s")) % transkw 601 d = SingleGame_StatsDialog( 602 self.top, header, self.app, player, gameid=self.game.id) 603 gameid = d.selected_game 604 elif mode == 102: 605 header = (_("%(app)s Demo Statistics") if demo 606 else _("Statistics for %(player)s")) % transkw 607 d = AllGames_StatsDialog(self.top, header, self.app, player) 608 gameid = d.selected_game 609 elif mode == 103: 610 header = (_("%(app)s Demo Full log") if demo 611 else _("Full log for %(player)s")) % transkw 612 d = FullLog_StatsDialog(self.top, header, self.app, player) 613 elif mode == 104: 614 header = (_("%(app)s Demo Session log") if demo 615 else _("Session log for %(player)s")) % transkw 616 d = SessionLog_StatsDialog(self.top, header, self.app, player) 617 elif mode == 105: 618 # TRANSLATORS: eg. top 10 or top 5 results for a certain game 619 header = (_("%(app)s Demo Top %(tops)d for %(game)s") if demo 620 else _("Top %(tops)d for %(game)s")) % transkw 621 d = Top_StatsDialog( 622 self.top, header, self.app, player, gameid=self.game.id) 623 elif mode == 106: 624 header = _("Game Info") 625 d = GameInfoDialog(self.top, header, self.app) 626 elif mode == 107: 627 header = _("Statistics progression") 628 d = ProgressionDialog( 629 self.top, header, self.app, player, gameid=self.game.id) 630 elif mode == 202: 631 # print stats to file 632 write_method = FileStatsFormatter.writeStats 633 self._mStatsSave(player, "stats", write_method) 634 elif mode == 203: 635 # print full log to file 636 write_method = FileStatsFormatter.writeFullLog 637 self._mStatsSave(player, "log", write_method) 638 elif mode == 204: 639 # print session log to file 640 write_method = FileStatsFormatter.writeSessionLog 641 self._mStatsSave(player, "log", write_method) 642 elif mode == 301: 643 # reset all player stats 644 if self.game.areYouSure( 645 _("Reset all statistics"), 646 _("Reset ALL statistics and logs for player\n" + 647 "%(player)s?") % transkw, 648 confirm=1, default=1 649 ): 650 self.app.stats.resetStats(player, 0) 651 self.game.updateStatus(stats=self.app.stats.getStats( 652 self.app.opt.player, self.game.id)) 653 elif mode == 302: 654 # reset player stats for current game 655 if self.game.areYouSure( 656 _("Reset game statistics"), 657 _('Reset statistics and logs for player\n%(player)s\n' 658 'and game\n%(game)s?') % transkw, 659 confirm=1, default=1 660 ): 661 self.app.stats.resetStats(player, self.game.id) 662 self.game.updateStatus(stats=self.app.stats.getStats( 663 self.app.opt.player, self.game.id)) 664 elif mode == 401: 665 # start a new game with a gameid 666 if gameid and gameid != self.game.id: 667 self.game.endGame() 668 self.game.quitGame(gameid) 669 elif mode == 402: 670 # start a new game with a gameid / gamenumber 671 # TODO 672 pass 673 else: 674 print_err("stats problem: %s %s %s" % (mode, demo, player)) 675 pass 676 if d.status != 0: 677 break 678 mode = d.button 679 if self.game.pause: 680 if wasPaused: 681 self.game.doPause() 682 683 # 684 # Assist menu 685 # 686 687 def mHint(self, *args): 688 if self._cancelDrag(): 689 return 690 if self.app.opt.hint: 691 if self.game.showHint(0, self.app.opt.timeouts['hint']): 692 self.game.stats.hints += 1 693 694 def mHint1(self, *args): 695 if self._cancelDrag(): 696 return 697 if self.app.opt.hint: 698 if self.game.showHint(1, self.app.opt.timeouts['hint']): 699 self.game.stats.hints += 1 700 701 def mHighlightPiles(self, *args): 702 if self._cancelDrag(): 703 return 704 if self.app.opt.highlight_piles: 705 if self.game.highlightPiles( 706 self.app.opt.timeouts['highlight_piles'] 707 ): 708 self.game.stats.highlight_piles += 1 709 710 def mDemo(self, *args): 711 if self._cancelDrag(): 712 return 713 if self.game.getHintClass() is not None: 714 self._mDemo(mixed=0) 715 716 def mMixedDemo(self, *args): 717 if self._cancelDrag(): 718 return 719 self._mDemo(mixed=1) 720 721 def _mDemo(self, mixed): 722 if self.changed(): 723 # only ask if there have been no demo moves or hints yet 724 if self.game.stats.demo_moves == 0 and self.game.stats.hints == 0: 725 if not self.game.areYouSure(_("Play demo")): 726 return 727 # self.app.demo_counter = 0 728 self.game.startDemo(mixed=mixed) 729 730 # 731 # Options menu 732 # 733 734 def mOptPlayerOptions(self, *args): 735 if self._cancelDrag(break_pause=False): 736 return 737 d = PlayerOptionsDialog(self.top, _("Set player options"), self.app) 738 if d.status == 0 and d.button == 0: 739 self.app.opt.confirm = bool(d.confirm) 740 self.app.opt.update_player_stats = bool(d.update_stats) 741 self.app.opt.win_animation = bool(d.win_animation) 742 # n = string.strip(d.player) 743 n = d.player[:30].strip() 744 if 0 < len(n) <= 30: 745 self.app.opt.player = n 746 self.game.updateStatus(player=self.app.opt.player) 747 self.game.updateStatus(stats=self.app.stats.getStats( 748 self.app.opt.player, self.game.id)) 749 750 def mOptColors(self, *args): 751 if self._cancelDrag(break_pause=False): 752 return 753 d = ColorsDialog(self.top, _("Set colors"), self.app) 754 text_color = self.app.opt.colors['text'] 755 if d.status == 0 and d.button == 0: 756 self.app.opt.colors['text'] = d.text_color 757 self.app.opt.colors['piles'] = d.piles_color 758 self.app.opt.colors['cards_1'] = d.cards_1_color 759 self.app.opt.colors['cards_2'] = d.cards_2_color 760 self.app.opt.colors['samerank_1'] = d.samerank_1_color 761 self.app.opt.colors['samerank_2'] = d.samerank_2_color 762 self.app.opt.colors['hintarrow'] = d.hintarrow_color 763 self.app.opt.colors['not_matching'] = d.not_matching_color 764 # 765 if text_color != self.app.opt.colors['text']: 766 self.app.setTile(self.app.tabletile_index, force=True) 767 768 def mOptFonts(self, *args): 769 if self._cancelDrag(break_pause=False): 770 return 771 d = FontsDialog(self.top, _("Set fonts"), self.app) 772 if d.status == 0 and d.button == 0: 773 self.app.opt.fonts.update(d.fonts) 774 self._cancelDrag() 775 self.game.endGame(bookmark=1) 776 self.game.quitGame(bookmark=1) 777 778 def mOptTimeouts(self, *args): 779 if self._cancelDrag(break_pause=False): 780 return 781 d = TimeoutsDialog(self.top, _("Set timeouts"), self.app) 782 if d.status == 0 and d.button == 0: 783 self.app.opt.timeouts['demo'] = d.demo_timeout 784 self.app.opt.timeouts['hint'] = d.hint_timeout 785 self.app.opt.timeouts['raise_card'] = d.raise_card_timeout 786 self.app.opt.timeouts['highlight_piles'] = \ 787 d.highlight_piles_timeout 788 self.app.opt.timeouts['highlight_cards'] = \ 789 d.highlight_cards_timeout 790 self.app.opt.timeouts['highlight_samerank'] = \ 791 d.highlight_samerank_timeout 792 793 # 794 # Help menu 795 # 796 797 def mHelpHtml(self, *args): 798 print('mHelpHtml: %s' % str(args)) 799 if self._cancelDrag(break_pause=False): 800 return 801 help_html(self.app, args[0], "html") 802 803 def mHelp(self, *args): 804 if self._cancelDrag(break_pause=False): 805 return 806 help_html(self.app, "index.html", "html") 807 808 def mHelpHowToPlay(self, *args): 809 if self._cancelDrag(break_pause=False): 810 return 811 help_html(self.app, "howtoplay.html", "html") 812 813 def mHelpRules(self, *args): 814 if self._cancelDrag(break_pause=False): 815 return 816 if not self.menustate.rules: 817 return 818 dir = os.path.join("html", "rules") 819 # FIXME: plugins 820 help_html(self.app, self.app.getGameRulesFilename(self.game.id), dir) 821 822 def mHelpLicense(self, *args): 823 if self._cancelDrag(break_pause=False): 824 return 825 help_html(self.app, "license.html", "html") 826 827 def mHelpNews(self, *args): 828 if self._cancelDrag(break_pause=False): 829 return 830 help_html(self.app, "news.html", "html") 831 832 def mHelpWebSite(self, *args): 833 openURL(PACKAGE_URL) 834 835 def mHelpAbout(self, *args): 836 if self._cancelDrag(break_pause=False): 837 return 838 help_about(self.app) 839 840 # 841 # misc 842 # 843 844 def mScreenshot(self, *args): 845 if self._cancelDrag(): 846 return 847 f = os.path.join(self.app.dn.config, "screenshots") 848 if not os.path.isdir(f): 849 return 850 f = os.path.join(f, self.app.getGameSaveName(self.game.id)) 851 i = 1 852 while 1: 853 fn = "%s-%d.ppm" % (f, i) 854 if not os.path.exists(fn): 855 break 856 i = i + 1 857 if i >= 10000: # give up 858 return 859 self.top.screenshot(fn) 860 861 def mPlayNextMusic(self, *args): 862 if self._cancelDrag(break_pause=False): 863 return 864 if (self.app.audio and self.app.music and 865 self.app.opt.sound_music_volume > 0): 866 self.app.audio.playNextMusic() 867 if 1 and DEBUG: 868 index = self.app.audio.getMusicInfo() 869 music = self.app.music_manager.get(index) 870 if music: 871 print("playing music:", music.filename) 872 873 def mIconify(self, *args): 874 if self._cancelDrag(break_pause=False): 875 return 876 self.top.wm_iconify() 877 878 879# ************************************************************************ 880# * toolbar 881# ************************************************************************ 882 883class PysolToolbar(PysolToolbarTk): 884 def __init__(self, *args, **kwargs): 885 self.game = None 886 PysolToolbarTk.__init__(self, *args, **kwargs) 887 888 # 889 # public methods 890 # 891 892 def connectGame(self, game): 893 self.game = game 894 895 # 896 # button event handlers - delegate to menubar 897 # 898 899 def mNewGame(self, *args): 900 if not self._busy(): 901 self.menubar.mNewGame() 902 return 1 903 904 def mOpen(self, *args): 905 if not self._busy(): 906 self.menubar.mOpen() 907 return 1 908 909 def mRestart(self, *args): 910 if not self._busy(): 911 self.menubar.mRestart() 912 return 1 913 914 def mSave(self, *args): 915 if not self._busy(): 916 self.menubar.mSaveAs() 917 return 1 918 919 def mUndo(self, *args): 920 if not self._busy(): 921 self.menubar.mUndo() 922 return 1 923 924 def mRedo(self, *args): 925 if not self._busy(): 926 self.menubar.mRedo() 927 return 1 928 929 def mDrop(self, *args): 930 if not self._busy(): 931 self.menubar.mDrop() 932 return 1 933 934 def mShuffle(self, *args): 935 if not self._busy(): 936 self.menubar.mShuffle() 937 return 1 938 939 def mPause(self, *args): 940 if not self._busy(): 941 self.menubar.mPause() 942 return 1 943 944 def mPlayerStats(self, *args): 945 if not self._busy(): 946 self.menubar.mPlayerStats() 947 return 1 948 949 def mHelpRules(self, *args): 950 if not self._busy(): 951 self.menubar.mHelpRules() 952 return 1 953 954 def mQuit(self, *args): 955 if not self._busy(): 956 self.menubar.mQuit() 957 return 1 958 959 def mOptPlayerOptions(self, *args): 960 if not self._busy(): 961 self.menubar.mOptPlayerOptions() 962 return 1 963