## vim:ts=4:et:nowrap
##
##---------------------------------------------------------------------------##
##
## PySol -- a Python Solitaire game
##
## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer
## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer
## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer
## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer
## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer
## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer
## All Rights Reserved.
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; see the file COPYING.
## If not, write to the Free Software Foundation, Inc.,
## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
##
## Markus F.X.J. Oberhumer
## <markus@oberhumer.com>
## http://www.oberhumer.com/pysol
##
##---------------------------------------------------------------------------##


# imports
import os, re, sys, string, time, types

# PySol imports
from mfxtools import *
from mfxutil import EnvError, SubclassResponsibility
from mfxutil import Struct, destruct, openURL
from util import PACKAGE, PACKAGE_URL, VERSION, bundle
from random import constructRandom

# stats imports
from stats import Status_StatsDialog, PysolStatsFormatter
from pysoltk import SingleGame_StatsDialog, AllGames_StatsDialog
from pysoltk import FullLog_StatsDialog, SessionLog_StatsDialog

# toolkit imports
from pysoltk import EVENT_HANDLED, EVENT_PROPAGATE
from pysoltk import MfxDialog, MfxSimpleSlider, MfxSimpleEntry
from pysoltk import MfxExceptionDialog
from pysoltk import MfxCheckMenuItem, MfxRadioMenuItem
from pysoltk import getFont
from pysoltk import PlayerOptionsDialog
from pysoltk import SoundOptionsDialog
from pysoltk import DemoOptionsDialog, HintOptionsDialog
from pysoltk import EditTextDialog
from help import helpAbout, helpAboutSimple, helpHTML


# /***********************************************************************
# // menubar
# ************************************************************************/

