## 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 math, os, re, string, sys, types
import Tkinter, tkColorChooser, tkFileDialog

# PySol imports
from mfxtools import *
from mfxutil import destruct, Struct, kwdefault
from util import PACKAGE, VERSION, CARDSET, bundle
from gamedb import GI
from actions import PysolMenubarActions
from pysolaudio import pysolsoundserver

# toolkit imports
from tkconst import EVENT_HANDLED, EVENT_PROPAGATE, CURSOR_WATCH
from tkutil import bind, after_idle
from selectgame import SelectGameDialog, SelectGameDialogWithPreview
from selectcardset import SelectCardsetDialogWithPreview
from selectcardset import SelectCardsetByTypeDialogWithPreview
from selecttile import SelectTileDialogWithPreview


# /***********************************************************************
# //
# ************************************************************************/

class MfxMenubar(Tkinter.Menu):
    addPath = None

    def __init__(self, master, **kw):
        assert kw.get("name")
        self.n = kw["tearoff"] = int(kw.get("tearoff", 0))
        apply(Tkinter.Menu.__init__, (self, master, ), kw)

    def labeltoname(self, label):
        underline = -1
        m = re.search(r"^(.*)\&([^\&].*)$", label)
        if m:
            l1, l2 = m.group(1), m.group(2)
            l1 = re.sub(r"\&\&", "&", l1)
            l2 = re.sub(r"\&\&", "&", l2)
            label = l1 + l2
            underline = len(l1)
        name = string.lower(re.sub(r"[^0-9a-zA-Z]", "", label))
        return name, label, underline

    def add(self, itemType, cnf={}):
        label = cnf.get("label")
        if label:
            name, label, underline = self.labeltoname(label)
            cnf["label"] = label
            cnf["underline"] = cnf.get("underline", underline)
            path = str(self._w) + "." + name
            if self.addPath:
                self.addPath(path, self, self.n, cnf.get("menu"))
            if os.name == "posix":
                pass
##              cnf["relief"] = "groove"
##                cnf["activeforeground"] = "#ff0000"
        Tkinter.Menu.add(self, itemType, cnf)
        self.n = self.n + 1


class MfxMenu(MfxMenubar):
    def __init__(self, master, label, underline=None, **kw):
        name, label, label_underline = self.labeltoname(label)
        kwdefault(kw, name=name)
        if 1 and os.name == "posix":
            pass
##            kw["relief"] = "groove"
##            kw["activeborderwidth"] = 1
        apply(MfxMenubar.__init__, (self, master,), kw)
        if underline is None:
            underline = label_underline
        master.add_cascade(menu=self, label=label, underline=underline)


# /***********************************************************************
# // - create menubar
# // - update menubar
# // - menu actions
# ************************************************************************/

