import math
import os
import re
import sys

from pysollib.gamedb import GI
from pysollib.hint import PySolHintLayoutImportError
from pysollib.mfxutil import Image, USE_PIL
from pysollib.mfxutil import Struct, kwdefault
from pysollib.mygettext import _, n_
from pysollib.settings import SELECT_GAME_MENU
from pysollib.settings import TITLE, WIN_SYSTEM
from pysollib.settings import USE_FREECELL_SOLVER
from pysollib.ui.tktile.tkconst import COMPOUNDS, CURSOR_WATCH, EVENT_HANDLED
from pysollib.ui.tktile.tkconst import EVENT_PROPAGATE
from pysollib.ui.tktile.tkconst import TOOLBAR_BUTTONS
from pysollib.ui.tktile.tkutil import after_idle, bind

from six.moves import tkinter
from six.moves import tkinter_tkfiledialog


def createToolbarMenu(menubar, menu):
    tearoff = menu.cget('tearoff')
#     data_dir = os.path.join(menubar.app.dataloader.dir, 'images', 'toolbar')
#     submenu = MfxMenu(menu, label=n_('Style'), tearoff=tearoff)
#     for f in os.listdir(data_dir):
#         d = os.path.join(data_dir, f)
#         if os.path.isdir(d) and os.path.exists(os.path.join(d, 'small')):
#             name = f.replace('_', ' ').capitalize()
#             submenu.add_radiobutton(
#               label=name,
#               variable=menubar.tkopt.toolbar_style,
#               value=f, command=menubar.mOptToolbarStyle)
    submenu = MfxMenu(menu, label=n_('Compound'), tearoff=tearoff)
    for comp, label in COMPOUNDS:
        submenu.add_radiobutton(
            label=label, variable=menubar.tkopt.toolbar_compound,
            value=comp, command=menubar.mOptToolbarCompound)
    menu.add_separator()
    menu.add_radiobutton(label=n_("Hide"),
                         variable=menubar.tkopt.toolbar, value=0,
                         command=menubar.mOptToolbar)
    menu.add_radiobutton(label=n_("Top"),
                         variable=menubar.tkopt.toolbar, value=1,
                         command=menubar.mOptToolbar)
    menu.add_radiobutton(label=n_("Bottom"),
                         variable=menubar.tkopt.toolbar, value=2,
                         command=menubar.mOptToolbar)
    menu.add_radiobutton(label=n_("Left"),
                         variable=menubar.tkopt.toolbar, value=3,
                         command=menubar.mOptToolbar)
    menu.add_radiobutton(label=n_("Right"),
                         variable=menubar.tkopt.toolbar, value=4,
                         command=menubar.mOptToolbar)
    #  menu.add_separator()
    #  menu.add_radiobutton(label=n_("Small icons"),
    #                       variable=menubar.tkopt.toolbar_size, value=0,
    #                       command=menubar.mOptToolbarSize)
    #  menu.add_radiobutton(label=n_("Large icons"),
    #                       variable=menubar.tkopt.toolbar_size, value=1,
    #                       command=menubar.mOptToolbarSize)
    menu.add_separator()
    submenu = MfxMenu(menu, label=n_('Visible buttons'), tearoff=tearoff)
    for w in TOOLBAR_BUTTONS:
        submenu.add_checkbutton(
            label=_(w.capitalize()),
            variable=menubar.tkopt.toolbar_vars[w],
            command=lambda m=menubar, w=w: m.mOptToolbarConfig(w))


# ************************************************************************
# *
# ************************************************************************

class MfxMenubar(tkinter.Menu):
    addPath = None

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

    def labeltoname(self, label):
        # print label, type(label)
        name = re.sub(r"[^0-9a-zA-Z]", "", label).lower()
        label = _(label)
        underline = label.find('&')
        if underline >= 0:
            label = label.replace('&', '')
        return name, label, underline

    def add(self, itemType, cnf={}):
        label = cnf.get("label")
        if label:
            name = cnf.get('name')
            if name:
                del cnf['name']  # TclError: unknown option "-name"
            else:
                name, label, underline = self.labeltoname(label)
                cnf["underline"] = cnf.get("underline", underline)
            cnf["label"] = label
            if name and self.addPath:
                path = str(self._w) + "." + name
                self.addPath(path, self, self.n, cnf.get("menu"))
        tkinter.Menu.add(self, itemType, cnf)
        self.n = self.n + 1


class MfxMenu(MfxMenubar):
    def __init__(self, master, label, underline=None, **kw):
        if 'name' in kw:
            name, label_underline = kw['name'], -1
        else:
            name, label, label_underline = self.labeltoname(label)
        kwdefault(kw, name=name)
        MfxMenubar.__init__(self, master, **kw)
        if underline is None:
            underline = label_underline
        if master:
            master.add_cascade(
                menu=self, name=name, label=label, underline=underline)