class PysolMenubarActions:
    def __init__(self, app, top):
        self.app = app
        self.top = top
        self.game = None
        # enabled/disabled - this is set by updateMenuState()
        self.menustate = Struct(
            save = 0,
            save_as = 0,
            hold_and_quit = 0,
            undo = 0,
            redo = 0,
            restart = 0,
            deal = 0,
            hint = 0,
            autofaceup = 0,
            autodrop = 0,
            autodeal = 0,
            quickplay = 0,
            demo = 0,
            highlight_piles = 0,
            rules = 0,
        )
        # structure to convert menu-options to Toolkit variables
        self.tkopt = Struct(
            gameid = MfxRadioMenuItem(self),
            gameid_popular = MfxRadioMenuItem(self),
            comment = MfxCheckMenuItem(self),
            autofaceup = MfxCheckMenuItem(self),
            autodrop = MfxCheckMenuItem(self),
            autodeal = MfxCheckMenuItem(self),
            quickplay = MfxCheckMenuItem(self),
            undo = MfxCheckMenuItem(self),
            bookmarks = MfxCheckMenuItem(self),
            hint = MfxCheckMenuItem(self),
            highlight_piles = MfxCheckMenuItem(self),
            highlight_cards = MfxCheckMenuItem(self),
            highlight_samerank = MfxCheckMenuItem(self),
            sound = MfxCheckMenuItem(self),
            cardback = MfxRadioMenuItem(self),
            tabletile = MfxRadioMenuItem(self),
            animations = MfxRadioMenuItem(self),
            shadow = MfxCheckMenuItem(self),
            shade = MfxCheckMenuItem(self),
            toolbar = MfxRadioMenuItem(self),
            toolbar_size = MfxRadioMenuItem(self),
            toolbar_relief = MfxRadioMenuItem(self),
            statusbar = MfxCheckMenuItem(self),
        )

    def connectGame(self, game):
        self.game = game
        if game is None:
            return
        assert self.app is game.app
        tkopt, opt = self.tkopt, self.app.opt
        # set state of the menu items
        tkopt.gameid.set(game.id)
        tkopt.gameid_popular.set(game.id)
        tkopt.comment.set(bool(game.gsaveinfo.comment))
        tkopt.autofaceup.set(opt.autofaceup)
        tkopt.autodrop.set(opt.autodrop)
        tkopt.autodeal.set(opt.autodeal)
        tkopt.quickplay.set(opt.quickplay)
        tkopt.undo.set(opt.undo)
        tkopt.hint.set(opt.hint)
        tkopt.bookmarks.set(opt.bookmarks)
        tkopt.highlight_piles.set(opt.highlight_piles)
        tkopt.highlight_cards.set(opt.highlight_cards)
        tkopt.highlight_samerank.set(opt.highlight_samerank)
        tkopt.sound.set(opt.sound)
        tkopt.cardback.set(self.app.cardset.backindex)
        tkopt.tabletile.set(self.app.tabletile_index),
        tkopt.animations.set(opt.animations)
        tkopt.shadow.set(opt.shadow)
        tkopt.shade.set(opt.shade)
        tkopt.toolbar.set(opt.toolbar)
        tkopt.toolbar_size.set(opt.toolbar_size)
        tkopt.toolbar_relief.set(opt.toolbar_relief)
        tkopt.statusbar.set(opt.statusbar)

    # will get called after connectGame()
    def updateRecentGamesMenu(self, gameids):
        pass

    def updateBookmarkMenuState(self):
        pass

    # will get called after a new cardset has been loaded
    def updateBackgroundImagesMenu(self):
        pass


    #
    # delegation to Game
    #

    def _finishDrag(self):
        return self.game is None or self.game._finishDrag()

    def _cancelDrag(self):
        return self.game is None or self.game._cancelDrag()

    def changed(self, *args, **kw):
        assert self.game is not None
        return apply(self.game.changed, args, kw)


    #
    # menu updates
    #

    def setMenuState(self, state, path):
        raise SubclassResponsibility

    def setToolbarState(self, state, path):
        raise SubclassResponsibility

    def _clearMenuState(self):
        ms = self.menustate
        for k, v in ms.__dict__.items():
            if type(v) is types.ListType:
                ms.__dict__[k] = [0] * len(v)
            else:
                ms.__dict__[k] = 0

    # update self.menustate for menu items and toolbar
    def _updateMenuState(self):
        self._clearMenuState()
        game = self.game
        assert game is not None
        opt = self.app.opt
        ms = self.menustate
        # 0 = DISABLED, 1 = ENABLED
        ms.save_as = game.canSaveGame()
        ms.hold_and_quit = ms.save_as
        if game.filename and ms.save_as:
            ms.save = 1
        if opt.undo:
            if game.canUndo() and game.moves.index > 0:
                ms.undo = 1
            if game.canRedo() and game.moves.index < len(game.moves.history):
                ms.redo = 1
        if game.moves.index > 0:
            ms.restart = 1
        if game.canDealCards():
            ms.deal = 1
        if game.getHintClass() is not None:
            if opt.hint:
                ms.hint = 1
            ###if not game.demo:       # if not already running
            ms.demo = 1
        autostacks = game.getAutoStacks()
        if autostacks[0]:
            ms.autofaceup = 1
        if autostacks[1] and game.s.foundations:
            ms.autodrop = 1
        if game.s.waste:
            ms.autodeal = 1
        if autostacks[2]:
            ms.quickplay = 1
        ms.highlight_piles = 0
        if opt.highlight_piles and game.getHighlightPilesStacks():
            ms.highlight_piles = 1
        if game.app.getGameRulesFilename(game.id):  # note: this may return ""
            ms.rules = 1

    # update menu items and toolbar
    def _updateMenus(self):
        if self.game is None:
            return
        ms = self.menustate
        # File menu
        self.setMenuState(ms.save, "file.save")
        self.setMenuState(ms.save_as, "file.saveas")
        self.setMenuState(ms.hold_and_quit, "file.holdandquit")
        # Edit menu
        self.setMenuState(ms.undo, "edit.undo")
        self.setMenuState(ms.redo, "edit.redo")
        self.setMenuState(ms.redo, "edit.redoall")
        self.updateBookmarkMenuState()
        self.setMenuState(ms.restart, "edit.restartgame")
        # Game menu
        self.setMenuState(ms.deal, "game.dealcards")
        self.setMenuState(ms.autodrop, "game.autodrop")
        # Assist menu
        self.setMenuState(ms.hint, "assist.hint")
        self.setMenuState(ms.highlight_piles, "assist.highlightpiles")
        self.setMenuState(ms.demo, "assist.demo")
        self.setMenuState(ms.demo, "assist.demoallgames")
        # Options menu
        self.setMenuState(ms.autofaceup, "options.automaticplay.autofaceup")
        self.setMenuState(ms.autodrop, "options.automaticplay.autodrop")
        self.setMenuState(ms.autodeal, "options.automaticplay.autodeal")
        self.setMenuState(ms.quickplay, "options.automaticplay.quickplay")
        # Help menu
        self.setMenuState(ms.rules, "help.rulesforthisgame")
        # Toolbar
        self.setToolbarState(ms.restart, "restart")
        self.setToolbarState(ms.save_as, "save")
        self.setToolbarState(ms.undo, "undo")
        self.setToolbarState(ms.redo, "redo")
        self.setToolbarState(ms.autodrop, "autodrop")
        self.setToolbarState(ms.rules, "rules")
        #
        self.tkopt.comment.set(bool(self.game.gsaveinfo.comment))

    # update menu items and toolbar
    def updateMenus(self):
        if self.game is None:
            return
        self._updateMenuState()
        self._updateMenus()

    # disable menu items and toolbar
    def disableMenus(self):
        if self.game is None:
            return
        self._clearMenuState()
        self._updateMenus()


    #
    # File menu
    #

    def mNewGame(self, *args):
        if self._cancelDrag(): return
        if self.changed():
            if not self.game.areYouSure("New game"): return
        if self.game.nextGameFlags(self.game.id) == 0:
            self.game.endGame()
            self.game.newGame()
        else:
            self.game.endGame()
            self.game.quitGame(self.game.id)

    def _mSelectGame(self, id, random=None):
        if self._cancelDrag(): return
        if self.game.id == id:
            return
        if self.changed():
            if not self.game.areYouSure("Select game"):
                # restore radiobutton settings
                self.tkopt.gameid.set(self.game.id)
                self.tkopt.gameid_popular.set(self.game.id)
                return
        self.game.endGame()
        self.game.quitGame(id, random=random)

    def mSelectGame(self, *args):
        self._mSelectGame(self.tkopt.gameid.get())

    def mSelectGamePopular(self, *args):
        self._mSelectGame(self.tkopt.gameid_popular.get())

    def _mNewGameBySeed(self, seed, origin):
        try:
            id, random = constructRandom(seed)
            if id is None:
                id = self.game.id
            if random is None:
                return
            if not self.app.getGameInfo(id):
                raise ValueError
        except (ValueError, TypeError), ex:
            d = MfxDialog(self.top, title="Invalid game number",
                          text="Invalid game number\n" + str(seed),
                          bitmap="error")
            return
        f = self.game.nextGameFlags(id, random)
        if f & 17 == 0:
            return
        random.origin = origin
        if f & 15 == 0:
            self.game.endGame()
            self.game.newGame(random=random)
        else:
            self.game.endGame()
            self.game.quitGame(id, random=random)

    def mNewGameWithNextId(self, *args):
        if self._cancelDrag(): return
        if self.changed():
            if not self.game.areYouSure("Select next game number"): return
        r = self.game.random
        seed = r.increaseSeed(r.initial_seed)
        seed = r.str(seed)
        self._mNewGameBySeed(seed, self.game.random.ORIGIN_NEXT_GAME)

    def mSelectGameById(self, *args):
        if self._cancelDrag(): return
        id, f = None, self.game.getGameNumber(format=0)
        d = MfxSimpleEntry(self.top, "Select new game number", "\n\nEnter new game number", f,
                           strings=("OK", "Next number", "Cancel"), default=0,
                           e_width=25)
        if d.status != 0: return
        if d.button == 2: return
        if d.button == 1:
            self.mNewGameWithNextId()
            return
        if self.changed():
            if not self.game.areYouSure("Select new game number"): return
        self._mNewGameBySeed(d.value, self.game.random.ORIGIN_SELECTED)

    def mSelectRandomGame(self, *args):
        if self._cancelDrag(): return
        if self.changed():
            if not self.game.areYouSure("Select random game"): return
        for i in range(1000):       # just in case, don't loop forever
            gi = self.app.getGameInfo(self.app.getRandomGameId())
            if gi is None:
                continue
            if 1 and gi.id == self.game.id:
                # force change of game
                continue
            if 1 and gi.category != self.game.gameinfo.category:
                # don't change game category
                continue
            break
        if gi and gi.id != self.game.id:
            self.game.endGame()
            self.game.quitGame(gi.id)

    def _mSelectNextGameFromList(self, gl, step):
        if self._cancelDrag(): return
        id = self.game.id
        gl = list(gl)
        if len(gl) < 2 or not id in gl:
            return
        if self.changed():
            if not self.game.areYouSure("Select next game"): return
        index = (gl.index(id) + step) % len(gl)
        self.game.endGame()
        self.game.quitGame(gl[index])

    def mSelectNextGameById(self, *args):
        self._mSelectNextGameFromList(self.app.gdb.getGamesIdSortedById(), 1)

    def mSelectPrevGameById(self, *args):
        self._mSelectNextGameFromList(self.app.gdb.getGamesIdSortedById(), -1)

    def mSelectNextGameByName(self, *args):
        self._mSelectNextGameFromList(self.app.gdb.getGamesIdSortedByName(), 1)

    def mSelectPrevGameByName(self, *args):
        self._mSelectNextGameFromList(self.app.gdb.getGamesIdSortedByName(), -1)

    def mSave(self, *args):
        if self._cancelDrag(): return
        if self.menustate.save_as:
            if self.game.filename:
                self.game.saveGame(self.game.filename)
            else:
                self.mSaveAs()

    def mHoldAndQuit(self, *args):
        if self._cancelDrag(): return
        self.game.endGame(holdgame=1)
        self.game.quitGame(holdgame=1)

    def mQuit(self, *args):
        if self._cancelDrag(): return
        if self.changed():
            if not self.game.areYouSure("Quit PySol"): return
        self.game.endGame()
        self.game.quitGame()


    #
    # Edit menu
    #

    def mUndo(self, *args):
        if self._cancelDrag(): return
        if self.menustate.undo:
            self.game.playSample("undo")
            self.game.undo()

    def mRedo(self, *args):
        if self._cancelDrag(): return
        if self.menustate.redo:
            self.game.playSample("redo")
            self.game.redo()
            self.game.checkForWin()

    def mRedoAll(self, *args):
        if self._cancelDrag(): return
        if self.menustate.redo:
            self.game.playSample("redo", loop=1)
            while self.game.moves.index < len(self.game.moves.history):
                self.game.redo()
                if self.game.checkForWin():
                    break
            self.game.stopSamples()

    def mSetBookmark(self, n, confirm=1):
        if self._cancelDrag(): return
        if not self.app.opt.bookmarks: return
        if not (0 <= n <= 8): return
        self.game.setBookmark(n, confirm=confirm)
        self.game.updateMenus()

    def mGotoBookmark(self, n, confirm=-1):
        if self._cancelDrag(): return
        if not self.app.opt.bookmarks: return
        if not (0 <= n <= 8): return
        self.game.gotoBookmark(n, confirm=confirm)
        self.game.updateMenus()

    def mClearBookmarks(self, *args):
        if self._cancelDrag(): return
        if not self.app.opt.bookmarks: return
        if not self.game.gsaveinfo.bookmarks: return
        if not self.game.areYouSure("Clear bookmarks","Clear all bookmarks ?"): return
        self.game.gsaveinfo.bookmarks = {}
        self.game.updateMenus()

    def mRestart(self, *args):
        if self._cancelDrag(): return
        if self.game.moves.index == 0:
            return
        if self.changed(restart=1):
            if not self.game.areYouSure("Restart game","Restart this game ?"): return
        self.game.restartGame()


    #
    # Game menu
    #

    def mDeal(self, *args):
        if self._cancelDrag(): return
        self.game.dealCards()

    def mDrop(self, *args):
        if self._cancelDrag(): return
        self.game.autoPlay(autofaceup=-1, autodrop=1)

    def mDrop1(self, *args):
        if self._cancelDrag(): return
        self.game.autoPlay(autofaceup=1, autodrop=1)

    def mStatus(self, *args):
        if self._cancelDrag(): return
        self.mPlayerStats(mode=100)

    def mEditGameComment(self, *args):
        if self._cancelDrag(): return
        game, gi = self.game, self.game.gameinfo
        t = " " + game.getGameNumber(format=1)
        cc = "Comments for " + gi.name + t + ":\n\n"
        c = game.gsaveinfo.comment or cc
        d = EditTextDialog(game.top, "Comments for"+t, text=c)
        if d.status == 0 and d.button == 0:
            if string.strip(d.text) == string.strip(cc):
                game.gsaveinfo.comment = ""
            else:
                game.gsaveinfo.comment = string.rstrip(d.text)
        self.tkopt.comment.set(bool(game.gsaveinfo.comment))


    #
    # Game menu - statistics
    #

    def _mStatsSave(self, player, header, filename, write_method):
        file = None
        if player is None:
            text = "Demo statistics"
            filename = filename + "_demo"
        else:
            text = "Your statistics"
        filename = os.path.join(self.app.dn.config, filename + ".txt")
        filename = os.path.normpath(filename)
        try:
            file = open(filename, "a")
            a = PysolStatsFormatter(self.app)
            writer = a.FileWriter(file)
            apply(write_method, (a, writer, player, header))
            destruct(a)
        except EnvError, ex:
            if file: file.close()
            d = MfxExceptionDialog(self.top, ex,
                              text="Error while writing to file")
        else:
            if file: file.close()
            d = MfxDialog(self.top, title=PACKAGE+" Info", bitmap="info",
                          text=text + " were appended to\n\n" + filename)


    def mPlayerStats(self, *args, **kw):
        mode = kw.get("mode", 101)
        demo = 0
        while mode > 0:
            if mode > 1000:
                demo = not demo
                mode = mode % 1000
            #
            d = Struct(status=-1, button=-1)
            if demo:
                player = None
                p0, p1, p2 = PACKAGE+" Demo", PACKAGE+" Demo ", ""
            else:
                player = self.app.opt.player
                p0, p1, p2 = player, "", " for " + player
            n = self.game.gameinfo.short_name
            #
            if mode == 100:
                d = Status_StatsDialog(self.top, game=self.game)
            elif mode == 101:
                header = p1 + "Statistics for " + n
                d = SingleGame_StatsDialog(self.top, header, self.app, player, gameid=self.game.id)
            elif mode == 102:
                header = p1 + "Statistics" + p2
                d = AllGames_StatsDialog(self.top, header, self.app, player)
            elif mode == 103:
                header = p1 + "Full log" + p2
                d = FullLog_StatsDialog(self.top, header, self.app, player)
            elif mode == 104:
                header = p1 + "Session log" + p2
                d = SessionLog_StatsDialog(self.top, header, self.app, player)
            elif mode == 202:
                # print stats to file
                header = "Statistics for " + p0
                write_method = PysolStatsFormatter.writeStats
                self._mStatsSave(player, header, "stats", write_method)
            elif mode == 203:
                # print full log to file
                header = "Full log for " + p0
                write_method = PysolStatsFormatter.writeFullLog
                self._mStatsSave(player, header, "log", write_method)
            elif mode == 204:
                # print session log to file
                header = "Session log for " + p0
                write_method = PysolStatsFormatter.writeSessionLog
                self._mStatsSave(player, header, "log", write_method)
            elif mode == 301:
                # reset all player stats
                if self.game.areYouSure("Reset all statistics",
                                        "Reset ALL statistics and logs for player\n" + p0 + " ?",
                                        confirm=1, default=1):
                    self.app.stats.resetStats(player, 0)
                    self.game.updateStatus(stats=self.app.stats.getStats(self.app.opt.player, self.game.id))
            elif mode == 302:
                # reset player stats for current game
                if self.game.areYouSure("Reset game statistics",
                                        "Reset statistics and logs for player\n" + p0 + "\nand game\n" + n +" ?",
                                        confirm=1, default=1):
                    self.app.stats.resetStats(player, self.game.id)
                    self.game.updateStatus(stats=self.app.stats.getStats(self.app.opt.player, self.game.id))
            elif mode == 401:
                # start a new game with a gameid
                ## TODO
                pass
            elif mode == 402:
                # start a new game with a gameid / gamenumber
                ## TODO
                pass
            else:
                print "stats problem:", mode, demo, player
                pass
            if d.status != 0:
                break
            mode = d.button


    #
    # Assist menu
    #

    def mHint(self, *args):
        if self._cancelDrag(): return
        if self.app.opt.hint:
            if self.game.showHint(0, self.app.opt.hint_sleep):
                self.game.stats.hints = self.game.stats.hints + 1

    def mHint1(self, *args):
        if self._cancelDrag(): return
        if self.app.opt.hint:
            if self.game.showHint(1, self.app.opt.hint_sleep):
                self.game.stats.hints = self.game.stats.hints + 1

    def mHighlightPiles(self, *args):
        if self._cancelDrag(): return
        if self.app.opt.highlight_piles:
            if self.game.highlightPiles(self.app.opt.highlight_piles_sleep):
                self.game.stats.highlight_piles = self.game.stats.highlight_piles + 1

    def mDemo(self, *args):
        if self._cancelDrag(): return
        if self.game.getHintClass() is not None:
            self._mDemo(mixed=0)

    def mMixedDemo(self, *args):
        if self._cancelDrag(): return
        self._mDemo(mixed=1)

    def _mDemo(self, mixed):
        if self._cancelDrag(): return
        if self.changed():
            # only ask if there have been no demo moves or hints yet
            if self.game.stats.demo_moves == 0 and self.game.stats.hints == 0:
                if not self.game.areYouSure("Play demo"): return
        ##self.app.demo_counter = 0
        self.game.startDemo(mixed=mixed)


    #
    # Options menu
    #

    def mOptPlayerOptions(self, *args):
        if self._cancelDrag(): return
        d = PlayerOptionsDialog(self.top, "Set player options", self.app)
        if d.status == 0 and d.button == 0:
            self.app.opt.confirm = bool(d.confirm)
            self.app.opt.update_player_stats = bool(d.update_stats)
            self.app.opt.win_animation = bool(d.win_animation)
            ##n = string.strip(d.player)
            n = string.strip(d.player[:30])
            if 0 < len(n) <= 30:
                self.app.opt.player = n
                self.game.updateStatus(player=self.app.opt.player)
                self.game.updateStatus(stats=self.app.stats.getStats(self.app.opt.player, self.game.id))

    def mOptAutoFaceUp(self, *args):
        if self._cancelDrag(): return
        self.app.opt.autofaceup = self.tkopt.autofaceup.get()
        if self.app.opt.autofaceup:
            self.game.autoPlay()

    def mOptAutoDrop(self, *args):
        if self._cancelDrag(): return
        self.app.opt.autodrop = self.tkopt.autodrop.get()
        if self.app.opt.autodrop:
            self.game.autoPlay()

    def mOptAutoDeal(self, *args):
        if self._cancelDrag(): return
        self.app.opt.autodeal = self.tkopt.autodeal.get()
        if self.app.opt.autodeal:
            self.game.autoPlay()

    def mOptQuickPlay(self, *args):
        if self._cancelDrag(): return
        self.app.opt.quickplay = self.tkopt.quickplay.get()

    def mOptEnableUndo(self, *args):
        if self._cancelDrag(): return
        self.app.opt.undo = self.tkopt.undo.get()
        self.game.updateMenus()

    def mOptEnableBookmarks(self, *args):
        if self._cancelDrag(): return
        self.app.opt.bookmarks = self.tkopt.bookmarks.get()
        self.game.updateMenus()

    def mOptEnableHint(self, *args):
        if self._cancelDrag(): return
        self.app.opt.hint = self.tkopt.hint.get()
        self.game.updateMenus()

    def mOptEnableHighlightPiles(self, *args):
        if self._cancelDrag(): return
        self.app.opt.highlight_piles = self.tkopt.highlight_piles.get()
        self.game.updateMenus()

    def mOptEnableHighlightCards(self, *args):
        if self._cancelDrag(): return
        self.app.opt.highlight_cards = self.tkopt.highlight_cards.get()
        self.game.updateMenus()

    def mOptEnableHighlightSameRank(self, *args):
        if self._cancelDrag(): return
        self.app.opt.highlight_samerank = self.tkopt.highlight_samerank.get()
        self.game.updateMenus()

    def mOptSound(self, *args):
        if self._cancelDrag(): return
        self.app.opt.sound = self.tkopt.sound.get()
        if not self.app.opt.sound:
            self.app.audio.stopAll()

    def mOptSoundDialog(self, *args):
        if self._cancelDrag(): return
        d = SoundOptionsDialog(self.top, "Sound settings", self.app)
        self.tkopt.sound.set(self.app.opt.sound)

    def mOptAnimations(self, *args):
        if self._cancelDrag(): return
        self.app.opt.animations = self.tkopt.animations.get()

    def mOptShadow(self, *args):
        if self._cancelDrag(): return
        self.app.opt.shadow = self.tkopt.shadow.get()

    def mOptShade(self, *args):
        if self._cancelDrag(): return
        self.app.opt.shade = self.tkopt.shade.get()

    def mOptIrregularPiles(self, *args):
        if self._cancelDrag(): return
        self.app.opt.irregular_piles = self.tkopt.irregular_piles.get()

    def mOptDemoOptions(self, *args):
        if self._cancelDrag(): return
        d = DemoOptionsDialog(self.top, "Set demo options", self.app)
        if d.status == 0 and d.button == 0:
            self.app.opt.demo_logo = d.demo_logo
            self.app.opt.demo_score = d.demo_score
            self.app.opt.demo_sleep = d.demo_sleep

    def mOptHintOptions(self, *args):
        if self._cancelDrag(): return
        d = HintOptionsDialog(self.top, "Set hint options", self.app)
        if d.status == 0 and d.button == 0:
            self.app.opt.hint_sleep = d.hint_sleep

    def mOptSave(self, *args):
        if self._cancelDrag(): return
        try:
            self.app.saveOptions()
        except Exception, ex:
            d = MfxExceptionDialog(self.top, ex,
                                   text="Error while saving options")
        else:
            # tell the player where their config files reside
            d = MfxDialog(self.top, title=PACKAGE+" Info",
                          text="Options were saved to\n\n" + self.app.fn.opt,
                          bitmap="info")

    #
    # Help menu
    #

    def mHelp(self, *args):
        if self._cancelDrag(): return
        helpHTML(self.app, "index.html", "html")

    def mHelpHowToPlay(self, *args):
        if self._cancelDrag(): return
        helpHTML(self.app, "howtoplay.html", "html")

    def mHelpRules(self, *args):
        if self._cancelDrag(): return
        if not self.menustate.rules:
            return
        dir = os.path.join("html", "rules")
        ## FIXME: plugins
        helpHTML(self.app, self.app.getGameRulesFilename(self.game.id), dir)

    def mHelpLicense(self, *args):
        if self._cancelDrag(): return
        helpHTML(self.app, "license.html", "html")

    def mHelpNews(self, *args):
        if self._cancelDrag(): return
        helpHTML(self.app, "news.html", "html")

    def mHelpWebSite(self, *args):
        openURL(PACKAGE_URL)

    def mHelpAbout(self, *args):
        if self._cancelDrag(): return
        if self.app and bundle & 4:
            helpAboutSimple(self.app)
        else:
            helpAbout(self.app)


    #
    # misc
    #

    def mScreenshot(self, *args):
        if self._cancelDrag(): return
        f = os.path.join(self.app.dn.config, "screenshots")
        if not os.path.isdir(f):
            return
        f = os.path.join(f, self.app.getGameSaveName(self.game.id))
        i = 1
        while 1:
            fn = f + ("-%d.ppm" % i)
            if not os.path.isfile(fn):
                break
            i = i + 1
            if i >= 10000:      # give up
                return
        self.top.screenshot(fn)

    def mPlayNextMusic(self, *args):
        if self._cancelDrag(): return
        if self.app.audio and self.app.opt.sound_music_volume > 0:
            self.app.audio.playNextMusic()
            if 1 and self.app.debug:
                index = self.app.audio.getMusicInfo()
                music = self.app.music_manager.get(index)
                if music:
                    print "playing music:", music.filename