class PysolMenubar(PysolMenubarActions):
    def __init__(self, app, top):
        PysolMenubarActions.__init__(self, app, top)
        # init columnbreak
        sh = self.top.winfo_screenheight()
        self.__cb_max = 25
        if sh >= 600: self.__cb_max = 30
        if sh >= 768: self.__cb_max = 35
        # create menus
        self.__menubar = None
        self.__menupath = {}
        self.__keybindings = {}
        self._createMenubar()
        # set the menubar
        self.updateBackgroundImagesMenu()
        self.top.config(menu=self.__menubar)


    # create a GTK-like path
    def _addPath(self, path, menu, index, submenu):
        if not self.__menupath.has_key(path):
            ##print path, menu, index, submenu
            self.__menupath[path] = (menu, index, submenu)

    def _getEnabledState(self, enabled):
        if enabled:
            return "normal"
        return "disabled"


    #
    # create the menubar
    #

    def _createMenubar(self):
        MfxMenubar.addPath = self._addPath
        kw = { "name": "menubar" }
        if 1 and os.name == "posix":
            pass
            kw["relief"] = "groove"
            kw["activeborderwidth"] = 1
        self.__menubar = apply(MfxMenubar, (self.top,), kw)

        # init keybindings
        bind(self.top, "<KeyPress>", self._keyPressHandler)

        m = "Ctrl-"
        if os.name == "mac": m = "Cmd-"

        menu = self._createMenu("&File")
        menu.add_command(label="&New game", command=self.mNewGame, accelerator="N")
        submenu = MfxMenu(menu, label="R&ecent games")
        menu.add_command(label="Select &random game", command=self.mSelectRandomGame, accelerator=m+"R")
        menu.add_command(label="Select game by nu&mber...", command=self.mSelectGameById, accelerator=m+"M")
        menu.add_separator()
        menu.add_command(label="&Open...", command=self.mOpen, accelerator=m+"O")
        menu.add_command(label="&Save", command=self.mSave, accelerator=m+"S")
        menu.add_command(label="Save &as...", command=self.mSaveAs)
        menu.add_separator()
        menu.add_command(label="&Hold and quit", command=self.mHoldAndQuit)
        menu.add_command(label="&Quit", command=self.mQuit, accelerator=m+"Q")

        menu = self._createMenu("&Select")
        self._addSelectGameMenu(menu)

        menu = self._createMenu("&Edit")
        menu.add_command(label="&Undo", command=self.mUndo, accelerator="Z")
        menu.add_command(label="&Redo", command=self.mRedo, accelerator="R")
        menu.add_command(label="Redo &all", command=self.mRedoAll)
        menu.add_separator()
        submenu = MfxMenu(menu, label="&Set bookmark")
        for i in range(9):
            label = "Bookmark %d" % (i + 1)
            submenu.add_command(label=label, command=lambda self=self, i=i: self.mSetBookmark(i))
        submenu = MfxMenu(menu, label="Go&to bookmark")
        for i in range(9):
            label = "Bookmark %d" % (i + 1)
            acc = m + "%d" % (i + 1)
            submenu.add_command(label=label, command=lambda self=self, i=i: self.mGotoBookmark(i), accelerator=acc)
        menu.add_command(label="&Clear bookmarks", command=self.mClearBookmarks)
        menu.add_separator()
        menu.add_command(label="Restart &game", command=self.mRestart, accelerator=m+"G")

        menu = self._createMenu("&Game")
        if PACKAGE != "PyJongg":
            menu.add_command(label="&Deal cards", command=self.mDeal, accelerator="D")
            menu.add_command(label="&Auto drop", command=self.mDrop, accelerator="A")
            menu.add_separator()
        menu.add_command(label="S&tatus...", command=self.mStatus, accelerator="T")
        menu.add_checkbutton(label="&Comments...", variable=self.tkopt.comment, command=self.mEditGameComment)
        menu.add_separator()
        submenu = MfxMenu(menu, label="&Statistics")
        submenu.add_command(label="Current game...", command=lambda self=self: self.mPlayerStats(mode=101))
        submenu.add_command(label="All games...", command=lambda self=self: self.mPlayerStats(mode=102))
        submenu.add_separator()
        submenu.add_command(label="Session log...", command=lambda self=self: self.mPlayerStats(mode=104))
        submenu.add_command(label="Full log...", command=lambda self=self: self.mPlayerStats(mode=103))
        submenu = MfxMenu(menu, label="Demo statistics")
        submenu.add_command(label="Current game...", command=lambda self=self: self.mPlayerStats(mode=1101))
        submenu.add_command(label="All games...", command=lambda self=self: self.mPlayerStats(mode=1102))

        menu = self._createMenu("&Assist")
        menu.add_command(label="&Hint", command=self.mHint, accelerator="H")
        if PACKAGE == "PyJongg":
            menu.add_command(label="Highlight t&iles", command=self.mHighlightPiles, accelerator="I")
        else:
            menu.add_command(label="Highlight p&iles", command=self.mHighlightPiles, accelerator="I")
        menu.add_separator()
        menu.add_command(label="&Demo", command=self.mDemo, accelerator=m+"D")
        menu.add_command(label="Demo (&all games)", command=self.mMixedDemo)

        menu = self._createMenu("&Options")
        menu.add_command(label="&Player options...", command=self.mOptPlayerOptions)
        if PACKAGE == "PyJongg":
            submenu = MfxMenu(menu, label="Assist &level")
            submenu.add_checkbutton(label="Enable &undo", variable=self.tkopt.undo, command=self.mOptEnableUndo)
            submenu.add_checkbutton(label="Enable &bookmarks", variable=self.tkopt.bookmarks, command=self.mOptEnableBookmarks)
            submenu.add_checkbutton(label="Enable &hint", variable=self.tkopt.hint, command=self.mOptEnableHint)
            submenu.add_checkbutton(label="Enable highlight t&iles", variable=self.tkopt.highlight_piles, command=self.mOptEnableHighlightPiles)
        else:
            submenu = MfxMenu(menu, label="&Automatic play")
            submenu.add_checkbutton(label="Auto &face up", variable=self.tkopt.autofaceup, command=self.mOptAutoFaceUp)
            submenu.add_checkbutton(label="&Auto drop", variable=self.tkopt.autodrop, command=self.mOptAutoDrop)
            submenu.add_checkbutton(label="Auto &deal", variable=self.tkopt.autodeal, command=self.mOptAutoDeal)
            submenu.add_separator()
            submenu.add_checkbutton(label="&Quick play", variable=self.tkopt.quickplay, command=self.mOptQuickPlay)
            submenu = MfxMenu(menu, label="Assist &level")
            submenu.add_checkbutton(label="Enable &undo", variable=self.tkopt.undo, command=self.mOptEnableUndo)
            submenu.add_checkbutton(label="Enable &bookmarks", variable=self.tkopt.bookmarks, command=self.mOptEnableBookmarks)
            submenu.add_checkbutton(label="Enable &hint", variable=self.tkopt.hint, command=self.mOptEnableHint)
            submenu.add_checkbutton(label="Enable highlight p&iles", variable=self.tkopt.highlight_piles, command=self.mOptEnableHighlightPiles)
            submenu.add_checkbutton(label="Enable highlight &cards", variable=self.tkopt.highlight_cards, command=self.mOptEnableHighlightCards)
            submenu.add_checkbutton(label="Enable highlight same &rank", variable=self.tkopt.highlight_samerank, command=self.mOptEnableHighlightSameRank)
        menu.add_separator()
        if self.app.audio.audiodev is None:
            menu.add_checkbutton(label="&Sound", variable=self.tkopt.sound, command=self.mOptSound, state=Tkinter.DISABLED)
        elif pysolsoundserver:
            menu.add_checkbutton(label="&Sound...", variable=self.tkopt.sound, command=self.mOptSoundDialog)
        else:
            menu.add_checkbutton(label="&Sound", variable=self.tkopt.sound, command=self.mOptSound)
        # cardsets
        manager = self.app.cardset_manager
        n = manager.len()
        if PACKAGE == "PyJongg":
            menu.add_command(label="Tiles&et...", command=self.mSelectCardsetDialog, accelerator="E")
            # this submenu will get set by updateBackgroundImagesMenu()
            submenu = MfxMenu(menu, label="Tile &border")
            menu.add_command(label="Table b&ackground...", command=self.mSelectTileDialog)
        else:
            menu.add_command(label="Cards&et...", command=self.mSelectCardsetDialog, accelerator="E")
            # this submenu will get set by updateBackgroundImagesMenu()
            submenu = MfxMenu(menu, label="Card &background")
            menu.add_command(label="Table t&ile...", command=self.mSelectTileDialog)
        submenu = MfxMenu(menu, label="A&nimations")
        submenu.add_radiobutton(label="&None", variable=self.tkopt.animations, value=0, command=self.mOptAnimations)
        submenu.add_radiobutton(label="&Timer based", variable=self.tkopt.animations, value=2, command=self.mOptAnimations)
        submenu.add_radiobutton(label="&Fast", variable=self.tkopt.animations, value=1, command=self.mOptAnimations)
        submenu.add_radiobutton(label="&Slow", variable=self.tkopt.animations, value=3, command=self.mOptAnimations)
        submenu.add_radiobutton(label="&Very slow", variable=self.tkopt.animations, value=4, command=self.mOptAnimations)
        if PACKAGE != "PyJongg":
            menu.add_checkbutton(label="Card shado&w", variable=self.tkopt.shadow, command=self.mOptShadow)
            menu.add_checkbutton(label="Shade &legal moves", variable=self.tkopt.shade, command=self.mOptShade)
        menu.add_separator()
        menu.add_command(label="&Hint options...", command=self.mOptHintOptions)
        menu.add_command(label="&Demo options...", command=self.mOptDemoOptions)
        menu.add_separator()
        submenu = MfxMenu(menu, label="&Toolbar")
        submenu.add_radiobutton(label="&Hide", variable=self.tkopt.toolbar, value=0, command=self.mOptToolbar)
        submenu.add_radiobutton(label="&Top", variable=self.tkopt.toolbar, value=1, command=self.mOptToolbar)
        submenu.add_radiobutton(label="&Bottom", variable=self.tkopt.toolbar, value=2, command=self.mOptToolbar)
        submenu.add_separator()
        submenu.add_radiobutton(label="&Small icons", variable=self.tkopt.toolbar_size, value=0, command=self.mOptToolbarSize)
        submenu.add_radiobutton(label="&Large icons", variable=self.tkopt.toolbar_size, value=1, command=self.mOptToolbarSize)
        menu.add_checkbutton(label="Stat&usbar", variable=self.tkopt.statusbar, command=self.mOptStatusbar)
