## 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, locale

# PySol imports
from mfxutil import EnvError, SubclassResponsibility
from mfxutil import Struct, openURL
from pysolrandom import constructRandom
from settings import PACKAGE, PACKAGE_URL
from settings import TOP_TITLE
from settings import DEBUG

# stats imports
from stats import FileStatsFormatter
from pysoltk import SingleGame_StatsDialog, AllGames_StatsDialog
from pysoltk import FullLog_StatsDialog, SessionLog_StatsDialog
from pysoltk import Status_StatsDialog, Top_StatsDialog
from pysoltk import ProgressionDialog
from pysoltk import GameInfoDialog

# toolkit imports
from pysoltk import MfxMessageDialog, MfxSimpleEntry
from pysoltk import MfxExceptionDialog
from pysoltk import PlayerOptionsDialog
from pysoltk import TimeoutsDialog
from pysoltk import ColorsDialog
from pysoltk import FontsDialog
from pysoltk import EditTextDialog
from pysoltk import create_find_card_dialog
from pysoltk import create_solver_dialog
from help import help_about, help_html

gettext = _

# /***********************************************************************
# // 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,
            find_card = 0,
            rules = 0,
            pause = 0,
        )

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

    # 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, break_pause=True):
        if self.game is None:
            return True
        ret = self.game._cancelDrag(break_pause=break_pause)
        self._setPauseMenu(self.game.pause)
        return ret

    def changed(self, *args, **kw):
        assert self.game is not None
        return 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 isinstance(v, list):
                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
        if opt.highlight_piles and game.getHighlightPilesStacks():
            ms.highlight_piles = 1
        if game.canFindCard():
            ms.find_card = 1
        if game.app.getGameRulesFilename(game.id):  # note: this may return ""
            ms.rules = 1
        if not game.finished:
            ms.pause = 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.restart")
        # Game menu
        self.setMenuState(ms.deal, "game.dealcards")
        self.setMenuState(ms.autodrop, "game.autodrop")
        self.setMenuState(ms.pause, "game.pause")
        # Assist menu
        self.setMenuState(ms.hint, "assist.hint")
        self.setMenuState(ms.highlight_piles, "assist.highlightpiles")
        self.setMenuState(ms.find_card, "assist.findcard")
        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.pause, "pause")
        self.setToolbarState(ms.rules, "rules")

    # 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")):
                return
        self.game.endGame()
        self.game.quitGame(id, random=random)

    def _mNewGameBySeed(self, seed, origin):
        try:
            random = constructRandom(seed)
            if random is None:
                return
            id = self.game.id
            if not self.app.getGameInfo(id):
                raise ValueError
        except (ValueError, TypeError), ex:
            d = MfxMessageDialog(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(break_pause=False): 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, type='all'):
        if self._cancelDrag(): return
        if self.changed():
            if not self.game.areYouSure(_("Select random game")): return
        game_id = None
        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
            if type == 'all':
                game_id = gi.id
                break
            won, lost = self.app.stats.getStats(self.app.opt.player, gi.id)
            if type == 'won' and won > 0:
                game_id = gi.id
                break
            if type == 'not won' and won == 0 and lost > 0:
                game_id = gi.id
                break
            if type == 'not played' and won+lost == 0:
                game_id = gi.id
                break
        if game_id and game_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(break_pause=False): 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 ") + PACKAGE): 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.app.top.busyUpdate()
            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)
        self.game.autoDrop(autofaceup=-1)

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

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

    def mTop10(self, *args):
        if self._cancelDrag(break_pause=False): return
        self.mPlayerStats(mode=105)

    def mGameInfo(self, *args):
        if self._cancelDrag(break_pause=False): return
        self.mPlayerStats(mode=106)

    def mFindCard(self, *args):
        create_find_card_dialog(self.game.top, self.game,
                                self.app.getFindCardImagesDir())

    def mSolver(self, *args):
        create_solver_dialog(self.game.top, self.app)

    def mEditGameComment(self, *args):
        if self._cancelDrag(break_pause=False): return
        game, gi = self.game, self.game.gameinfo
        t = " " + game.getGameNumber(format=1)
        cc = _("Comments for %s:\n\n") % (gi.name + t)
        c = game.gsaveinfo.comment or cc
        d = EditTextDialog(game.top, _("Comments for ")+t, text=c)
        if d.status == 0 and d.button == 0:
            text = d.text
            if text.strip() == cc.strip():
                game.gsaveinfo.comment = ""
            else:
                game.gsaveinfo.comment = d.text
                # save to file
                fn = os.path.join(self.app.dn.config, "comments.txt")
                fn = os.path.normpath(fn)
                if not text.endswith(os.linesep):
                    text += os.linesep
                enc = locale.getpreferredencoding()
                try:
                    fd = open(fn, 'a')
                    fd.write(text.encode(enc, 'replace'))
                except Exception, err:
                    d = MfxExceptionDialog(self.top, err,
                                           text=_("Error while writing to file"))
                else:
                    if fd: fd.close()
                    d = MfxMessageDialog(self.top, title=PACKAGE+_(" Info"), bitmap="info",
                                         text=_("Comments were appended to\n\n") + fn)
        self._setCommentMenu(bool(game.gsaveinfo.comment))


    #
    # Game menu - statistics
    #

    def _mStatsSave(self, player, 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 = FileStatsFormatter(self.app, file)
            write_method(a, player)
        except EnvError, ex:
            if file: file.close()
            d = MfxExceptionDialog(self.top, ex,
                                   text=_("Error while writing to file"))
        else:
            if file: file.close()
            d = MfxMessageDialog(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
        gameid = None
        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 = gettext(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)
                gameid = d.selected_game
            elif mode == 102:
                header = p1 + _("Statistics") + p2
                d = AllGames_StatsDialog(self.top, header, self.app, player)
                gameid = d.selected_game
            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 == 105:
                header = p1 + TOP_TITLE + _(" for ") + n
                d = Top_StatsDialog(self.top, header, self.app, player, gameid=self.game.id)
            elif mode == 106:
                header = _("Game Info")
                d = GameInfoDialog(self.top, header, self.app)
            elif mode == 107:
                header = _("Statistics progression")
                d = ProgressionDialog(self.top, header, self.app, player, gameid=self.game.id)
            elif mode == 202:
                # print stats to file
                write_method = FileStatsFormatter.writeStats
                self._mStatsSave(player, "stats", write_method)
            elif mode == 203:
                # print full log to file
                write_method = FileStatsFormatter.writeFullLog
                self._mStatsSave(player, "log", write_method)
            elif mode == 204:
                # print session log to file
                write_method = FileStatsFormatter.writeSessionLog
                self._mStatsSave(player, "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%s ?") % 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%s\nand game\n%s ?') % (p0, 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
                if gameid and gameid != self.game.id:
                    self.game.endGame()
                    self.game.quitGame(gameid)
            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.timeouts['hint']):
                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.timeouts['hint']):
                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.timeouts['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.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(break_pause=False): 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 = d.player[:30].strip()
            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 mOptColors(self, *args):
        if self._cancelDrag(break_pause=False): return
        d = ColorsDialog(self.top, _("Set colors"), self.app)
        text_color = self.app.opt.colors['text']
        if d.status == 0 and d.button == 0:
            self.app.opt.colors['text'] = d.text_color
            self.app.opt.colors['piles'] = d.piles_color
            self.app.opt.colors['cards_1'] = d.cards_1_color
            self.app.opt.colors['cards_2'] = d.cards_2_color
            self.app.opt.colors['samerank_1'] = d.samerank_1_color
            self.app.opt.colors['samerank_2'] = d.samerank_2_color
            self.app.opt.colors['hintarrow'] = d.hintarrow_color
            self.app.opt.colors['not_matching'] = d.not_matching_color
            #
            if text_color != self.app.opt.colors['text']:
                self.app.setTile(self.app.tabletile_index, force=True)

    def mOptFonts(self, *args):
        if self._cancelDrag(break_pause=False): return
        d = FontsDialog(self.top, _("Set fonts"), self.app)
        if d.status == 0 and d.button == 0:
            self.app.opt.fonts.update(d.fonts)
            self._cancelDrag()
            self.game.endGame(bookmark=1)
            self.game.quitGame(bookmark=1)

    def mOptTimeouts(self, *args):
        if self._cancelDrag(break_pause=False): return
        d = TimeoutsDialog(self.top, _("Set timeouts"), self.app)
        if d.status == 0 and d.button == 0:
            self.app.opt.timeouts['demo'] = d.demo_timeout
            self.app.opt.timeouts['hint'] = d.hint_timeout
            self.app.opt.timeouts['raise_card'] = d.raise_card_timeout
            self.app.opt.timeouts['highlight_piles'] = d.highlight_piles_timeout
            self.app.opt.timeouts['highlight_cards'] = d.highlight_cards_timeout
            self.app.opt.timeouts['highlight_samerank'] = d.highlight_samerank_timeout

##     def mOptSave(self, *args):
##         if self._cancelDrag(break_pause=False): 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 = MfxMessageDialog(self.top, title=PACKAGE+_(" Info"), bitmap="info",
##                                  text=_("Options were saved to\n\n") + self.app.fn.opt)


    #
    # Help menu
    #

    def mHelp(self, *args):
        if self._cancelDrag(break_pause=False): return
        help_html(self.app, "index.html", "html")

    def mHelpHowToPlay(self, *args):
        if self._cancelDrag(break_pause=False): return
        help_html(self.app, "howtoplay.html", "html")

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

    def mHelpLicense(self, *args):
        if self._cancelDrag(break_pause=False): return
        help_html(self.app, "license.html", "html")

    def mHelpNews(self, *args):
        if self._cancelDrag(break_pause=False): return
        help_html(self.app, "news.html", "html")

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

    def mHelpAbout(self, *args):
        if self._cancelDrag(break_pause=False): return
        help_about(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 = "%s-%d.ppm" % (f, i)
            if not os.path.exists(fn):
                break
            i = i + 1
            if i >= 10000:      # give up
                return
        self.top.screenshot(fn)

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

    def mIconify(self, *args):
        if self._cancelDrag(break_pause=False): return
        self.top.wm_iconify()


# /***********************************************************************
# // 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 mPause(self, *args):
        if not self._busy():
            self.menubar.mPause()
        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