# /***********************************************************************
# // toolbar
# ************************************************************************/

class PysolToolbarActions:
    def __init__(self):
        self.game = None
        self.menubar = None

    #
    # public methods
    #

    def connectGame(self, game, menubar):
        self.game = game
        self.menubar = menubar


    #
    # button event handlers - delegate to menubar
    #

    def _busy(self):
        raise SubclassResponsibility

    def mNewGame(self, *args):
        if not self._busy():
            self.menubar.mNewGame()
        return 1

    def mOpen(self, *args):
        if not self._busy():
            self.menubar.mOpen()
        return 1

    def mRestart(self, *args):
        if not self._busy():
            self.menubar.mRestart()
        return 1

    def mSave(self, *args):
        if not self._busy():
            self.menubar.mSaveAs()
        return 1

    def mUndo(self, *args):
        if not self._busy():
            self.menubar.mUndo()
        return 1

    def mRedo(self, *args):
        if not self._busy():
            self.menubar.mRedo()
        return 1

    def mDrop(self, *args):
        if not self._busy():
            self.menubar.mDrop()
        return 1

    def mStatus(self, *args):
        if not self._busy():
            self.menubar.mStatus()
        return 1

    def mPlayerStats(self, *args):
        if not self._busy():
            self.menubar.mPlayerStats()
        return 1

    def mHelpRules(self, *args):
        if not self._busy():
            self.menubar.mHelpRules()
        return 1

    def mQuit(self, *args):
        if not self._busy():
            self.menubar.mQuit()
        return 1

    def mOptPlayerOptions(self, *args):
        if not self._busy():
            self.menubar.mOptPlayerOptions()
        return 1