###        menu.add_separator()
###        menu.add_command(label="Save options", command=self.mOptSave)

        menu = self._createMenu("&Help")
        menu.add_command(label="&Contents", command=self.mHelp, accelerator=m+"F1")
        menu.add_command(label="&How to play", command=self.mHelpHowToPlay)
        menu.add_command(label="&Rules for this game", command=self.mHelpRules, accelerator="F1")
        if bundle & 4 == 0:
            menu.add_command(label="&License terms", command=self.mHelpLicense)
            menu.add_command(label="What's &new ?", command=self.mHelpNews)
        menu.add_separator()
        menu.add_command(label="&About "+PACKAGE+"...", command=self.mHelpAbout)


        MfxMenubar.addPath = None

        ### FIXME: all key bindings should be *added* to keyPressHandler
        ctrl = "Control-"
        self._bindKey("",   "n", self.mNewGame)
        self._bindKey("",   "g", self.mSelectGameDialog)
        self._bindKey("",   "v", self.mSelectGameDialogWithPreview)
        self._bindKey(ctrl, "r", self.mSelectRandomGame)
        self._bindKey(ctrl, "m", self.mSelectGameById)
        self._bindKey(ctrl, "n", self.mNewGameWithNextId)
        self._bindKey(ctrl, "o", self.mOpen)
        ##self._bindKey("",   "F3", self.mOpen)           # undocumented
        self._bindKey(ctrl, "s", self.mSave)
        ##self._bindKey("",   "F2", self.mSaveAs)         # undocumented
        self._bindKey(ctrl, "q", self.mQuit)
        self._bindKey("",   "z", self.mUndo)
        self._bindKey("",   "BackSpace", self.mUndo)    # undocumented
        self._bindKey("",   "r", self.mRedo)
        self._bindKey(ctrl, "g", self.mRestart)
        self._bindKey("",   "space", self.mDeal)        # undocumented
        self._bindKey("",   "t", self.mStatus)
        self._bindKey(ctrl, "t", self.mPlayerStats)     # undocumented
        self._bindKey("",   "h", self.mHint)
        self._bindKey(ctrl, "h", self.mHint1)           # undocumented
        ##self._bindKey("",   "Shift_L", self.mHighlightPiles)
        ##self._bindKey("",   "Shift_R", self.mHighlightPiles)
        self._bindKey("",   "i", self.mHighlightPiles)
        self._bindKey(ctrl, "d", self.mDemo)
        self._bindKey("",   "e", self.mSelectCardsetDialog)
        self._bindKey(ctrl, "b", self.mOptChangeCardback) # undocumented
        self._bindKey(ctrl, "i", self.mOptChangeTableTile) # undocumented
        self._bindKey(ctrl, "p", self.mOptPlayerOptions)   # undocumented
        self._bindKey(ctrl, "F1", self.mHelp)
        self._bindKey("",   "F1", self.mHelpRules)
        self._bindKey("",   "Print", self.mScreenshot)
        self._bindKey(ctrl, "u", self.mPlayNextMusic)   # undocumented
        # ASD and LKJ
        self._bindKey("",   "a", self.mDrop)
        self._bindKey(ctrl, "a", self.mDrop1)
        self._bindKey("",   "s", self.mUndo)
        self._bindKey("",   "d", self.mDeal)
        self._bindKey("",   "l", self.mDrop)
        self._bindKey(ctrl, "l", self.mDrop1)
        self._bindKey("",   "k", self.mUndo)
        self._bindKey("",   "j", self.mDeal)

        for i in range(9):
            self._bindKey(ctrl, str(i+1),  lambda event, self=self, i=i: self.mGotoBookmark(i, confirm=0))

        if 1 and self.app.debug:
            self._bindKey(ctrl, "End", self.mPlayNextMusic)
            self._bindKey(ctrl, "Prior", self.mSelectPrevGameByName)
            self._bindKey(ctrl, "Next", self.mSelectNextGameByName)
            self._bindKey(ctrl, "Up", self.mSelectPrevGameById)
            self._bindKey(ctrl, "Down", self.mSelectNextGameById)


    #
    # key binding utility
    #

    def _bindKey(self, modifier, key, func):
        if 0 and not modifier and len(key) == 1:
            self.__keybindings[string.lower(key)] = func
            self.__keybindings[string.upper(key)] = func
            return
        sequence = "<" + modifier + "KeyPress-" + key + ">"
        try:
            bind(self.top, sequence, func)
            if len(key) == 1 and key != string.upper(key):
                key = string.upper(key)
                sequence = "<" + modifier + "KeyPress-" + key + ">"
                bind(self.top, sequence, func)
        except:
            raise                                                   #bundle#
            pass


    def _keyPressHandler(self, event):
        r = EVENT_PROPAGATE
        if event and self.game:
            ##print event.__dict__
            if self.game.demo:
                # stop the demo by setting self.game.demo.keypress
                if event.char:    # ignore Ctrl/Shift/etc.
                    self.game.demo.keypress = event.char
                    r = EVENT_HANDLED
            func = self.__keybindings.get(event.char)
            if func and (event.state & ~2) == 0:
                func(event)
                r = EVENT_HANDLED
        return r


    # utility
    def _createMenu(self, label):
        # It took me hours to find out how to place the help-menu
        # on the right side of the menubar. Finally I discovered
        # that there seems to be some magic with the widget name...
        tearoff = 0
        if os.name == "nt":
            tearoff = 1
        menu = MfxMenu(self.__menubar, label, tearoff=tearoff, underline=-1)
        return menu


    #
    # Select Game menu creation
    #

    def _addSelectGameMenu(self, menu):
        games = map(self.app.gdb.get, self.app.gdb.getGamesIdSortedByShortName())
        games = tuple(games)
        ###menu = MfxMenu(menu, label="Select &game")
        menu.add_command(label="All &games...", command=self.mSelectGameDialog, accelerator="G")
        menu.add_command(label="Playable pre&view...", command=self.mSelectGameDialogWithPreview, accelerator="V")
        menu.add_separator()
        data = ("&Popular games", lambda gi: gi.si.game_flags & GI.GT_POPULAR)
        self._addSelectGameSubMenu(menu, games, (data, ),
                                   self.mSelectGamePopular, self.tkopt.gameid_popular)
        if PACKAGE == "PyJongg":
            menu.add_separator()
            self._addSelectMahjonggGameSubMenu(menu, games, self.mSelectGame, self.tkopt.gameid)
            return
        ##menu.add_separator()
        submenu = MfxMenu(menu, label="&Special games")
        self._addSelectGameSubMenu(submenu, games, GI.SELECT_SPECIAL_GAME_BY_TYPE,
                                   self.mSelectGame, self.tkopt.gameid)
        menu.add_separator()
        self._addSelectGameSubMenu(menu, games, GI.SELECT_GAME_BY_TYPE,
                                   self.mSelectGame, self.tkopt.gameid)

    def _addSelectGameSubMenu(self, menu, games, select_data, command, variable):
        ##print select_data
        need_sep = 0
        for label, select_func in select_data:
            if label is None:
                need_sep = 1
                continue
            g = filter(select_func, games)
            if not g:
                continue
            if need_sep:
                menu.add_separator()
                need_sep = 0
            submenu = MfxMenu(menu, label=label)
            if 1 and label == "Mahjongg type":
                ##self._addSelectMahjonggGameSubMenu(submenu, g, command, variable)
                pass
            else:
                self._addSelectGameSubSubMenu(submenu, g, command, variable)

    def _addSelectMahjonggGameSubMenu(self, menu, g, command, variable):
        for c in ("AC", "DF", "GK", "LR", "ST", "UZ"):
            gg = filter(lambda gi, c0=c[0], c1=c[1]: c0 <= gi.short_name[0] <= c1, g)
            if not gg:
                continue
            label = c[0]
            if c[0] != c[1]:
                label = c[0] + " - " + c[1]
            submenu = MfxMenu(menu, label=label)
            self._addSelectGameSubSubMenu(submenu, gg, command, variable)

    def _addSelectGameSubSubMenu(self, menu, g, command, variable):
        cb = (25, self.__cb_max) [ len(g) > 4 * 25 ]
        for i in range(len(g)):
            gi = g[i]
            columnbreak = i > 0 and (i % cb) == 0
            menu.add_radiobutton(command=command, variable=variable,
                                 columnbreak=columnbreak,
                                 value=gi.id, label=gi.short_name)


    #
    # Select Game menu actions
    #

    def _mSelectGameDialog(self, d):
        if d.status == 0 and d.button == 0 and d.gameid != self.game.id:
            self.tkopt.gameid.set(d.gameid)
            self.tkopt.gameid_popular.set(d.gameid)
            if 0:
                self._mSelectGame(d.gameid, random=d.random)
            else:
                # don't ask areYouSure()
                self.game.endGame()
                self.game.quitGame(d.gameid, random=d.random)
        return EVENT_HANDLED

    def __restoreCursor(self, *event):
        self.game.setCursor(cursor=self.app.top_cursor)

    def mSelectGameDialog(self, *event):
        if self._cancelDrag(): return
        self.game.setCursor(cursor=CURSOR_WATCH)
        after_idle(self.top, self.__restoreCursor)
        d = SelectGameDialog(self.top, title="Select game",
                             app=self.app, gameid=self.game.id)
        return self._mSelectGameDialog(d)

    def mSelectGameDialogWithPreview(self, *event):
        if self._cancelDrag(): return
        self.game.setCursor(cursor=CURSOR_WATCH)
        bookmark = None
        if 0:
            # use a bookmark for our preview game
            if self.game.setBookmark(-2, confirm=0):
                bookmark = self.game.gsaveinfo.bookmarks[-2][0]
                del self.game.gsaveinfo.bookmarks[-2]
        after_idle(self.top, self.__restoreCursor)
        d = SelectGameDialogWithPreview(self.top, title="Select game",
                                        app=self.app, gameid=self.game.id,
                                        bookmark=bookmark)
        return self._mSelectGameDialog(d)


    #
    # menubar overrides
    #

    def updateRecentGamesMenu(self, gameids):
        # delete all entries
        submenu = self.__menupath[".menubar.file.recentgames"][2]
        submenu.delete(0, "last")
        # insert games
        cb = (25, self.__cb_max) [ len(gameids) > 4 * 25 ]
        i = 0
        for id in gameids:
            gi = self.app.getGameInfo(id)
            if not gi:
                continue
            columnbreak = i > 0 and (i % cb) == 0
            submenu.add_radiobutton(command=self.mSelectGame,
                                    variable=self.tkopt.gameid,
                                    columnbreak=columnbreak,
                                    value=gi.id, label=gi.short_name)
            i = i + 1

    def updateBookmarkMenuState(self):
        state = self._getEnabledState
        mp1 = self.__menupath.get(".menubar.edit.setbookmark")
        mp2 = self.__menupath.get(".menubar.edit.gotobookmark")
        mp3 = self.__menupath.get(".menubar.edit.clearbookmarks")
        if mp1 is None or mp2 is None or mp3 is None:
            return
        x = self.app.opt.bookmarks and self.game.canSetBookmark()
        #
        menu, index, submenu = mp1
        for i in range(9):
            submenu.entryconfig(i, state=state(x))
        menu.entryconfig(index, state=state(x))
        #
        menu, index, submenu = mp2
        ms = 0
        for i in range(9):
            s = self.game.gsaveinfo.bookmarks.get(i) is not None
            submenu.entryconfig(i, state=state(s and x))
            ms = ms or s
        menu.entryconfig(index, state=state(ms and x))
        #
        menu, index, submenu = mp3
        menu.entryconfig(index, state=state(ms and x))

    def updateBackgroundImagesMenu(self):
        mp = self.__menupath.get(".menubar.options.cardbackground")
        if PACKAGE == "PyJongg" and mp is None:
            mp = self.__menupath.get(".menubar.options.tileborder")
        # delete all entries
        submenu = mp[2]
        submenu.delete(0, "last")
        # insert new cardbacks
        mbacks = self.app.images.getCardbacks()
        cb = int(math.ceil(math.sqrt(len(mbacks))))
        for i in range(len(mbacks)):
            columnbreak = i > 0 and (i % cb) == 0
            submenu.add_radiobutton(label=mbacks[i].name, image=mbacks[i].menu_image, variable=self.tkopt.cardback, value=i,
                                    command=self.mOptCardback, columnbreak=columnbreak, indicatoron=0, hidemargin=0)


    #
    # menu updates
    #

    def setMenuState(self, state, path):
        #print state, path
        path = ".menubar." + path
        mp = self.__menupath.get(path)
        if PACKAGE == "PyJongg" and mp is None:
            return
        menu, index, submenu = mp
        s = self._getEnabledState(state)
        menu.entryconfig(index, state=s)

    def setToolbarState(self, state, path):
        #print state, path
        s = self._getEnabledState(state)
        w = getattr(self.app.toolbar, path + "_button")
        w["state"] = s


    #
    # menu actions
    #

    if PACKAGE == "PyJongg":
        DEFAULTEXTENSION = ".pjo"
    else:
        DEFAULTEXTENSION = ".pso"
    FILETYPES = ((PACKAGE+" files", "*"+DEFAULTEXTENSION), ("All files", "*"))

    def mOpen(self, *event):
        if self._cancelDrag(): return
        filename = self.game.filename
        if filename:
            idir, ifile = os.path.split(os.path.normpath(filename))
        else:
            idir, ifile = "", ""
        if not idir:
            idir = self.app.dn.savegames
        d = tkFileDialog.Open()
        filename = d.show(filetypes=self.FILETYPES, defaultextension=self.DEFAULTEXTENSION,
                          initialdir=idir, initialfile=ifile)
        if filename:
            filename = os.path.normpath(filename)
            ##filename = os.path.normcase(filename)
            if os.path.isfile(filename):
                self.game.loadGame(filename)

    def mSaveAs(self, *event):
        if self._cancelDrag(): return
        if not self.menustate.save_as:
            return
        filename = self.game.filename
        if not filename:
            filename = self.app.getGameSaveName(self.game.id)
            if os.name == "posix":
                filename = filename + "-" + self.game.getGameNumber(format=0)
            else:
                filename = filename + "-01"
            filename = filename + self.DEFAULTEXTENSION
        idir, ifile = os.path.split(os.path.normpath(filename))
        if not idir:
            idir = self.app.dn.savegames
        ##print self.game.filename, ifile
        d = tkFileDialog.SaveAs()
        filename = d.show(filetypes=self.FILETYPES, defaultextension=self.DEFAULTEXTENSION,
                          initialdir=idir, initialfile=ifile)
        if filename:
            filename = os.path.normpath(filename)
            ##filename = os.path.normcase(filename)
            self.game.saveGame(filename)
            self.updateMenus()

    def mSelectCardsetDialog(self, *event):
        if self._cancelDrag(): return
        ##strings, default = ("OK", "Load", "Cancel"), 0
        strings, default = (None, "Load", "Cancel"), 1
        if PACKAGE == "PySol":
            if os.name == "posix":
                strings, default = (None, "Load", "Cancel", "Info...",), 1
            pass
        t = CARDSET
        key = self.app.nextgame.cardset.index
        d = SelectCardsetDialogWithPreview(self.top, title="Select "+t,
                app=self.app, manager=self.app.cardset_manager, key=key,
                strings=strings, default=default)
        cs = self.app.cardset_manager.get(d.key)
        if cs is None or d.key == self.app.cardset.index:
            return
        if d.status == 0 and d.button in (0, 1) and d.key >= 0:
            self.app.nextgame.cardset = cs
            if d.button == 1:
                self.game.endGame(bookmark=1)
                self.game.quitGame(bookmark=1)

    def _mOptCardback(self, index):
        if self._cancelDrag(): return
        cs = self.app.cardset
        old_index = cs.backindex
        cs.updateCardback(backindex=index)
        if cs.backindex == old_index:
            return
        self.app.updateCardset(self.game.id)
        for card in self.game.cards:
            image = self.app.images.getBack(card.deck, card.suit, card.rank)
            card.updateCardBackground(image=image)
        self.app.canvas.update_idletasks()
        self.tkopt.cardback.set(cs.backindex)

    def mOptCardback(self, *event):
        self._mOptCardback(self.tkopt.cardback.get())

    def mOptChangeCardback(self, *event):
        self._mOptCardback(self.app.cardset.backindex + 1)

    def _mOptTableTile(self, i):
        if self.app.setTile(i):
            self.tkopt.tabletile.set(i)

    def _mOptTableColor(self, color):
        tile = self.app.tabletile_manager.get(0)
        tile.color = color
        if self.app.setTile(0):
            self.tkopt.tabletile.set(0)

    def mOptTableTile(self, *event):
        if self._cancelDrag(): return
        self._mOptTableTile(self.tkopt.tabletile.get())

    def mOptChangeTableTile(self, *event):
        if self._cancelDrag(): return
        n = self.app.tabletile_manager.len()
        if n >= 2:
            i = (self.tkopt.tabletile.get() + 1) % n
            self._mOptTableTile(i)

    def mSelectTileDialog(self, *event):
        if self._cancelDrag(): return
        key = self.app.tabletile_index
        if key <= 0:
            key = string.lower(self.app.opt.tablecolor)
        d = SelectTileDialogWithPreview(self.top, title="Select table background",
                app=self.app, manager=self.app.tabletile_manager, key=key)
        if d.status == 0 and d.button in (0, 1):
            if type(d.key) is types.StringType:
                self._mOptTableColor(d.key)
            elif d.key > 0 and d.key != self.app.tabletile_index:
                self._mOptTableTile(d.key)

    def mOptTableColor(self, *event):
        if self._cancelDrag(): return
        c = tkColorChooser.askcolor(initialcolor=self.app.opt.tablecolor,
                                    title="Select table color")
        if c and c[1]:
            self._mOptTableColor(c[1])

    def mOptToolbar(self, *event):
        if self._cancelDrag(): return
        self.setToolbarSide(self.tkopt.toolbar.get())

    def mOptToolbarSize(self, *event):
        if self._cancelDrag(): return
        self.setToolbarSize(self.tkopt.toolbar_size.get())

    def mOptToolbarRelief(self, *event):
        if self._cancelDrag(): return
        self.setToolbarRelief(self.tkopt.toolbar_relief.get())

    def mOptStatusbar(self, *event):
        if self._cancelDrag(): return
        if not self.app.statusbar: return
        side = self.tkopt.statusbar.get()
        self.app.opt.statusbar = side
        if self.app.statusbar.show(side):
            self.top.update_idletasks()


    #
    # toolbar support
    #

    def setToolbarSide(self, side):
        if self._cancelDrag(): return
        self.app.opt.toolbar = side
        self.tkopt.toolbar.set(side)                    # update radiobutton
        if self.app.toolbar.show(side):
            self.top.update_idletasks()

    def setToolbarSize(self, size):
        if self._cancelDrag(): return
        self.app.opt.toolbar_size = size
        self.tkopt.toolbar_size.set(size)                # update radiobutton
        dir = self.app.getToolbarImagesDir(size)
        if self.app.toolbar.updateImages(dir, size):
            self.game.updateStatus(player=self.app.opt.player)
            self.top.update_idletasks()

    def setToolbarRelief(self, relief):
        if self._cancelDrag(): return
        self.app.opt.toolbar_relief = relief
        self.tkopt.toolbar_relief.set(relief)           # update radiobutton
        ### FIXME
        self.top.update_idletasks()