class PysolMenubarTkCommon:
    def __init__(self, app, top, progress=None):
        self._createTkOpt()
        self._setOptions()
        # init columnbreak
        self.cb_max = int(self.top.winfo_screenheight()//23)
        #  sh = self.top.winfo_screenheight()
        #  self.cb_max = 22
        #  if sh >= 600: self.cb_max = 27
        #  if sh >= 768: self.cb_max = 32
        #  if sh >= 1024: self.cb_max = 40
        self.progress = progress
        # create menus
        self.menubar = None
        self.menupath = {}
        self.keybindings = {}
        self._createMenubar()
        self.top = top

        if self.progress:
            self.progress.update(step=1)

        # set the menubar
        self.updateBackgroundImagesMenu()
        self.top.config(menu=self.menubar)

    def _createTkOpt(self):
        # structure to convert menu-options to Toolkit variables
        self.tkopt = Struct(
            gameid=tkinter.IntVar(),
            gameid_popular=tkinter.IntVar(),
            comment=tkinter.BooleanVar(),
            autofaceup=tkinter.BooleanVar(),
            autodrop=tkinter.BooleanVar(),
            autodeal=tkinter.BooleanVar(),
            quickplay=tkinter.BooleanVar(),
            undo=tkinter.BooleanVar(),
            bookmarks=tkinter.BooleanVar(),
            hint=tkinter.BooleanVar(),
            shuffle=tkinter.BooleanVar(),
            highlight_piles=tkinter.BooleanVar(),
            highlight_cards=tkinter.BooleanVar(),
            highlight_samerank=tkinter.BooleanVar(),
            highlight_not_matching=tkinter.BooleanVar(),
            mahjongg_show_removed=tkinter.BooleanVar(),
            shisen_show_hint=tkinter.BooleanVar(),
            accordion_deal_all=tkinter.BooleanVar(),
            sound=tkinter.BooleanVar(),
            auto_scale=tkinter.BooleanVar(),
            preserve_aspect_ratio=tkinter.BooleanVar(),
            spread_stacks=tkinter.BooleanVar(),
            center_layout=tkinter.BooleanVar(),
            cardback=tkinter.IntVar(),
            tabletile=tkinter.IntVar(),
            animations=tkinter.IntVar(),
            redeal_animation=tkinter.BooleanVar(),
            win_animation=tkinter.BooleanVar(),
            shadow=tkinter.BooleanVar(),
            shade=tkinter.BooleanVar(),
            shade_filled_stacks=tkinter.BooleanVar(),
            shrink_face_down=tkinter.BooleanVar(),
            toolbar=tkinter.IntVar(),
            toolbar_style=tkinter.StringVar(),
            toolbar_relief=tkinter.StringVar(),
            toolbar_compound=tkinter.StringVar(),
            toolbar_size=tkinter.IntVar(),
            statusbar=tkinter.BooleanVar(),
            num_cards=tkinter.BooleanVar(),
            helpbar=tkinter.BooleanVar(),
            save_games_geometry=tkinter.BooleanVar(),
            splashscreen=tkinter.BooleanVar(),
            demo_logo=tkinter.BooleanVar(),
            mouse_type=tkinter.StringVar(),
            mouse_undo=tkinter.BooleanVar(),
            negative_bottom=tkinter.BooleanVar(),
            pause=tkinter.BooleanVar(),
            theme=tkinter.StringVar(),
            toolbar_vars={},
        )
        for w in TOOLBAR_BUTTONS:
            self.tkopt.toolbar_vars[w] = tkinter.BooleanVar()

    def _setOptions(self):
        tkopt, opt = self.tkopt, self.app.opt
        # set state of the menu items
        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.shuffle.set(opt.shuffle)
        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.highlight_not_matching.set(opt.highlight_not_matching)
        tkopt.shrink_face_down.set(opt.shrink_face_down)
        tkopt.shade_filled_stacks.set(opt.shade_filled_stacks)
        tkopt.mahjongg_show_removed.set(opt.mahjongg_show_removed)
        tkopt.shisen_show_hint.set(opt.shisen_show_hint)
        tkopt.accordion_deal_all.set(opt.accordion_deal_all)
        tkopt.sound.set(opt.sound)
        tkopt.auto_scale.set(opt.auto_scale)
        tkopt.preserve_aspect_ratio.set(opt.preserve_aspect_ratio)
        tkopt.spread_stacks.set(opt.spread_stacks)
        tkopt.center_layout.set(opt.center_layout)
        tkopt.cardback.set(self.app.cardset.backindex)
        tkopt.tabletile.set(self.app.tabletile_index)
        tkopt.animations.set(opt.animations)
        tkopt.redeal_animation.set(opt.redeal_animation)
        tkopt.win_animation.set(opt.win_animation)
        tkopt.shadow.set(opt.shadow)
        tkopt.shade.set(opt.shade)
        tkopt.toolbar.set(opt.toolbar)
        tkopt.toolbar_style.set(opt.toolbar_style)
        tkopt.toolbar_relief.set(opt.toolbar_relief)
        tkopt.toolbar_compound.set(opt.toolbar_compound)
        tkopt.toolbar_size.set(opt.toolbar_size)
        tkopt.toolbar_relief.set(opt.toolbar_relief)
        tkopt.statusbar.set(opt.statusbar)
        tkopt.num_cards.set(opt.num_cards)
        tkopt.helpbar.set(opt.helpbar)
        tkopt.save_games_geometry.set(opt.save_games_geometry)
        tkopt.demo_logo.set(opt.demo_logo)
        tkopt.splashscreen.set(opt.splashscreen)
        tkopt.mouse_type.set(opt.mouse_type)
        tkopt.mouse_undo.set(opt.mouse_undo)
        tkopt.negative_bottom.set(opt.negative_bottom)
        for w in TOOLBAR_BUTTONS:
            tkopt.toolbar_vars[w].set(opt.toolbar_vars.get(w, False))

    def connectGame(self, game):
        self.game = game
        if game is None:
            return
        assert self.app is game.app
        tkopt = self.tkopt
        tkopt.gameid.set(game.id)
        tkopt.gameid_popular.set(game.id)
        tkopt.comment.set(bool(game.gsaveinfo.comment))
        tkopt.pause.set(self.game.pause)
        if game.canFindCard():
            self._connect_game_find_card_dialog(game)
        else:
            self._destroy_find_card_dialog()
        self._connect_game_solver_dialog(game)

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

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

    def updateProgress(self):
        if self.progress:
            self.progress.update(step=1)

    def _createMenubar(self):
        MfxMenubar.addPath = self._addPath
        kw = {"name": "menubar"}
        self.menubar = MfxMenubar(self.top, **kw)

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

        m = "Ctrl-"
        if sys.platform == "darwin":
            m = "Cmd-"

        if WIN_SYSTEM == "aqua":
            applemenu = MfxMenu(self.menubar, "apple")
            applemenu.add_command(
                label=_("&About %s") % TITLE, command=self.mHelpAbout)

        menu = MfxMenu(self.menubar, n_("&File"))
        menu.add_command(
            label=n_("&New game"), command=self.mNewGame, accelerator="N")
        submenu = MfxMenu(menu, label=n_("R&ecent games"))
        # menu.add_command(label=n_("Select &random game"),
        #   command=self.mSelectRandomGame, accelerator=m+"R")
        submenu = MfxMenu(menu, label=n_("Select &random game"))
        submenu.add_command(
            label=n_("&All games"), command=lambda:
            self.mSelectRandomGame('all'), accelerator=m+"R")
        submenu.add_command(
            label=n_("Games played and &won"),
            command=lambda: self.mSelectRandomGame('won'))
        submenu.add_command(
            label=n_("Games played and &not won"),
            command=lambda: self.mSelectRandomGame('not won'))
        submenu.add_command(
            label=n_("Games not &played"),
            command=lambda: self.mSelectRandomGame('not played'))
        menu.add_command(
            label=n_("Select game by nu&mber..."),
            command=self.mSelectGameById, accelerator=m+"M")
        menu.add_separator()
        submenu = MfxMenu(menu, label=n_("Fa&vorite games"))
        menu.add_command(label=n_("A&dd to favorites"), command=self.mAddFavor)
        menu.add_command(
            label=n_("Remove &from favorites"),
            command=self.mDelFavor)
        menu.add_separator()
        menu.add_command(
            label=n_("&Open..."),
            command=self.mOpen, accelerator=m+"O")
        menu.add_command(
            label=n_("&Save"),
            command=self.mSave, accelerator=m+"S")
        menu.add_command(label=n_("Save &as..."), command=self.mSaveAs)
        menu.add_command(
            label=n_("E&xport current layout..."),
            command=self.mExportCurrentLayout)
        menu.add_command(
            label=n_("&Import starting layout..."),
            command=self.mImportStartingLayout)
        menu.add_separator()
        menu.add_command(
            label=n_("&Hold and quit"),
            command=self.mHoldAndQuit, accelerator=m+"X")
        if WIN_SYSTEM != "aqua":
            menu.add_command(
                label=n_("&Quit"),
                command=self.mQuit, accelerator=m+"Q")

        if self.progress:
            self.progress.update(step=1)

        menu = MfxMenu(self.menubar, label=n_("&Select"))
        self._addSelectGameMenu(menu)

        if self.progress:
            self.progress.update(step=1)

        menu = MfxMenu(self.menubar, label=n_("&Edit"))
        menu.add_command(
            label=n_("&Undo"),
            command=self.mUndo, accelerator="Z")
        menu.add_command(
            label=n_("&Redo"),
            command=self.mRedo, accelerator="R")
        menu.add_command(label=n_("Redo &all"), command=self.mRedoAll)

        menu.add_separator()
        submenu = MfxMenu(menu, label=n_("&Set bookmark"))
        for i in range(9):
            label = _("Bookmark %d") % (i + 1)
            submenu.add_command(
                label=label,
                command=lambda i=i: self.mSetBookmark(i))
        submenu = MfxMenu(menu, label=n_("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 i=i: self.mGotoBookmark(i), accelerator=acc)
        menu.add_command(
            label=n_("&Clear bookmarks"),
            command=self.mClearBookmarks)
        menu.add_separator()

        menu.add_command(
            label=n_("Restart"),
            command=self.mRestart, accelerator=m+"G")

        menu.add_separator()
        menu.add_command(label=n_("Solitaire &Wizard"), command=self.mWizard)
        menu.add_command(
            label=n_("&Edit current game"),
            command=self.mWizardEdit)
        menu.add_command(
            label=n_("&Delete current game"),
            command=self.mWizardDelete)

        menu = MfxMenu(self.menubar, label=n_("&Game"))
        menu.add_command(
            label=n_("&Deal cards"),
            command=self.mDeal, accelerator="D")
        menu.add_command(
            label=n_("&Auto drop"),
            command=self.mDrop, accelerator="A")
        menu.add_command(
            label=n_("Shu&ffle tiles"),
            command=self.mShuffle, accelerator="F")
        menu.add_checkbutton(
            label=n_("&Pause"), variable=self.tkopt.pause,
            command=self.mPause, accelerator="P")
        # menu.add_command(
        #    label=n_("&Pause"), command=self.mPause, accelerator="P")
        menu.add_separator()
        menu.add_command(
            label=n_("S&tatus..."),
            command=lambda: self.mPlayerStats(mode=100), accelerator=m+"Y")
        menu.add_checkbutton(
            label=n_("&Comments..."), variable=self.tkopt.comment,
            command=self.mEditGameComment)
        menu.add_separator()
        menu.add_command(
            label=n_("&Statistics..."),
            command=self.mPlayerStats, accelerator=m+"T")
        menu.add_command(
            label=n_("Log..."),
            command=lambda: self.mPlayerStats(mode=103))
        menu.add_separator()
        menu.add_command(
            label=n_("D&emo statistics"),
            command=lambda: self.mPlayerStats(mode=1101))

        menu = MfxMenu(self.menubar, label=n_("&Assist"))
        menu.add_command(
            label=n_("&Hint"),
            command=self.mHint, accelerator="H")
        menu.add_command(
            label=n_("Highlight p&iles"),
            command=self.mHighlightPiles, accelerator="I")
        menu.add_command(
            label=n_("&Find card"),
            command=self.mFindCard, accelerator="F3")
        menu.add_separator()
        menu.add_command(
            label=n_("&Demo"),
            command=self.mDemo, accelerator=m+"D")
        menu.add_command(
            label=n_("Demo (&all games)"),
            command=self.mMixedDemo)
        if USE_FREECELL_SOLVER:
            menu.add_command(label=n_("&Solver"), command=self.mSolver)
        else:
            menu.add_command(label=n_("&Solver"), state='disabled')
        menu.add_separator()
        menu.add_command(
            label=n_("&Piles description"),
            command=self.mStackDesk, accelerator="F2")

        if self.progress:
            self.progress.update(step=1)

        menu = MfxMenu(self.menubar, label=n_("&Options"))
        menu.add_command(
            label=n_("&Player options..."),
            command=self.mOptPlayerOptions)
        submenu = MfxMenu(menu, label=n_("&Automatic play"))
        submenu.add_checkbutton(
            label=n_("Auto &face up"), variable=self.tkopt.autofaceup,
            command=self.mOptAutoFaceUp)
        submenu.add_checkbutton(
            label=n_("A&uto drop"), variable=self.tkopt.autodrop,
            command=self.mOptAutoDrop)
        submenu.add_checkbutton(
            label=n_("Auto &deal"), variable=self.tkopt.autodeal,
            command=self.mOptAutoDeal)
        submenu.add_separator()
        submenu.add_checkbutton(
            label=n_("&Quick play"), variable=self.tkopt.quickplay,
            command=self.mOptQuickPlay)
        submenu = MfxMenu(menu, label=n_("Assist &level"))
        submenu.add_checkbutton(
            label=n_("Enable &undo"), variable=self.tkopt.undo,
            command=self.mOptEnableUndo)
        submenu.add_checkbutton(
            label=n_("Enable &bookmarks"), variable=self.tkopt.bookmarks,
            command=self.mOptEnableBookmarks)
        submenu.add_checkbutton(
            label=n_("Enable &hint"), variable=self.tkopt.hint,
            command=self.mOptEnableHint)
        submenu.add_checkbutton(
            label=n_("Enable shu&ffle"), variable=self.tkopt.shuffle,
            command=self.mOptEnableShuffle)
        submenu.add_checkbutton(
            label=n_("Enable highlight p&iles"),
            variable=self.tkopt.highlight_piles,
            command=self.mOptEnableHighlightPiles)
        submenu.add_checkbutton(
            label=n_("Enable highlight &cards"),
            variable=self.tkopt.highlight_cards,
            command=self.mOptEnableHighlightCards)
        submenu.add_checkbutton(
            label=n_("Enable highlight same &rank"),
            variable=self.tkopt.highlight_samerank,
            command=self.mOptEnableHighlightSameRank)
        submenu.add_checkbutton(
            label=n_("Highlight &no matching"),
            variable=self.tkopt.highlight_not_matching,
            command=self.mOptEnableHighlightNotMatching)
        submenu.add_separator()
        submenu.add_checkbutton(
            label=n_("&Show removed tiles (in Mahjongg games)"),
            variable=self.tkopt.mahjongg_show_removed,
            command=self.mOptMahjonggShowRemoved)
        submenu.add_checkbutton(
            label=n_("Show hint &arrow (in Shisen-Sho games)"),
            variable=self.tkopt.shisen_show_hint,
            command=self.mOptShisenShowHint)
        submenu.add_checkbutton(
            label=n_("&Deal all cards (in Accordion type games)"),
            variable=self.tkopt.accordion_deal_all,
            command=self.mOptAccordionDealAll)
        menu.add_separator()
        label = n_("&Sound...")
        menu.add_command(
            label=label, command=self.mOptSoundDialog)
        # cardsets
        if USE_PIL:
            submenu = MfxMenu(menu, label=n_("Card si&ze"))
            submenu.add_command(
                label=n_("&Increase the card size"),
                command=self.mIncreaseCardset, accelerator=m+"+")
            submenu.add_command(
                label=n_("&Decrease the card size"),
                command=self.mDecreaseCardset, accelerator=m+"-")
            submenu.add_command(
                label=n_("&Reset the card size"),
                command=self.mResetCardset)
            submenu.add_separator()
            submenu.add_checkbutton(
                label=n_("&Auto scaling"), variable=self.tkopt.auto_scale,
                command=self.mOptAutoScale, accelerator=m+'0')
            submenu.add_checkbutton(
                label=n_("&Preserve aspect ratio"),
                variable=self.tkopt.preserve_aspect_ratio,
                command=self.mOptPreserveAspectRatio)
            submenu = MfxMenu(menu, label=n_("Card la&yout"))
            submenu.add_checkbutton(
                label=n_("&Spread stacks"), variable=self.tkopt.spread_stacks,
                command=self.mOptSpreadStacks)
            submenu.add_checkbutton(
                label=n_("&Center layout"), variable=self.tkopt.center_layout,
                command=self.mOptCenterLayout)
        # manager = self.app.cardset_manager
        # n = manager.len()
        menu.add_command(
            label=n_("Cards&et..."),
            command=self.mSelectCardsetDialog, accelerator=m+"E")
        menu.add_command(
            label=n_("Table t&ile..."),
            command=self.mSelectTileDialog)
        # this submenu will get set by updateBackgroundImagesMenu()
        submenu = MfxMenu(menu, label=n_("Card &background"))
        submenu = MfxMenu(menu, label=n_("Card &view"))
        submenu.add_checkbutton(
            label=n_("Card shado&w"), variable=self.tkopt.shadow,
            command=self.mOptShadow)
        submenu.add_checkbutton(
            label=n_("Shade &legal moves"), variable=self.tkopt.shade,
            command=self.mOptShade)
        submenu.add_checkbutton(
            label=n_("&Negative cards bottom"),
            variable=self.tkopt.negative_bottom,
            command=self.mOptNegativeBottom)
        submenu.add_checkbutton(
            label=n_("Shrink face-down cards"),
            variable=self.tkopt.shrink_face_down,
            command=self.mOptShrinkFaceDown)
        submenu.add_checkbutton(
            label=n_("Shade &filled stacks"),
            variable=self.tkopt.shade_filled_stacks,
            command=self.mOptShadeFilledStacks)
        submenu = MfxMenu(menu, label=n_("A&nimations"))
        submenu.add_radiobutton(
            label=n_("&None"), variable=self.tkopt.animations, value=0,
            command=self.mOptAnimations)
        submenu.add_radiobutton(
            label=n_("&Very fast"), variable=self.tkopt.animations, value=1,
            command=self.mOptAnimations)
        submenu.add_radiobutton(
            label=n_("&Fast"), variable=self.tkopt.animations, value=2,
            command=self.mOptAnimations)
        submenu.add_radiobutton(
            label=n_("&Medium"), variable=self.tkopt.animations, value=3,
            command=self.mOptAnimations)
        submenu.add_radiobutton(
            label=n_("&Slow"), variable=self.tkopt.animations, value=4,
            command=self.mOptAnimations)
        submenu.add_radiobutton(
            label=n_("V&ery slow"), variable=self.tkopt.animations, value=5,
            command=self.mOptAnimations)
        submenu.add_separator()
        submenu.add_checkbutton(
            label=n_("&Redeal animation"),
            variable=self.tkopt.redeal_animation,
            command=self.mRedealAnimation)
        if Image:
            submenu.add_checkbutton(
                label=n_("&Winning animation"),
                variable=self.tkopt.win_animation,
                command=self.mWinAnimation)
        submenu = MfxMenu(menu, label=n_("&Mouse"))
        submenu.add_radiobutton(
            label=n_("&Drag-and-Drop"), variable=self.tkopt.mouse_type,
            value='drag-n-drop',
            command=self.mOptMouseType)
        submenu.add_radiobutton(
            label=n_("&Point-and-Click"), variable=self.tkopt.mouse_type,
            value='point-n-click',
            command=self.mOptMouseType)
        submenu.add_radiobutton(
            label=n_("&Sticky mouse"), variable=self.tkopt.mouse_type,
            value='sticky-mouse',
            command=self.mOptMouseType)
        submenu.add_separator()
        submenu.add_checkbutton(
            label=n_("Use mouse for undo/redo"),
            variable=self.tkopt.mouse_undo,
            command=self.mOptMouseUndo)
        menu.add_separator()
        menu.add_command(label=n_("&Fonts..."), command=self.mOptFonts)
        menu.add_command(label=n_("&Colors..."), command=self.mOptColors)
        menu.add_command(label=n_("Time&outs..."), command=self.mOptTimeouts)
        menu.add_separator()
        self.createThemesMenu(menu)
        submenu = MfxMenu(menu, label=n_("&Toolbar"))
        createToolbarMenu(self, submenu)
        submenu = MfxMenu(menu, label=n_("Stat&usbar"))
        submenu.add_checkbutton(
            label=n_("Show &statusbar"), variable=self.tkopt.statusbar,
            command=self.mOptStatusbar)
        submenu.add_checkbutton(
            label=n_("Show &number of cards"), variable=self.tkopt.num_cards,
            command=self.mOptNumCards)
        submenu.add_checkbutton(
            label=n_("Show &help bar"), variable=self.tkopt.helpbar,
            command=self.mOptHelpbar)
        # if not USE_PIL:
        menu.add_checkbutton(
            label=n_("Save games &geometry"),
            variable=self.tkopt.save_games_geometry,
            command=self.mOptSaveGamesGeometry)
        menu.add_checkbutton(
            label=n_("&Demo logo"), variable=self.tkopt.demo_logo,
            command=self.mOptDemoLogo)
        menu.add_checkbutton(
            label=n_("Startup splash sc&reen"),
            variable=self.tkopt.splashscreen,
            command=self.mOptSplashscreen)
        #  menu.add_separator()
        #  menu.add_command(label="Save options", command=self.mOptSave)

        if self.progress:
            self.progress.update(step=1)

        # macOS: tk creates the menu item "Help->PySolFC Help", therefore
        # we will not create a duplicate "Help->Contents" item.
        # The tk-provided menu item expects this callback.
        self.top.createcommand('tk::mac::ShowHelp', self.mHelp)

        menu = MfxMenu(self.menubar, label=n_("&Help"))
        if WIN_SYSTEM != "aqua":
            menu.add_command(
                label=n_("&Contents"),
                command=self.mHelp, accelerator=m+"F1")
        menu.add_command(
            label=n_("&How to use PySol"),
            command=self.mHelpHowToPlay)
        menu.add_command(
            label=n_("&Rules for this game"),
            command=self.mHelpRules, accelerator="F1")
        menu.add_command(
            label=n_("&License terms"),
            command=self.mHelpLicense)
        # menu.add_command(label=n_("What's &new ?"), command=self.mHelpNews)
        if WIN_SYSTEM != "aqua":
            menu.add_separator()
            menu.add_command(
                label=_("&About %s...") % TITLE,
                command=self.mHelpAbout)

        MfxMenubar.addPath = None

        # FIXME: all key bindings should be *added* to keyPressHandler
        ctrl = "Control-"
        if sys.platform == "darwin":
            ctrl = "Command-"
        self._bindKey("",   "n", self.mNewGame)
        self._bindKey(ctrl, "w", self.mSelectGameDialog)
        self._bindKey(ctrl, "v", self.mSelectGameDialogWithPreview)
        self._bindKey(ctrl, "r", lambda e: self.mSelectRandomGame())
        self._bindKey(ctrl, "m", self.mSelectGameById)
        self._bindKey(ctrl, "n", self.mNewGameWithNextId)
        self._bindKey(ctrl, "o", self.mOpen)
        self._bindKey(ctrl, "s", self.mSave)
        self._bindKey(ctrl, "x", self.mHoldAndQuit)
        self._bindKey(ctrl, "q", self.mQuit)
        self._bindKey(ctrl, "z", self.mUndo)
        self._bindKey("",   "z", self.mUndo)
        self._bindKey("",   "BackSpace", self.mUndo)    # undocumented
        self._bindKey("",   "KP_Enter", self.mUndo)     # undocumented
        self._bindKey("",   "r", self.mRedo)
        self._bindKey(ctrl, "g", self.mRestart)
        self._bindKey("",   "space", self.mDeal)        # undocumented
        self._bindKey(ctrl, "y", lambda e: self.mPlayerStats(mode=100))
        self._bindKey(ctrl, "t", lambda e: self.mPlayerStats(mode=105))
        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("",   "F3", self.mFindCard)
        self._bindKey(ctrl, "d", self.mDemo)
        self._bindKey(ctrl, "e", self.mSelectCardsetDialog)
        if USE_PIL:
            self._bindKey(ctrl, "plus", self.mIncreaseCardset)
            self._bindKey(ctrl, "equal", self.mIncreaseCardset)
            self._bindKey(ctrl, "minus", self.mDecreaseCardset)
            self._bindKey(ctrl, "0", self.mOptAutoScale)
        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
        self._bindKey("",   "p", self.mPause)
        self._bindKey("",   "Pause", self.mPause)       # undocumented
        self._bindKey("",   "Escape", self.mIconify)    # 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)

        self._bindKey("",   "F2", self.mStackDesk)
        #
        # undocumented, devel
        self._bindKey("", "slash", lambda e: self.mPlayerStats(mode=106))
        #
        self._bindKey("",   "f", self.mShuffle)

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

        # undocumented, devel
        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):
        sequence = "<" + modifier + "KeyPress-" + key + ">"
        bind(self.top, sequence, func)
        if len(key) == 1 and key != key.upper():
            key = key.upper()
            sequence = "<" + modifier + "KeyPress-" + key + ">"
            bind(self.top, sequence, func)

    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

    #
    # Select Game menu creation
    #

    def _addSelectGameMenu(self, menu):
        # games = map(self.app.gdb.get,
        #   self.app.gdb.getGamesIdSortedByShortName())
        games = list(map(
            self.app.gdb.get, self.app.gdb.getGamesIdSortedByName()))
        # games = tuple(games)
        # menu = MfxMenu(menu, label="Select &game")
        m = "Ctrl-"
        if sys.platform == "darwin":
            m = "Cmd-"
        menu.add_command(label=n_("All &games..."), accelerator=m+"V",
                         command=self.mSelectGameDialogWithPreview)
        if not SELECT_GAME_MENU:
            return
        menu.add_separator()
        self._addSelectPopularGameSubMenu(games, menu, self.mSelectGame,
                                          self.tkopt.gameid)
        self._addSelectFrenchGameSubMenu(games, menu, self.mSelectGame,
                                         self.tkopt.gameid)
        if self.progress:
            self.progress.update(step=1)
        self._addSelectMahjonggGameSubMenu(games, menu, self.mSelectGame,
                                           self.tkopt.gameid)
        self._addSelectOrientalGameSubMenu(games, menu, self.mSelectGame,
                                           self.tkopt.gameid)
        self._addSelectSpecialGameSubMenu(games, menu, self.mSelectGame,
                                          self.tkopt.gameid)
        self._addSelectCustomGameSubMenu(games, menu, self.mSelectGame,
                                         self.tkopt.gameid)
        menu.add_separator()
        if self.progress:
            self.progress.update(step=1)
        self._addSelectAllGameSubMenu(games, menu, self.mSelectGame,
                                      self.tkopt.gameid)

    def _addSelectGameSubMenu(self, games, menu, 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 = list(filter(select_func, games))
            if not g:
                continue
            if need_sep:
                menu.add_separator()
                need_sep = 0
            submenu = MfxMenu(menu, label=label)
            self._addSelectGameSubSubMenu(g, submenu, command, variable)

    def _getNumGames(self, games, select_data):
        ngames = 0
        for label, select_func in select_data:
            ngames += len(list(filter(select_func, games)))
        return ngames

    def _addSelectMahjonggGameSubMenu(self, games, menu, command, variable):
        def select_func(gi):
            return gi.si.game_type == GI.GT_MAHJONGG
        mahjongg_games = list(filter(select_func, games))
        if len(mahjongg_games) == 0:
            return
        #
        menu = MfxMenu(menu, label=n_("&Mahjongg games"))

        def add_menu(games, c0, c1, menu=menu,
                     variable=variable, command=command):
            if not games:
                return
            label = c0 + ' - ' + c1
            if c0 == c1:
                label = c0
            submenu = MfxMenu(menu, label=label, name=None)
            self._addSelectGameSubSubMenu(games, submenu, command,
                                          variable, short_name=True)

        games = {}
        for gi in mahjongg_games:
            c = gi.short_name.strip()[0]
            if c in games:
                games[c].append(gi)
            else:
                games[c] = [gi]
        games = list(games.items())
        games.sort()
        g0 = []
        c0 = c1 = games[0][0]
        for c, g1 in games:
            if len(g0)+len(g1) >= self.cb_max:
                add_menu(g0, c0, c1)
                g0 = g1
                c0 = c1 = c
            else:
                g0 += g1
                c1 = c
        add_menu(g0, c0, c1)

    def _addSelectPopularGameSubMenu(self, games, menu, command, variable):
        def select_func(gi):
            return gi.si.game_flags & GI.GT_POPULAR
        if len(list(filter(select_func, games))) == 0:
            return
        data = (n_("&Popular games"), select_func)
        self._addSelectGameSubMenu(games, menu, (data, ),
                                   self.mSelectGamePopular,
                                   self.tkopt.gameid_popular)

    def _addSelectFrenchGameSubMenu(self, games, menu, command, variable):
        if self._getNumGames(games, GI.SELECT_GAME_BY_TYPE) == 0:
            return
        submenu = MfxMenu(menu, label=n_("&French games"))
        self._addSelectGameSubMenu(games, submenu, GI.SELECT_GAME_BY_TYPE,
                                   self.mSelectGame, self.tkopt.gameid)

    def _addSelectOrientalGameSubMenu(self, games, menu, command, variable):
        if self._getNumGames(games, GI.SELECT_ORIENTAL_GAME_BY_TYPE) == 0:
            return
        submenu = MfxMenu(menu, label=n_("&Oriental games"))
        self._addSelectGameSubMenu(games, submenu,
                                   GI.SELECT_ORIENTAL_GAME_BY_TYPE,
                                   self.mSelectGame, self.tkopt.gameid)

    def _addSelectSpecialGameSubMenu(self, games, menu, command, variable):
        if self._getNumGames(games, GI.SELECT_ORIENTAL_GAME_BY_TYPE) == 0:
            return
        submenu = MfxMenu(menu, label=n_("&Special games"))
        self._addSelectGameSubMenu(games, submenu,
                                   GI.SELECT_SPECIAL_GAME_BY_TYPE,
                                   self.mSelectGame, self.tkopt.gameid)

    def _addSelectCustomGameSubMenu(self, games, menu, command, variable):
        submenu = MfxMenu(menu, label=n_("&Custom games"))

        def select_func(gi):
            return gi.si.game_type == GI.GT_CUSTOM
        games = list(filter(select_func, games))
        self.updateGamesMenu(submenu, games)

    def _addSelectAllGameSubMenu(self, games, menu, command, variable):
        if menu.name != "allgamesbyname":
            menu = MfxMenu(menu, label=n_("&All games by name"))
        n, d = 0, self.cb_max
        i = 0
        while True:
            if self.progress:
                self.progress.update(step=1)
            columnbreak = i > 0 and (i % d) == 0
            i += 1
            if not games[n:n+d]:
                break
            m = min(n+d-1, len(games)-1)
            label = games[n].name[:3] + ' - ' + games[m].name[:3]
            submenu = MfxMenu(menu, label=label, name=None)
            self._addSelectGameSubSubMenu(games[n:n+d], submenu,
                                          command, variable)
            n += d
            if columnbreak:
                menu.entryconfigure(i, columnbreak=columnbreak)

    def _addSelectGameSubSubMenu(self, games, menu, command, variable,
                                 short_name=False):
        # cb = (25, self.cb_max) [ len(g) > 4 * 25 ]
        # cb = min(cb, self.cb_max)
        cb = self.cb_max
        for i in range(len(games)):
            gi = games[i]
            columnbreak = i > 0 and (i % cb) == 0
            if short_name:
                label = gi.short_name
            else:
                label = gi.name
            # optimized by inlining
            menu.tk.call((menu._w, 'add', 'radiobutton') +
                         menu._options({'command': command,
                                        'variable': variable,
                                        'columnbreak': columnbreak,
                                        'value': gi.id,
                                        'label': label}))

    def updateGamesMenu(self, menu, games):
        menu.delete(0, 'last')
        if len(games) == 0:
            menu.add_radiobutton(label=_('<none>'), name=None,
                                 state='disabled')
        elif len(games) > self.cb_max*4:
            games.sort(key=lambda x: x.name)
            self._addSelectAllGameSubMenu(games, menu,
                                          command=self.mSelectGame,
                                          variable=self.tkopt.gameid)
        else:
            self._addSelectGameSubSubMenu(games, menu,
                                          command=self.mSelectGame,
                                          variable=self.tkopt.gameid)

    #
    # Select Game menu actions
    #

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

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

    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._cancelDrag()
                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(break_pause=False):
            return
        self.game.setCursor(cursor=CURSOR_WATCH)
        after_idle(self.top, self.__restoreCursor)
        d = self._calcSelectGameDialog()(
            self.top, title=_("Select game"),
            app=self.app, gameid=self.game.id)
        return self._mSelectGameDialog(d)

    def mSelectGameDialogWithPreview(self, *event):
        if self._cancelDrag(break_pause=False):
            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 = self._calcSelectGameDialogWithPreview()(
            self.top, title=_("Select game"),
            app=self.app, gameid=self.game.id,
            bookmark=bookmark)
        return self._mSelectGameDialog(d)

    #
    # menubar overrides
    #

    def updateFavoriteGamesMenu(self):
        gameids = self.app.opt.favorite_gameid
        submenu = self.menupath[".menubar.file.favoritegames"][2]
        games = []
        for id in gameids:
            gi = self.app.getGameInfo(id)
            if gi:
                games.append(gi)
        self.updateGamesMenu(submenu, games)
        state = self._getEnabledState
        in_favor = self.app.game.id in gameids
        menu, index, submenu = self.menupath[".menubar.file.addtofavorites"]
        menu.entryconfig(index, state=state(not in_favor))
        menu, index, submenu = \
            self.menupath[".menubar.file.removefromfavorites"]
        menu.entryconfig(index, state=state(in_favor))

    def updateRecentGamesMenu(self, gameids):
        submenu = self.menupath[".menubar.file.recentgames"][2]
        games = []
        for id in gameids:
            gi = self.app.getGameInfo(id)
            if gi:
                games.append(gi)
        self.updateGamesMenu(submenu, games)

    def updateCustomGamesMenu(self):
        menu = self.menupath[".menubar.select.customgames"][2]
        menu2 = self.menupath[".menubar.select.allgamesbyname"][2]

        def select_func_visible(gi):
            return gi.si.game_type != GI.GT_HIDDEN

        def select_func_custom(gi):
            return gi.si.game_type == GI.GT_CUSTOM

        games = list(map(self.app.gdb.get,
                         self.app.gdb.getGamesIdSortedByName()))
        games = list(filter(select_func_visible, games))
        self.progress = False
        self.updateGamesMenu(menu2, games)
        games = list(filter(select_func_custom, games))
        self.updateGamesMenu(menu, games)

    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")
        # 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)
        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

    def _setCommentMenu(self, v):
        self.tkopt.comment.set(v)

    def _setPauseMenu(self, v):
        self.tkopt.pause.set(v)

    #
    # menu actions
    #

    DEFAULTEXTENSION = ".pso"
    FILETYPES = ((_("%s files") % TITLE, "*" + DEFAULTEXTENSION),
                 (_("All files"), "*"))

    def mAddFavor(self, *event):
        gameid = self.app.game.id
        if gameid not in self.app.opt.favorite_gameid:
            self.app.opt.favorite_gameid.append(gameid)
            self.updateFavoriteGamesMenu()

    def mDelFavor(self, *event):
        gameid = self.app.game.id
        if gameid in self.app.opt.favorite_gameid:
            self.app.opt.favorite_gameid.remove(gameid)
            self.updateFavoriteGamesMenu()

    def mOpen(self, *event):
        if self._cancelDrag(break_pause=False):
            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 = tkinter_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 mExportCurrentLayout(self, *event):
        if self._cancelDrag(break_pause=False):
            return
        game = self.game
        if not self.menustate.save_as:
            return
        if not game.Solver_Class:
            d = self._calc_MfxMessageDialog()(
                self.top, title=_('Export game error'),
                text=_('''
Unsupported game for export.
'''),
                bitmap='error')
            return

        filename = game.filename
        if not filename:
            filename = self.app.getGameSaveName(self.game.id)
            if os.name == "posix" or os.path.supports_unicode_filenames:
                filename += "-" + self.game.getGameNumber(format=0)
            else:
                filename += "-01"
            filename += ".board"
        idir, ifile = os.path.split(os.path.normpath(filename))
        if not idir:
            idir = self.app.dn.savegames
        # print self.game.filename, ifile
        d = tkinter_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)
            with open(filename, 'w') as fh:
                game = self.game
                fh.write(game.Solver_Class(game, self).calcBoardString())
            self.updateMenus()

    def mImportStartingLayout(self, *event):
        if self._cancelDrag(break_pause=False):
            return
        game = self.game
        if not game.Solver_Class:
            d = self._calc_MfxMessageDialog()(
                self.top, title=_('Import game error'),
                text=_('''
Unsupported game for import.
'''),
                bitmap='error')
            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 = tkinter_tkfiledialog.Open()
        key = 'PYSOL_DEBUG_IMPORT'
        if key not in os.environ:
            filename = d.show(filetypes=self.FILETYPES,
                              defaultextension=self.DEFAULTEXTENSION,
                              initialdir=idir, initialfile=ifile)
        else:
            filename = os.environ[key]
        if filename:
            filename = os.path.normpath(filename)
            # filename = os.path.normcase(filename)
            if os.path.isfile(filename):
                with open(filename, 'r+b') as fh:
                    game = self.game
                    try:
                        game.Solver_Class(game, self).importFile(
                            fh, game, self)
                    except PySolHintLayoutImportError as err:
                        self._calc_MfxMessageDialog()(
                            self.top,
                            title=_('Import game error'),
                            text=err.format(),
                            bitmap='error'
                        )
                        game.busy = False
                        game.endGame()
                        game.newGame()

    def mSaveAs(self, *event):
        if self._cancelDrag(break_pause=False):
            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)
            elif os.path.supports_unicode_filenames:  # new in python 2.3
                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 = tkinter_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 mPause(self, *args):
        if not self.game:
            return
        if not self.game.pause:
            if self._cancelDrag():
                return
        self.game.doPause()
        self.tkopt.pause.set(self.game.pause)

    def mOptSoundDialog(self, *args):
        if self._cancelDrag(break_pause=False):
            return
        self._calcSoundOptionsDialog()(
            self.top, _("Sound settings"), self.app)
        self.tkopt.sound.set(self.app.opt.sound)

    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(break_pause=False):
            return
        self.app.opt.quickplay = self.tkopt.quickplay.get()

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

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

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

    def mOptEnableShuffle(self, *args):
        if self._cancelDrag(break_pause=False):
            return
        self.app.opt.shuffle = self.tkopt.shuffle.get()
        self.game.updateMenus()

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

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

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

    def mOptEnableHighlightNotMatching(self, *args):
        if self._cancelDrag(break_pause=False):
            return
        self.app.opt.highlight_not_matching = \
            self.tkopt.highlight_not_matching.get()
        # self.game.updateMenus()

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

    def mRedealAnimation(self, *args):
        if self._cancelDrag(break_pause=False):
            return
        self.app.opt.redeal_animation = self.tkopt.redeal_animation.get()

    def mWinAnimation(self, *args):
        if self._cancelDrag(break_pause=False):
            return
        self.app.opt.win_animation = self.tkopt.win_animation.get()

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

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

    def mOptShrinkFaceDown(self, *args):
        if self._cancelDrag(break_pause=False):
            return
        self.app.opt.shrink_face_down = self.tkopt.shrink_face_down.get()
        self.game.endGame(bookmark=1)
        self.game.quitGame(bookmark=1)

    def mOptShadeFilledStacks(self, *args):
        if self._cancelDrag(break_pause=False):
            return
        self.app.opt.shade_filled_stacks = self.tkopt.shade_filled_stacks.get()
        self.game.endGame(bookmark=1)
        self.game.quitGame(bookmark=1)

    def mOptMahjonggShowRemoved(self, *args):
        if self._cancelDrag():
            return
        self.app.opt.mahjongg_show_removed = \
            self.tkopt.mahjongg_show_removed.get()
        # self.game.updateMenus()
        self.game.endGame(bookmark=1)
        self.game.quitGame(bookmark=1)

    def mOptShisenShowHint(self, *args):
        if self._cancelDrag(break_pause=False):
            return
        self.app.opt.shisen_show_hint = self.tkopt.shisen_show_hint.get()
        # self.game.updateMenus()

    def mOptAccordionDealAll(self, *args):
        if self._cancelDrag(break_pause=False):
            return
        self.app.opt.accordion_deal_all = self.tkopt.accordion_deal_all.get()
        # self.game.updateMenus()

    def _updateCardSize(self):
        geom = (self.app.canvas.winfo_width(),
                self.app.canvas.winfo_height())
        self.app.opt.game_geometry = geom
        self.app.game.resizeGame(card_size_manually=True)
        if self.app.opt.auto_scale or self.app.opt.spread_stacks:
            w, h = self.app.opt.game_geometry
            self.app.canvas.setInitialSize(w, h, scrollregion=False)
            # Resize a second time to auto scale
            self.app.game.resizeGame(card_size_manually=False)
        else:
            w = int(round(self.app.game.width * self.app.opt.scale_x))
            h = int(round(self.app.game.height * self.app.opt.scale_y))
            self.app.canvas.setInitialSize(w, h)
            self.app.top.wm_geometry("")    # cancel user-specified geometry
        # self.app.top.update_idletasks()
        self.app.setTile(self.app.tabletile_index)
        self.tkopt.tabletile.set(self.app.tabletile_index)

        self.updateMenus()

    def mIncreaseCardset(self, *event):
        if self._cancelDrag(break_pause=True):
            return
        if self.app.opt.scale_x < 4:
            self.app.opt.scale_x += 0.1
        else:
            return
        if self.app.opt.scale_y < 4:
            self.app.opt.scale_y += 0.1
        else:
            return
        self.app.opt.auto_scale = False
        self.tkopt.auto_scale.set(False)
        self._updateCardSize()

    def mDecreaseCardset(self, *event):
        if self._cancelDrag(break_pause=True):
            return
        if self.app.opt.scale_x > 0.5:
            self.app.opt.scale_x -= 0.1
        else:
            return
        if self.app.opt.scale_y > 0.5:
            self.app.opt.scale_y -= 0.1
        else:
            return
        self.app.opt.auto_scale = False
        self.tkopt.auto_scale.set(False)
        self._updateCardSize()

    def mResetCardset(self, *event):
        if self._cancelDrag(break_pause=True):
            return
        self.app.opt.scale_x = 1
        self.app.opt.scale_y = 1

        self.app.opt.auto_scale = False
        self.tkopt.auto_scale.set(False)
        self._updateCardSize()

    def mOptAutoScale(self, *event):
        if self._cancelDrag(break_pause=True):
            return
        auto_scale = not self.app.opt.auto_scale

        # In the future, it should be possible to use both options together,
        # but the current logic conflicts, so not allowed for now.
        self.app.opt.spread_stacks = False
        self.tkopt.spread_stacks.set(False)

        self.app.opt.auto_scale = auto_scale
        self.tkopt.auto_scale.set(auto_scale)
        self._updateCardSize()

    def mOptPreserveAspectRatio(self, *event):
        if self._cancelDrag(break_pause=True):
            return
        preserve_aspect_ratio = not self.app.opt.preserve_aspect_ratio

        self.app.opt.preserve_aspect_ratio = preserve_aspect_ratio
        self.tkopt.preserve_aspect_ratio.set(preserve_aspect_ratio)
        self._updateCardSize()

    def mOptSpreadStacks(self, *event):
        if self._cancelDrag(break_pause=True):
            return
        spread_stacks = not self.app.opt.spread_stacks

        # In the future, it should be possible to use both options together,
        # but the current logic conflicts, so not allowed for now.
        self.app.opt.auto_scale = False
        self.tkopt.auto_scale.set(False)

        self.app.opt.spread_stacks = spread_stacks
        self.tkopt.spread_stacks.set(spread_stacks)
        self._updateCardSize()

    def mOptCenterLayout(self, *event):
        if self._cancelDrag(break_pause=True):
            return
        self.app.opt.center_layout = not self.app.opt.center_layout
        self._updateCardSize()

    def _mOptCardback(self, index):
        if self._cancelDrag(break_pause=False):
            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)
        image = self.app.images.getBack(update=True)
        for card in self.game.cards:
            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 mOptChangeTableTile(self, *event):
        if self._cancelDrag(break_pause=False):
            return
        n = self.app.tabletile_manager.len()
        if n >= 2:
            i = (self.tkopt.tabletile.get() + 1) % n
            if self.app.setTile(i):
                self.tkopt.tabletile.set(i)

    def mSelectTileDialog(self, *event):
        if self._cancelDrag(break_pause=False):
            return
        key = self.app.tabletile_index
        if key <= 0:
            key = self.app.opt.colors['table']  # .lower()
        d = self._calcSelectTileDialogWithPreview()(
            self.top, app=self.app,
            title=_("Select table background"),
            manager=self.app.tabletile_manager,
            key=key)
        if d.status == 0 and d.button == 0:
            if isinstance(d.key, str):
                tile = self.app.tabletile_manager.get(0)
                tile.color = d.key
                if self.app.setTile(0):
                    self.tkopt.tabletile.set(0)
            elif d.key > 0 and d.key != self.app.tabletile_index:
                if self.app.setTile(d.key):
                    self.tkopt.tabletile.set(d.key)

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

    def mOptToolbarStyle(self, *event):
        # if self._cancelDrag(break_pause=False): return
        self.setToolbarStyle(self.tkopt.toolbar_style.get())

    def mOptToolbarCompound(self, *event):
        # if self._cancelDrag(break_pause=False): return
        self.setToolbarCompound(self.tkopt.toolbar_compound.get())

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

    def mOptToolbarConfig(self, w):
        self.toolbarConfig(w, self.tkopt.toolbar_vars[w].get())

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

    def mOptNumCards(self, *event):
        if self._cancelDrag(break_pause=False):
            return
        self.app.opt.num_cards = self.tkopt.num_cards.get()

    def mOptHelpbar(self, *event):
        if self._cancelDrag(break_pause=False):
            return
        if not self.app.helpbar:
            return
        show = self.tkopt.helpbar.get()
        self.app.opt.helpbar = show
        resize = not self.app.opt.save_games_geometry
        if self.app.helpbar.show(show, resize=resize):
            self.top.update_idletasks()

    def mOptSaveGamesGeometry(self, *event):
        if self._cancelDrag(break_pause=False):
            return
        self.app.opt.save_games_geometry = self.tkopt.save_games_geometry.get()

    def mOptDemoLogo(self, *event):
        if self._cancelDrag(break_pause=False):
            return
        self.app.opt.demo_logo = self.tkopt.demo_logo.get()

    def mOptSplashscreen(self, *event):
        if self._cancelDrag(break_pause=False):
            return
        self.app.opt.splashscreen = self.tkopt.splashscreen.get()

    def mOptMouseType(self, *event):
        if self._cancelDrag(break_pause=False):
            return
        self.app.opt.mouse_type = self.tkopt.mouse_type.get()

    def mOptMouseUndo(self, *event):
        if self._cancelDrag(break_pause=False):
            return
        self.app.opt.mouse_undo = self.tkopt.mouse_undo.get()

    def mOptNegativeBottom(self, *event):
        if self._cancelDrag():
            return
        self.app.opt.negative_bottom = self.tkopt.negative_bottom.get()
        self.app.updateCardset()
        self.game.endGame(bookmark=1)
        self.game.quitGame(bookmark=1)

    #
    # toolbar support
    #

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

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

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

    def setToolbarCompound(self, compound):
        if self._cancelDrag(break_pause=False):
            return
        self.app.opt.toolbar_compound = compound
        self.tkopt.toolbar_compound.set(compound)          # update radiobutton
        if self.app.toolbar.setCompound(compound):
            self.game.updateStatus(player=self.app.opt.player)
            self.top.update_idletasks()

    def wizardDialog(self, edit=False):
        from pysollib.wizardutil import write_game, reset_wizard
        WizardDialog = self._calcWizardDialog()

        if edit:
            reset_wizard(self.game)
        else:
            reset_wizard(None)
        d = WizardDialog(self.top, _('Solitaire Wizard'), self.app)
        if d.status == 0 and d.button == 0:
            try:
                if edit:
                    gameid = write_game(self.app, game=self.game)
                else:
                    gameid = write_game(self.app)
            except Exception as err:
                # if False:
                #    traceback.print_exc()
                self._calc_MfxMessageDialog()(
                    self.top, title=_('Save game error'),
                    text=_('''
Error while saving game.

%s
''') % str(err),
                    bitmap='error')
                return

            if SELECT_GAME_MENU:
                self.updateCustomGamesMenu()

            self.tkopt.gameid.set(gameid)
            self._mSelectGame(gameid, force=True)

    def mWizard(self, *event):
        if self._cancelDrag(break_pause=False):
            return
        self.wizardDialog()

    def mWizardEdit(self, *event):
        if self._cancelDrag(break_pause=False):
            return
        self.wizardDialog(edit=True)

    def mWizardDelete(self, *event):
        if self._cancelDrag(break_pause=False):
            return
        if not self.game.areYouSure(_("Delete game"),
                                    _("Delete the game "
                                      + self.game.gameinfo.name + "?")):
            return
        from pysollib.wizardutil import delete_game
        delete_game(self.game)
        self.game.endGame()
        self.game.quitGame(2)

        if SELECT_GAME_MENU:
            self.updateCustomGamesMenu()

    def toolbarConfig(self, w, v):
        if self._cancelDrag(break_pause=False):
            return
        self.app.opt.toolbar_vars[w] = v
        self.app.toolbar.config(w, v)
        self.top.update_idletasks()

    #
    # stacks descriptions
    #

    def mStackDesk(self, *event):
        if self.game.stackdesc_list:
            self.game.deleteStackDesc()
        else:
            if self._cancelDrag(break_pause=True):
                return
            self.game.showStackDesc()
