1import math
2import os
3import re
4import sys
5
6from pysollib.gamedb import GI
7from pysollib.hint import PySolHintLayoutImportError
8from pysollib.mfxutil import Image, USE_PIL
9from pysollib.mfxutil import Struct, kwdefault
10from pysollib.mygettext import _, n_
11from pysollib.settings import SELECT_GAME_MENU
12from pysollib.settings import TITLE, WIN_SYSTEM
13from pysollib.settings import USE_FREECELL_SOLVER
14from pysollib.ui.tktile.tkconst import COMPOUNDS, CURSOR_WATCH, EVENT_HANDLED
15from pysollib.ui.tktile.tkconst import EVENT_PROPAGATE
16from pysollib.ui.tktile.tkconst import TOOLBAR_BUTTONS
17from pysollib.ui.tktile.tkutil import after_idle, bind
18
19from six.moves import tkinter
20from six.moves import tkinter_tkfiledialog
21
22
23def createToolbarMenu(menubar, menu):
24    tearoff = menu.cget('tearoff')
25#     data_dir = os.path.join(menubar.app.dataloader.dir, 'images', 'toolbar')
26#     submenu = MfxMenu(menu, label=n_('Style'), tearoff=tearoff)
27#     for f in os.listdir(data_dir):
28#         d = os.path.join(data_dir, f)
29#         if os.path.isdir(d) and os.path.exists(os.path.join(d, 'small')):
30#             name = f.replace('_', ' ').capitalize()
31#             submenu.add_radiobutton(
32#               label=name,
33#               variable=menubar.tkopt.toolbar_style,
34#               value=f, command=menubar.mOptToolbarStyle)
35    submenu = MfxMenu(menu, label=n_('Compound'), tearoff=tearoff)
36    for comp, label in COMPOUNDS:
37        submenu.add_radiobutton(
38            label=label, variable=menubar.tkopt.toolbar_compound,
39            value=comp, command=menubar.mOptToolbarCompound)
40    menu.add_separator()
41    menu.add_radiobutton(label=n_("Hide"),
42                         variable=menubar.tkopt.toolbar, value=0,
43                         command=menubar.mOptToolbar)
44    menu.add_radiobutton(label=n_("Top"),
45                         variable=menubar.tkopt.toolbar, value=1,
46                         command=menubar.mOptToolbar)
47    menu.add_radiobutton(label=n_("Bottom"),
48                         variable=menubar.tkopt.toolbar, value=2,
49                         command=menubar.mOptToolbar)
50    menu.add_radiobutton(label=n_("Left"),
51                         variable=menubar.tkopt.toolbar, value=3,
52                         command=menubar.mOptToolbar)
53    menu.add_radiobutton(label=n_("Right"),
54                         variable=menubar.tkopt.toolbar, value=4,
55                         command=menubar.mOptToolbar)
56    #  menu.add_separator()
57    #  menu.add_radiobutton(label=n_("Small icons"),
58    #                       variable=menubar.tkopt.toolbar_size, value=0,
59    #                       command=menubar.mOptToolbarSize)
60    #  menu.add_radiobutton(label=n_("Large icons"),
61    #                       variable=menubar.tkopt.toolbar_size, value=1,
62    #                       command=menubar.mOptToolbarSize)
63    menu.add_separator()
64    submenu = MfxMenu(menu, label=n_('Visible buttons'), tearoff=tearoff)
65    for w in TOOLBAR_BUTTONS:
66        submenu.add_checkbutton(
67            label=_(w.capitalize()),
68            variable=menubar.tkopt.toolbar_vars[w],
69            command=lambda m=menubar, w=w: m.mOptToolbarConfig(w))
70
71
72# ************************************************************************
73# *
74# ************************************************************************
75
76class MfxMenubar(tkinter.Menu):
77    addPath = None
78
79    def __init__(self, master, **kw):
80        self.name = kw["name"]
81        tearoff = 0
82        self.n = kw["tearoff"] = int(kw.get("tearoff", tearoff))
83        tkinter.Menu.__init__(self, master, **kw)
84
85    def labeltoname(self, label):
86        # print label, type(label)
87        name = re.sub(r"[^0-9a-zA-Z]", "", label).lower()
88        label = _(label)
89        underline = label.find('&')
90        if underline >= 0:
91            label = label.replace('&', '')
92        return name, label, underline
93
94    def add(self, itemType, cnf={}):
95        label = cnf.get("label")
96        if label:
97            name = cnf.get('name')
98            if name:
99                del cnf['name']  # TclError: unknown option "-name"
100            else:
101                name, label, underline = self.labeltoname(label)
102                cnf["underline"] = cnf.get("underline", underline)
103            cnf["label"] = label
104            if name and self.addPath:
105                path = str(self._w) + "." + name
106                self.addPath(path, self, self.n, cnf.get("menu"))
107        tkinter.Menu.add(self, itemType, cnf)
108        self.n = self.n + 1
109
110
111class MfxMenu(MfxMenubar):
112    def __init__(self, master, label, underline=None, **kw):
113        if 'name' in kw:
114            name, label_underline = kw['name'], -1
115        else:
116            name, label, label_underline = self.labeltoname(label)
117        kwdefault(kw, name=name)
118        MfxMenubar.__init__(self, master, **kw)
119        if underline is None:
120            underline = label_underline
121        if master:
122            master.add_cascade(
123                menu=self, name=name, label=label, underline=underline)
124
125
126class PysolMenubarTkCommon:
127    def __init__(self, app, top, progress=None):
128        self._createTkOpt()
129        self._setOptions()
130        # init columnbreak
131        self.cb_max = int(self.top.winfo_screenheight()//23)
132        #  sh = self.top.winfo_screenheight()
133        #  self.cb_max = 22
134        #  if sh >= 600: self.cb_max = 27
135        #  if sh >= 768: self.cb_max = 32
136        #  if sh >= 1024: self.cb_max = 40
137        self.progress = progress
138        # create menus
139        self.menubar = None
140        self.menupath = {}
141        self.keybindings = {}
142        self._createMenubar()
143        self.top = top
144
145        if self.progress:
146            self.progress.update(step=1)
147
148        # set the menubar
149        self.updateBackgroundImagesMenu()
150        self.top.config(menu=self.menubar)
151
152    def _createTkOpt(self):
153        # structure to convert menu-options to Toolkit variables
154        self.tkopt = Struct(
155            gameid=tkinter.IntVar(),
156            gameid_popular=tkinter.IntVar(),
157            comment=tkinter.BooleanVar(),
158            autofaceup=tkinter.BooleanVar(),
159            autodrop=tkinter.BooleanVar(),
160            autodeal=tkinter.BooleanVar(),
161            quickplay=tkinter.BooleanVar(),
162            undo=tkinter.BooleanVar(),
163            bookmarks=tkinter.BooleanVar(),
164            hint=tkinter.BooleanVar(),
165            shuffle=tkinter.BooleanVar(),
166            highlight_piles=tkinter.BooleanVar(),
167            highlight_cards=tkinter.BooleanVar(),
168            highlight_samerank=tkinter.BooleanVar(),
169            highlight_not_matching=tkinter.BooleanVar(),
170            mahjongg_show_removed=tkinter.BooleanVar(),
171            shisen_show_hint=tkinter.BooleanVar(),
172            accordion_deal_all=tkinter.BooleanVar(),
173            sound=tkinter.BooleanVar(),
174            auto_scale=tkinter.BooleanVar(),
175            preserve_aspect_ratio=tkinter.BooleanVar(),
176            spread_stacks=tkinter.BooleanVar(),
177            center_layout=tkinter.BooleanVar(),
178            cardback=tkinter.IntVar(),
179            tabletile=tkinter.IntVar(),
180            animations=tkinter.IntVar(),
181            redeal_animation=tkinter.BooleanVar(),
182            win_animation=tkinter.BooleanVar(),
183            shadow=tkinter.BooleanVar(),
184            shade=tkinter.BooleanVar(),
185            shade_filled_stacks=tkinter.BooleanVar(),
186            shrink_face_down=tkinter.BooleanVar(),
187            toolbar=tkinter.IntVar(),
188            toolbar_style=tkinter.StringVar(),
189            toolbar_relief=tkinter.StringVar(),
190            toolbar_compound=tkinter.StringVar(),
191            toolbar_size=tkinter.IntVar(),
192            statusbar=tkinter.BooleanVar(),
193            num_cards=tkinter.BooleanVar(),
194            helpbar=tkinter.BooleanVar(),
195            save_games_geometry=tkinter.BooleanVar(),
196            splashscreen=tkinter.BooleanVar(),
197            demo_logo=tkinter.BooleanVar(),
198            mouse_type=tkinter.StringVar(),
199            mouse_undo=tkinter.BooleanVar(),
200            negative_bottom=tkinter.BooleanVar(),
201            pause=tkinter.BooleanVar(),
202            theme=tkinter.StringVar(),
203            toolbar_vars={},
204        )
205        for w in TOOLBAR_BUTTONS:
206            self.tkopt.toolbar_vars[w] = tkinter.BooleanVar()
207
208    def _setOptions(self):
209        tkopt, opt = self.tkopt, self.app.opt
210        # set state of the menu items
211        tkopt.autofaceup.set(opt.autofaceup)
212        tkopt.autodrop.set(opt.autodrop)
213        tkopt.autodeal.set(opt.autodeal)
214        tkopt.quickplay.set(opt.quickplay)
215        tkopt.undo.set(opt.undo)
216        tkopt.hint.set(opt.hint)
217        tkopt.shuffle.set(opt.shuffle)
218        tkopt.bookmarks.set(opt.bookmarks)
219        tkopt.highlight_piles.set(opt.highlight_piles)
220        tkopt.highlight_cards.set(opt.highlight_cards)
221        tkopt.highlight_samerank.set(opt.highlight_samerank)
222        tkopt.highlight_not_matching.set(opt.highlight_not_matching)
223        tkopt.shrink_face_down.set(opt.shrink_face_down)
224        tkopt.shade_filled_stacks.set(opt.shade_filled_stacks)
225        tkopt.mahjongg_show_removed.set(opt.mahjongg_show_removed)
226        tkopt.shisen_show_hint.set(opt.shisen_show_hint)
227        tkopt.accordion_deal_all.set(opt.accordion_deal_all)
228        tkopt.sound.set(opt.sound)
229        tkopt.auto_scale.set(opt.auto_scale)
230        tkopt.preserve_aspect_ratio.set(opt.preserve_aspect_ratio)
231        tkopt.spread_stacks.set(opt.spread_stacks)
232        tkopt.center_layout.set(opt.center_layout)
233        tkopt.cardback.set(self.app.cardset.backindex)
234        tkopt.tabletile.set(self.app.tabletile_index)
235        tkopt.animations.set(opt.animations)
236        tkopt.redeal_animation.set(opt.redeal_animation)
237        tkopt.win_animation.set(opt.win_animation)
238        tkopt.shadow.set(opt.shadow)
239        tkopt.shade.set(opt.shade)
240        tkopt.toolbar.set(opt.toolbar)
241        tkopt.toolbar_style.set(opt.toolbar_style)
242        tkopt.toolbar_relief.set(opt.toolbar_relief)
243        tkopt.toolbar_compound.set(opt.toolbar_compound)
244        tkopt.toolbar_size.set(opt.toolbar_size)
245        tkopt.toolbar_relief.set(opt.toolbar_relief)
246        tkopt.statusbar.set(opt.statusbar)
247        tkopt.num_cards.set(opt.num_cards)
248        tkopt.helpbar.set(opt.helpbar)
249        tkopt.save_games_geometry.set(opt.save_games_geometry)
250        tkopt.demo_logo.set(opt.demo_logo)
251        tkopt.splashscreen.set(opt.splashscreen)
252        tkopt.mouse_type.set(opt.mouse_type)
253        tkopt.mouse_undo.set(opt.mouse_undo)
254        tkopt.negative_bottom.set(opt.negative_bottom)
255        for w in TOOLBAR_BUTTONS:
256            tkopt.toolbar_vars[w].set(opt.toolbar_vars.get(w, False))
257
258    def connectGame(self, game):
259        self.game = game
260        if game is None:
261            return
262        assert self.app is game.app
263        tkopt = self.tkopt
264        tkopt.gameid.set(game.id)
265        tkopt.gameid_popular.set(game.id)
266        tkopt.comment.set(bool(game.gsaveinfo.comment))
267        tkopt.pause.set(self.game.pause)
268        if game.canFindCard():
269            self._connect_game_find_card_dialog(game)
270        else:
271            self._destroy_find_card_dialog()
272        self._connect_game_solver_dialog(game)
273
274    # create a GTK-like path
275    def _addPath(self, path, menu, index, submenu):
276        if path not in self.menupath:
277            # print path, menu, index, submenu
278            self.menupath[path] = (menu, index, submenu)
279
280    def _getEnabledState(self, enabled):
281        if enabled:
282            return "normal"
283        return "disabled"
284
285    def updateProgress(self):
286        if self.progress:
287            self.progress.update(step=1)
288
289    def _createMenubar(self):
290        MfxMenubar.addPath = self._addPath
291        kw = {"name": "menubar"}
292        self.menubar = MfxMenubar(self.top, **kw)
293
294        # init keybindings
295        bind(self.top, "<KeyPress>", self._keyPressHandler)
296
297        m = "Ctrl-"
298        if sys.platform == "darwin":
299            m = "Cmd-"
300
301        if WIN_SYSTEM == "aqua":
302            applemenu = MfxMenu(self.menubar, "apple")
303            applemenu.add_command(
304                label=_("&About %s") % TITLE, command=self.mHelpAbout)
305
306        menu = MfxMenu(self.menubar, n_("&File"))
307        menu.add_command(
308            label=n_("&New game"), command=self.mNewGame, accelerator="N")
309        submenu = MfxMenu(menu, label=n_("R&ecent games"))
310        # menu.add_command(label=n_("Select &random game"),
311        #   command=self.mSelectRandomGame, accelerator=m+"R")
312        submenu = MfxMenu(menu, label=n_("Select &random game"))
313        submenu.add_command(
314            label=n_("&All games"), command=lambda:
315            self.mSelectRandomGame('all'), accelerator=m+"R")
316        submenu.add_command(
317            label=n_("Games played and &won"),
318            command=lambda: self.mSelectRandomGame('won'))
319        submenu.add_command(
320            label=n_("Games played and &not won"),
321            command=lambda: self.mSelectRandomGame('not won'))
322        submenu.add_command(
323            label=n_("Games not &played"),
324            command=lambda: self.mSelectRandomGame('not played'))
325        menu.add_command(
326            label=n_("Select game by nu&mber..."),
327            command=self.mSelectGameById, accelerator=m+"M")
328        menu.add_separator()
329        submenu = MfxMenu(menu, label=n_("Fa&vorite games"))
330        menu.add_command(label=n_("A&dd to favorites"), command=self.mAddFavor)
331        menu.add_command(
332            label=n_("Remove &from favorites"),
333            command=self.mDelFavor)
334        menu.add_separator()
335        menu.add_command(
336            label=n_("&Open..."),
337            command=self.mOpen, accelerator=m+"O")
338        menu.add_command(
339            label=n_("&Save"),
340            command=self.mSave, accelerator=m+"S")
341        menu.add_command(label=n_("Save &as..."), command=self.mSaveAs)
342        menu.add_command(
343            label=n_("E&xport current layout..."),
344            command=self.mExportCurrentLayout)
345        menu.add_command(
346            label=n_("&Import starting layout..."),
347            command=self.mImportStartingLayout)
348        menu.add_separator()
349        menu.add_command(
350            label=n_("&Hold and quit"),
351            command=self.mHoldAndQuit, accelerator=m+"X")
352        if WIN_SYSTEM != "aqua":
353            menu.add_command(
354                label=n_("&Quit"),
355                command=self.mQuit, accelerator=m+"Q")
356
357        if self.progress:
358            self.progress.update(step=1)
359
360        menu = MfxMenu(self.menubar, label=n_("&Select"))
361        self._addSelectGameMenu(menu)
362
363        if self.progress:
364            self.progress.update(step=1)
365
366        menu = MfxMenu(self.menubar, label=n_("&Edit"))
367        menu.add_command(
368            label=n_("&Undo"),
369            command=self.mUndo, accelerator="Z")
370        menu.add_command(
371            label=n_("&Redo"),
372            command=self.mRedo, accelerator="R")
373        menu.add_command(label=n_("Redo &all"), command=self.mRedoAll)
374
375        menu.add_separator()
376        submenu = MfxMenu(menu, label=n_("&Set bookmark"))
377        for i in range(9):
378            label = _("Bookmark %d") % (i + 1)
379            submenu.add_command(
380                label=label,
381                command=lambda i=i: self.mSetBookmark(i))
382        submenu = MfxMenu(menu, label=n_("Go&to bookmark"))
383        for i in range(9):
384            label = _("Bookmark %d") % (i + 1)
385            acc = m + "%d" % (i + 1)
386            submenu.add_command(
387                label=label,
388                command=lambda i=i: self.mGotoBookmark(i), accelerator=acc)
389        menu.add_command(
390            label=n_("&Clear bookmarks"),
391            command=self.mClearBookmarks)
392        menu.add_separator()
393
394        menu.add_command(
395            label=n_("Restart"),
396            command=self.mRestart, accelerator=m+"G")
397
398        menu.add_separator()
399        menu.add_command(label=n_("Solitaire &Wizard"), command=self.mWizard)
400        menu.add_command(
401            label=n_("&Edit current game"),
402            command=self.mWizardEdit)
403        menu.add_command(
404            label=n_("&Delete current game"),
405            command=self.mWizardDelete)
406
407        menu = MfxMenu(self.menubar, label=n_("&Game"))
408        menu.add_command(
409            label=n_("&Deal cards"),
410            command=self.mDeal, accelerator="D")
411        menu.add_command(
412            label=n_("&Auto drop"),
413            command=self.mDrop, accelerator="A")
414        menu.add_command(
415            label=n_("Shu&ffle tiles"),
416            command=self.mShuffle, accelerator="F")
417        menu.add_checkbutton(
418            label=n_("&Pause"), variable=self.tkopt.pause,
419            command=self.mPause, accelerator="P")
420        # menu.add_command(
421        #    label=n_("&Pause"), command=self.mPause, accelerator="P")
422        menu.add_separator()
423        menu.add_command(
424            label=n_("S&tatus..."),
425            command=lambda: self.mPlayerStats(mode=100), accelerator=m+"Y")
426        menu.add_checkbutton(
427            label=n_("&Comments..."), variable=self.tkopt.comment,
428            command=self.mEditGameComment)
429        menu.add_separator()
430        menu.add_command(
431            label=n_("&Statistics..."),
432            command=self.mPlayerStats, accelerator=m+"T")
433        menu.add_command(
434            label=n_("Log..."),
435            command=lambda: self.mPlayerStats(mode=103))
436        menu.add_separator()
437        menu.add_command(
438            label=n_("D&emo statistics"),
439            command=lambda: self.mPlayerStats(mode=1101))
440
441        menu = MfxMenu(self.menubar, label=n_("&Assist"))
442        menu.add_command(
443            label=n_("&Hint"),
444            command=self.mHint, accelerator="H")
445        menu.add_command(
446            label=n_("Highlight p&iles"),
447            command=self.mHighlightPiles, accelerator="I")
448        menu.add_command(
449            label=n_("&Find card"),
450            command=self.mFindCard, accelerator="F3")
451        menu.add_separator()
452        menu.add_command(
453            label=n_("&Demo"),
454            command=self.mDemo, accelerator=m+"D")
455        menu.add_command(
456            label=n_("Demo (&all games)"),
457            command=self.mMixedDemo)
458        if USE_FREECELL_SOLVER:
459            menu.add_command(label=n_("&Solver"), command=self.mSolver)
460        else:
461            menu.add_command(label=n_("&Solver"), state='disabled')
462        menu.add_separator()
463        menu.add_command(
464            label=n_("&Piles description"),
465            command=self.mStackDesk, accelerator="F2")
466
467        if self.progress:
468            self.progress.update(step=1)
469
470        menu = MfxMenu(self.menubar, label=n_("&Options"))
471        menu.add_command(
472            label=n_("&Player options..."),
473            command=self.mOptPlayerOptions)
474        submenu = MfxMenu(menu, label=n_("&Automatic play"))
475        submenu.add_checkbutton(
476            label=n_("Auto &face up"), variable=self.tkopt.autofaceup,
477            command=self.mOptAutoFaceUp)
478        submenu.add_checkbutton(
479            label=n_("A&uto drop"), variable=self.tkopt.autodrop,
480            command=self.mOptAutoDrop)
481        submenu.add_checkbutton(
482            label=n_("Auto &deal"), variable=self.tkopt.autodeal,
483            command=self.mOptAutoDeal)
484        submenu.add_separator()
485        submenu.add_checkbutton(
486            label=n_("&Quick play"), variable=self.tkopt.quickplay,
487            command=self.mOptQuickPlay)
488        submenu = MfxMenu(menu, label=n_("Assist &level"))
489        submenu.add_checkbutton(
490            label=n_("Enable &undo"), variable=self.tkopt.undo,
491            command=self.mOptEnableUndo)
492        submenu.add_checkbutton(
493            label=n_("Enable &bookmarks"), variable=self.tkopt.bookmarks,
494            command=self.mOptEnableBookmarks)
495        submenu.add_checkbutton(
496            label=n_("Enable &hint"), variable=self.tkopt.hint,
497            command=self.mOptEnableHint)
498        submenu.add_checkbutton(
499            label=n_("Enable shu&ffle"), variable=self.tkopt.shuffle,
500            command=self.mOptEnableShuffle)
501        submenu.add_checkbutton(
502            label=n_("Enable highlight p&iles"),
503            variable=self.tkopt.highlight_piles,
504            command=self.mOptEnableHighlightPiles)
505        submenu.add_checkbutton(
506            label=n_("Enable highlight &cards"),
507            variable=self.tkopt.highlight_cards,
508            command=self.mOptEnableHighlightCards)
509        submenu.add_checkbutton(
510            label=n_("Enable highlight same &rank"),
511            variable=self.tkopt.highlight_samerank,
512            command=self.mOptEnableHighlightSameRank)
513        submenu.add_checkbutton(
514            label=n_("Highlight &no matching"),
515            variable=self.tkopt.highlight_not_matching,
516            command=self.mOptEnableHighlightNotMatching)
517        submenu.add_separator()
518        submenu.add_checkbutton(
519            label=n_("&Show removed tiles (in Mahjongg games)"),
520            variable=self.tkopt.mahjongg_show_removed,
521            command=self.mOptMahjonggShowRemoved)
522        submenu.add_checkbutton(
523            label=n_("Show hint &arrow (in Shisen-Sho games)"),
524            variable=self.tkopt.shisen_show_hint,
525            command=self.mOptShisenShowHint)
526        submenu.add_checkbutton(
527            label=n_("&Deal all cards (in Accordion type games)"),
528            variable=self.tkopt.accordion_deal_all,
529            command=self.mOptAccordionDealAll)
530        menu.add_separator()
531        label = n_("&Sound...")
532        menu.add_command(
533            label=label, command=self.mOptSoundDialog)
534        # cardsets
535        if USE_PIL:
536            submenu = MfxMenu(menu, label=n_("Card si&ze"))
537            submenu.add_command(
538                label=n_("&Increase the card size"),
539                command=self.mIncreaseCardset, accelerator=m+"+")
540            submenu.add_command(
541                label=n_("&Decrease the card size"),
542                command=self.mDecreaseCardset, accelerator=m+"-")
543            submenu.add_command(
544                label=n_("&Reset the card size"),
545                command=self.mResetCardset)
546            submenu.add_separator()
547            submenu.add_checkbutton(
548                label=n_("&Auto scaling"), variable=self.tkopt.auto_scale,
549                command=self.mOptAutoScale, accelerator=m+'0')
550            submenu.add_checkbutton(
551                label=n_("&Preserve aspect ratio"),
552                variable=self.tkopt.preserve_aspect_ratio,
553                command=self.mOptPreserveAspectRatio)
554            submenu = MfxMenu(menu, label=n_("Card la&yout"))
555            submenu.add_checkbutton(
556                label=n_("&Spread stacks"), variable=self.tkopt.spread_stacks,
557                command=self.mOptSpreadStacks)
558            submenu.add_checkbutton(
559                label=n_("&Center layout"), variable=self.tkopt.center_layout,
560                command=self.mOptCenterLayout)
561        # manager = self.app.cardset_manager
562        # n = manager.len()
563        menu.add_command(
564            label=n_("Cards&et..."),
565            command=self.mSelectCardsetDialog, accelerator=m+"E")
566        menu.add_command(
567            label=n_("Table t&ile..."),
568            command=self.mSelectTileDialog)
569        # this submenu will get set by updateBackgroundImagesMenu()
570        submenu = MfxMenu(menu, label=n_("Card &background"))
571        submenu = MfxMenu(menu, label=n_("Card &view"))
572        submenu.add_checkbutton(
573            label=n_("Card shado&w"), variable=self.tkopt.shadow,
574            command=self.mOptShadow)
575        submenu.add_checkbutton(
576            label=n_("Shade &legal moves"), variable=self.tkopt.shade,
577            command=self.mOptShade)
578        submenu.add_checkbutton(
579            label=n_("&Negative cards bottom"),
580            variable=self.tkopt.negative_bottom,
581            command=self.mOptNegativeBottom)
582        submenu.add_checkbutton(
583            label=n_("Shrink face-down cards"),
584            variable=self.tkopt.shrink_face_down,
585            command=self.mOptShrinkFaceDown)
586        submenu.add_checkbutton(
587            label=n_("Shade &filled stacks"),
588            variable=self.tkopt.shade_filled_stacks,
589            command=self.mOptShadeFilledStacks)
590        submenu = MfxMenu(menu, label=n_("A&nimations"))
591        submenu.add_radiobutton(
592            label=n_("&None"), variable=self.tkopt.animations, value=0,
593            command=self.mOptAnimations)
594        submenu.add_radiobutton(
595            label=n_("&Very fast"), variable=self.tkopt.animations, value=1,
596            command=self.mOptAnimations)
597        submenu.add_radiobutton(
598            label=n_("&Fast"), variable=self.tkopt.animations, value=2,
599            command=self.mOptAnimations)
600        submenu.add_radiobutton(
601            label=n_("&Medium"), variable=self.tkopt.animations, value=3,
602            command=self.mOptAnimations)
603        submenu.add_radiobutton(
604            label=n_("&Slow"), variable=self.tkopt.animations, value=4,
605            command=self.mOptAnimations)
606        submenu.add_radiobutton(
607            label=n_("V&ery slow"), variable=self.tkopt.animations, value=5,
608            command=self.mOptAnimations)
609        submenu.add_separator()
610        submenu.add_checkbutton(
611            label=n_("&Redeal animation"),
612            variable=self.tkopt.redeal_animation,
613            command=self.mRedealAnimation)
614        if Image:
615            submenu.add_checkbutton(
616                label=n_("&Winning animation"),
617                variable=self.tkopt.win_animation,
618                command=self.mWinAnimation)
619        submenu = MfxMenu(menu, label=n_("&Mouse"))
620        submenu.add_radiobutton(
621            label=n_("&Drag-and-Drop"), variable=self.tkopt.mouse_type,
622            value='drag-n-drop',
623            command=self.mOptMouseType)
624        submenu.add_radiobutton(
625            label=n_("&Point-and-Click"), variable=self.tkopt.mouse_type,
626            value='point-n-click',
627            command=self.mOptMouseType)
628        submenu.add_radiobutton(
629            label=n_("&Sticky mouse"), variable=self.tkopt.mouse_type,
630            value='sticky-mouse',
631            command=self.mOptMouseType)
632        submenu.add_separator()
633        submenu.add_checkbutton(
634            label=n_("Use mouse for undo/redo"),
635            variable=self.tkopt.mouse_undo,
636            command=self.mOptMouseUndo)
637        menu.add_separator()
638        menu.add_command(label=n_("&Fonts..."), command=self.mOptFonts)
639        menu.add_command(label=n_("&Colors..."), command=self.mOptColors)
640        menu.add_command(label=n_("Time&outs..."), command=self.mOptTimeouts)
641        menu.add_separator()
642        self.createThemesMenu(menu)
643        submenu = MfxMenu(menu, label=n_("&Toolbar"))
644        createToolbarMenu(self, submenu)
645        submenu = MfxMenu(menu, label=n_("Stat&usbar"))
646        submenu.add_checkbutton(
647            label=n_("Show &statusbar"), variable=self.tkopt.statusbar,
648            command=self.mOptStatusbar)
649        submenu.add_checkbutton(
650            label=n_("Show &number of cards"), variable=self.tkopt.num_cards,
651            command=self.mOptNumCards)
652        submenu.add_checkbutton(
653            label=n_("Show &help bar"), variable=self.tkopt.helpbar,
654            command=self.mOptHelpbar)
655        # if not USE_PIL:
656        menu.add_checkbutton(
657            label=n_("Save games &geometry"),
658            variable=self.tkopt.save_games_geometry,
659            command=self.mOptSaveGamesGeometry)
660        menu.add_checkbutton(
661            label=n_("&Demo logo"), variable=self.tkopt.demo_logo,
662            command=self.mOptDemoLogo)
663        menu.add_checkbutton(
664            label=n_("Startup splash sc&reen"),
665            variable=self.tkopt.splashscreen,
666            command=self.mOptSplashscreen)
667        #  menu.add_separator()
668        #  menu.add_command(label="Save options", command=self.mOptSave)
669
670        if self.progress:
671            self.progress.update(step=1)
672
673        # macOS: tk creates the menu item "Help->PySolFC Help", therefore
674        # we will not create a duplicate "Help->Contents" item.
675        # The tk-provided menu item expects this callback.
676        self.top.createcommand('tk::mac::ShowHelp', self.mHelp)
677
678        menu = MfxMenu(self.menubar, label=n_("&Help"))
679        if WIN_SYSTEM != "aqua":
680            menu.add_command(
681                label=n_("&Contents"),
682                command=self.mHelp, accelerator=m+"F1")
683        menu.add_command(
684            label=n_("&How to use PySol"),
685            command=self.mHelpHowToPlay)
686        menu.add_command(
687            label=n_("&Rules for this game"),
688            command=self.mHelpRules, accelerator="F1")
689        menu.add_command(
690            label=n_("&License terms"),
691            command=self.mHelpLicense)
692        # menu.add_command(label=n_("What's &new ?"), command=self.mHelpNews)
693        if WIN_SYSTEM != "aqua":
694            menu.add_separator()
695            menu.add_command(
696                label=_("&About %s...") % TITLE,
697                command=self.mHelpAbout)
698
699        MfxMenubar.addPath = None
700
701        # FIXME: all key bindings should be *added* to keyPressHandler
702        ctrl = "Control-"
703        if sys.platform == "darwin":
704            ctrl = "Command-"
705        self._bindKey("",   "n", self.mNewGame)
706        self._bindKey(ctrl, "w", self.mSelectGameDialog)
707        self._bindKey(ctrl, "v", self.mSelectGameDialogWithPreview)
708        self._bindKey(ctrl, "r", lambda e: self.mSelectRandomGame())
709        self._bindKey(ctrl, "m", self.mSelectGameById)
710        self._bindKey(ctrl, "n", self.mNewGameWithNextId)
711        self._bindKey(ctrl, "o", self.mOpen)
712        self._bindKey(ctrl, "s", self.mSave)
713        self._bindKey(ctrl, "x", self.mHoldAndQuit)
714        self._bindKey(ctrl, "q", self.mQuit)
715        self._bindKey(ctrl, "z", self.mUndo)
716        self._bindKey("",   "z", self.mUndo)
717        self._bindKey("",   "BackSpace", self.mUndo)    # undocumented
718        self._bindKey("",   "KP_Enter", self.mUndo)     # undocumented
719        self._bindKey("",   "r", self.mRedo)
720        self._bindKey(ctrl, "g", self.mRestart)
721        self._bindKey("",   "space", self.mDeal)        # undocumented
722        self._bindKey(ctrl, "y", lambda e: self.mPlayerStats(mode=100))
723        self._bindKey(ctrl, "t", lambda e: self.mPlayerStats(mode=105))
724        self._bindKey("",   "h", self.mHint)
725        self._bindKey(ctrl, "h", self.mHint1)           # undocumented
726        # self._bindKey("",   "Shift_L", self.mHighlightPiles)
727        # self._bindKey("",   "Shift_R", self.mHighlightPiles)
728        self._bindKey("",   "i", self.mHighlightPiles)
729        self._bindKey("",   "F3", self.mFindCard)
730        self._bindKey(ctrl, "d", self.mDemo)
731        self._bindKey(ctrl, "e", self.mSelectCardsetDialog)
732        if USE_PIL:
733            self._bindKey(ctrl, "plus", self.mIncreaseCardset)
734            self._bindKey(ctrl, "equal", self.mIncreaseCardset)
735            self._bindKey(ctrl, "minus", self.mDecreaseCardset)
736            self._bindKey(ctrl, "0", self.mOptAutoScale)
737        self._bindKey(ctrl, "b", self.mOptChangeCardback)  # undocumented
738        self._bindKey(ctrl, "i", self.mOptChangeTableTile)  # undocumented
739        self._bindKey(ctrl, "p", self.mOptPlayerOptions)   # undocumented
740        self._bindKey(ctrl, "F1", self.mHelp)
741        self._bindKey("",   "F1", self.mHelpRules)
742        self._bindKey("",   "Print", self.mScreenshot)
743        self._bindKey(ctrl, "u", self.mPlayNextMusic)   # undocumented
744        self._bindKey("",   "p", self.mPause)
745        self._bindKey("",   "Pause", self.mPause)       # undocumented
746        self._bindKey("",   "Escape", self.mIconify)    # undocumented
747        # ASD and LKJ
748        self._bindKey("",   "a", self.mDrop)
749        self._bindKey(ctrl, "a", self.mDrop1)
750        self._bindKey("",   "s", self.mUndo)
751        self._bindKey("",   "d", self.mDeal)
752        self._bindKey("",   "l", self.mDrop)
753        self._bindKey(ctrl, "l", self.mDrop1)
754        self._bindKey("",   "k", self.mUndo)
755        self._bindKey("",   "j", self.mDeal)
756
757        self._bindKey("",   "F2", self.mStackDesk)
758        #
759        # undocumented, devel
760        self._bindKey("", "slash", lambda e: self.mPlayerStats(mode=106))
761        #
762        self._bindKey("",   "f", self.mShuffle)
763
764        for i in range(9):
765            self._bindKey(
766                ctrl, str(i+1),
767                lambda e, i=i: self.mGotoBookmark(i, confirm=0))
768
769        # undocumented, devel
770        self._bindKey(ctrl, "End", self.mPlayNextMusic)
771        self._bindKey(ctrl, "Prior", self.mSelectPrevGameByName)
772        self._bindKey(ctrl, "Next", self.mSelectNextGameByName)
773        self._bindKey(ctrl, "Up", self.mSelectPrevGameById)
774        self._bindKey(ctrl, "Down", self.mSelectNextGameById)
775
776    #
777    # key binding utility
778    #
779
780    def _bindKey(self, modifier, key, func):
781        sequence = "<" + modifier + "KeyPress-" + key + ">"
782        bind(self.top, sequence, func)
783        if len(key) == 1 and key != key.upper():
784            key = key.upper()
785            sequence = "<" + modifier + "KeyPress-" + key + ">"
786            bind(self.top, sequence, func)
787
788    def _keyPressHandler(self, event):
789        r = EVENT_PROPAGATE
790        if event and self.game:
791            # print event.__dict__
792            if self.game.demo:
793                # stop the demo by setting self.game.demo.keypress
794                if event.char:    # ignore Ctrl/Shift/etc.
795                    self.game.demo.keypress = event.char
796                    r = EVENT_HANDLED
797                # func = self.keybindings.get(event.char)
798                # if func and (event.state & ~2) == 0:
799                #    func(event)
800                #    r = EVENT_HANDLED
801        return r
802
803    #
804    # Select Game menu creation
805    #
806
807    def _addSelectGameMenu(self, menu):
808        # games = map(self.app.gdb.get,
809        #   self.app.gdb.getGamesIdSortedByShortName())
810        games = list(map(
811            self.app.gdb.get, self.app.gdb.getGamesIdSortedByName()))
812        # games = tuple(games)
813        # menu = MfxMenu(menu, label="Select &game")
814        m = "Ctrl-"
815        if sys.platform == "darwin":
816            m = "Cmd-"
817        menu.add_command(label=n_("All &games..."), accelerator=m+"V",
818                         command=self.mSelectGameDialogWithPreview)
819        if not SELECT_GAME_MENU:
820            return
821        menu.add_separator()
822        self._addSelectPopularGameSubMenu(games, menu, self.mSelectGame,
823                                          self.tkopt.gameid)
824        self._addSelectFrenchGameSubMenu(games, menu, self.mSelectGame,
825                                         self.tkopt.gameid)
826        if self.progress:
827            self.progress.update(step=1)
828        self._addSelectMahjonggGameSubMenu(games, menu, self.mSelectGame,
829                                           self.tkopt.gameid)
830        self._addSelectOrientalGameSubMenu(games, menu, self.mSelectGame,
831                                           self.tkopt.gameid)
832        self._addSelectSpecialGameSubMenu(games, menu, self.mSelectGame,
833                                          self.tkopt.gameid)
834        self._addSelectCustomGameSubMenu(games, menu, self.mSelectGame,
835                                         self.tkopt.gameid)
836        menu.add_separator()
837        if self.progress:
838            self.progress.update(step=1)
839        self._addSelectAllGameSubMenu(games, menu, self.mSelectGame,
840                                      self.tkopt.gameid)
841
842    def _addSelectGameSubMenu(self, games, menu, select_data,
843                              command, variable):
844        # print select_data
845        need_sep = 0
846        for label, select_func in select_data:
847            if label is None:
848                need_sep = 1
849                continue
850            g = list(filter(select_func, games))
851            if not g:
852                continue
853            if need_sep:
854                menu.add_separator()
855                need_sep = 0
856            submenu = MfxMenu(menu, label=label)
857            self._addSelectGameSubSubMenu(g, submenu, command, variable)
858
859    def _getNumGames(self, games, select_data):
860        ngames = 0
861        for label, select_func in select_data:
862            ngames += len(list(filter(select_func, games)))
863        return ngames
864
865    def _addSelectMahjonggGameSubMenu(self, games, menu, command, variable):
866        def select_func(gi):
867            return gi.si.game_type == GI.GT_MAHJONGG
868        mahjongg_games = list(filter(select_func, games))
869        if len(mahjongg_games) == 0:
870            return
871        #
872        menu = MfxMenu(menu, label=n_("&Mahjongg games"))
873
874        def add_menu(games, c0, c1, menu=menu,
875                     variable=variable, command=command):
876            if not games:
877                return
878            label = c0 + ' - ' + c1
879            if c0 == c1:
880                label = c0
881            submenu = MfxMenu(menu, label=label, name=None)
882            self._addSelectGameSubSubMenu(games, submenu, command,
883                                          variable, short_name=True)
884
885        games = {}
886        for gi in mahjongg_games:
887            c = gi.short_name.strip()[0]
888            if c in games:
889                games[c].append(gi)
890            else:
891                games[c] = [gi]
892        games = list(games.items())
893        games.sort()
894        g0 = []
895        c0 = c1 = games[0][0]
896        for c, g1 in games:
897            if len(g0)+len(g1) >= self.cb_max:
898                add_menu(g0, c0, c1)
899                g0 = g1
900                c0 = c1 = c
901            else:
902                g0 += g1
903                c1 = c
904        add_menu(g0, c0, c1)
905
906    def _addSelectPopularGameSubMenu(self, games, menu, command, variable):
907        def select_func(gi):
908            return gi.si.game_flags & GI.GT_POPULAR
909        if len(list(filter(select_func, games))) == 0:
910            return
911        data = (n_("&Popular games"), select_func)
912        self._addSelectGameSubMenu(games, menu, (data, ),
913                                   self.mSelectGamePopular,
914                                   self.tkopt.gameid_popular)
915
916    def _addSelectFrenchGameSubMenu(self, games, menu, command, variable):
917        if self._getNumGames(games, GI.SELECT_GAME_BY_TYPE) == 0:
918            return
919        submenu = MfxMenu(menu, label=n_("&French games"))
920        self._addSelectGameSubMenu(games, submenu, GI.SELECT_GAME_BY_TYPE,
921                                   self.mSelectGame, self.tkopt.gameid)
922
923    def _addSelectOrientalGameSubMenu(self, games, menu, command, variable):
924        if self._getNumGames(games, GI.SELECT_ORIENTAL_GAME_BY_TYPE) == 0:
925            return
926        submenu = MfxMenu(menu, label=n_("&Oriental games"))
927        self._addSelectGameSubMenu(games, submenu,
928                                   GI.SELECT_ORIENTAL_GAME_BY_TYPE,
929                                   self.mSelectGame, self.tkopt.gameid)
930
931    def _addSelectSpecialGameSubMenu(self, games, menu, command, variable):
932        if self._getNumGames(games, GI.SELECT_ORIENTAL_GAME_BY_TYPE) == 0:
933            return
934        submenu = MfxMenu(menu, label=n_("&Special games"))
935        self._addSelectGameSubMenu(games, submenu,
936                                   GI.SELECT_SPECIAL_GAME_BY_TYPE,
937                                   self.mSelectGame, self.tkopt.gameid)
938
939    def _addSelectCustomGameSubMenu(self, games, menu, command, variable):
940        submenu = MfxMenu(menu, label=n_("&Custom games"))
941
942        def select_func(gi):
943            return gi.si.game_type == GI.GT_CUSTOM
944        games = list(filter(select_func, games))
945        self.updateGamesMenu(submenu, games)
946
947    def _addSelectAllGameSubMenu(self, games, menu, command, variable):
948        if menu.name != "allgamesbyname":
949            menu = MfxMenu(menu, label=n_("&All games by name"))
950        n, d = 0, self.cb_max
951        i = 0
952        while True:
953            if self.progress:
954                self.progress.update(step=1)
955            columnbreak = i > 0 and (i % d) == 0
956            i += 1
957            if not games[n:n+d]:
958                break
959            m = min(n+d-1, len(games)-1)
960            label = games[n].name[:3] + ' - ' + games[m].name[:3]
961            submenu = MfxMenu(menu, label=label, name=None)
962            self._addSelectGameSubSubMenu(games[n:n+d], submenu,
963                                          command, variable)
964            n += d
965            if columnbreak:
966                menu.entryconfigure(i, columnbreak=columnbreak)
967
968    def _addSelectGameSubSubMenu(self, games, menu, command, variable,
969                                 short_name=False):
970        # cb = (25, self.cb_max) [ len(g) > 4 * 25 ]
971        # cb = min(cb, self.cb_max)
972        cb = self.cb_max
973        for i in range(len(games)):
974            gi = games[i]
975            columnbreak = i > 0 and (i % cb) == 0
976            if short_name:
977                label = gi.short_name
978            else:
979                label = gi.name
980            # optimized by inlining
981            menu.tk.call((menu._w, 'add', 'radiobutton') +
982                         menu._options({'command': command,
983                                        'variable': variable,
984                                        'columnbreak': columnbreak,
985                                        'value': gi.id,
986                                        'label': label}))
987
988    def updateGamesMenu(self, menu, games):
989        menu.delete(0, 'last')
990        if len(games) == 0:
991            menu.add_radiobutton(label=_('<none>'), name=None,
992                                 state='disabled')
993        elif len(games) > self.cb_max*4:
994            games.sort(key=lambda x: x.name)
995            self._addSelectAllGameSubMenu(games, menu,
996                                          command=self.mSelectGame,
997                                          variable=self.tkopt.gameid)
998        else:
999            self._addSelectGameSubSubMenu(games, menu,
1000                                          command=self.mSelectGame,
1001                                          variable=self.tkopt.gameid)
1002
1003    #
1004    # Select Game menu actions
1005    #
1006
1007    def mSelectGame(self, *args):
1008        self._mSelectGame(self.tkopt.gameid.get())
1009
1010    def mSelectGamePopular(self, *args):
1011        self._mSelectGame(self.tkopt.gameid_popular.get())
1012
1013    def _mSelectGameDialog(self, d):
1014        if d.status == 0 and d.button == 0 and d.gameid != self.game.id:
1015            self.tkopt.gameid.set(d.gameid)
1016            self.tkopt.gameid_popular.set(d.gameid)
1017            if 0:
1018                self._mSelectGame(d.gameid, random=d.random)
1019            else:
1020                # don't ask areYouSure()
1021                self._cancelDrag()
1022                self.game.endGame()
1023                self.game.quitGame(d.gameid, random=d.random)
1024        return EVENT_HANDLED
1025
1026    def __restoreCursor(self, *event):
1027        self.game.setCursor(cursor=self.app.top_cursor)
1028
1029    def mSelectGameDialog(self, *event):
1030        if self._cancelDrag(break_pause=False):
1031            return
1032        self.game.setCursor(cursor=CURSOR_WATCH)
1033        after_idle(self.top, self.__restoreCursor)
1034        d = self._calcSelectGameDialog()(
1035            self.top, title=_("Select game"),
1036            app=self.app, gameid=self.game.id)
1037        return self._mSelectGameDialog(d)
1038
1039    def mSelectGameDialogWithPreview(self, *event):
1040        if self._cancelDrag(break_pause=False):
1041            return
1042        self.game.setCursor(cursor=CURSOR_WATCH)
1043        bookmark = None
1044        if 0:
1045            # use a bookmark for our preview game
1046            if self.game.setBookmark(-2, confirm=0):
1047                bookmark = self.game.gsaveinfo.bookmarks[-2][0]
1048                del self.game.gsaveinfo.bookmarks[-2]
1049        after_idle(self.top, self.__restoreCursor)
1050        d = self._calcSelectGameDialogWithPreview()(
1051            self.top, title=_("Select game"),
1052            app=self.app, gameid=self.game.id,
1053            bookmark=bookmark)
1054        return self._mSelectGameDialog(d)
1055
1056    #
1057    # menubar overrides
1058    #
1059
1060    def updateFavoriteGamesMenu(self):
1061        gameids = self.app.opt.favorite_gameid
1062        submenu = self.menupath[".menubar.file.favoritegames"][2]
1063        games = []
1064        for id in gameids:
1065            gi = self.app.getGameInfo(id)
1066            if gi:
1067                games.append(gi)
1068        self.updateGamesMenu(submenu, games)
1069        state = self._getEnabledState
1070        in_favor = self.app.game.id in gameids
1071        menu, index, submenu = self.menupath[".menubar.file.addtofavorites"]
1072        menu.entryconfig(index, state=state(not in_favor))
1073        menu, index, submenu = \
1074            self.menupath[".menubar.file.removefromfavorites"]
1075        menu.entryconfig(index, state=state(in_favor))
1076
1077    def updateRecentGamesMenu(self, gameids):
1078        submenu = self.menupath[".menubar.file.recentgames"][2]
1079        games = []
1080        for id in gameids:
1081            gi = self.app.getGameInfo(id)
1082            if gi:
1083                games.append(gi)
1084        self.updateGamesMenu(submenu, games)
1085
1086    def updateCustomGamesMenu(self):
1087        menu = self.menupath[".menubar.select.customgames"][2]
1088        menu2 = self.menupath[".menubar.select.allgamesbyname"][2]
1089
1090        def select_func_visible(gi):
1091            return gi.si.game_type != GI.GT_HIDDEN
1092
1093        def select_func_custom(gi):
1094            return gi.si.game_type == GI.GT_CUSTOM
1095
1096        games = list(map(self.app.gdb.get,
1097                         self.app.gdb.getGamesIdSortedByName()))
1098        games = list(filter(select_func_visible, games))
1099        self.progress = False
1100        self.updateGamesMenu(menu2, games)
1101        games = list(filter(select_func_custom, games))
1102        self.updateGamesMenu(menu, games)
1103
1104    def updateBookmarkMenuState(self):
1105        state = self._getEnabledState
1106        mp1 = self.menupath.get(".menubar.edit.setbookmark")
1107        mp2 = self.menupath.get(".menubar.edit.gotobookmark")
1108        mp3 = self.menupath.get(".menubar.edit.clearbookmarks")
1109        if mp1 is None or mp2 is None or mp3 is None:
1110            return
1111        x = self.app.opt.bookmarks and self.game.canSetBookmark()
1112        #
1113        menu, index, submenu = mp1
1114        for i in range(9):
1115            submenu.entryconfig(i, state=state(x))
1116        menu.entryconfig(index, state=state(x))
1117        #
1118        menu, index, submenu = mp2
1119        ms = 0
1120        for i in range(9):
1121            s = self.game.gsaveinfo.bookmarks.get(i) is not None
1122            submenu.entryconfig(i, state=state(s and x))
1123            ms = ms or s
1124        menu.entryconfig(index, state=state(ms and x))
1125        #
1126        menu, index, submenu = mp3
1127        menu.entryconfig(index, state=state(ms and x))
1128
1129    def updateBackgroundImagesMenu(self):
1130        mp = self.menupath.get(".menubar.options.cardbackground")
1131        # delete all entries
1132        submenu = mp[2]
1133        submenu.delete(0, "last")
1134        # insert new cardbacks
1135        mbacks = self.app.images.getCardbacks()
1136        cb = int(math.ceil(math.sqrt(len(mbacks))))
1137        for i in range(len(mbacks)):
1138            columnbreak = i > 0 and (i % cb) == 0
1139            submenu.add_radiobutton(
1140                label=mbacks[i].name, image=mbacks[i].menu_image,
1141                variable=self.tkopt.cardback, value=i,
1142                command=self.mOptCardback, columnbreak=columnbreak,
1143                indicatoron=0, hidemargin=0)
1144
1145    #
1146    # menu updates
1147    #
1148
1149    def setMenuState(self, state, path):
1150        # print state, path
1151        path = ".menubar." + path
1152        mp = self.menupath.get(path)
1153        menu, index, submenu = mp
1154        s = self._getEnabledState(state)
1155        menu.entryconfig(index, state=s)
1156
1157    def setToolbarState(self, state, path):
1158        # print state, path
1159        s = self._getEnabledState(state)
1160        w = getattr(self.app.toolbar, path + "_button")
1161        w["state"] = s
1162
1163    def _setCommentMenu(self, v):
1164        self.tkopt.comment.set(v)
1165
1166    def _setPauseMenu(self, v):
1167        self.tkopt.pause.set(v)
1168
1169    #
1170    # menu actions
1171    #
1172
1173    DEFAULTEXTENSION = ".pso"
1174    FILETYPES = ((_("%s files") % TITLE, "*" + DEFAULTEXTENSION),
1175                 (_("All files"), "*"))
1176
1177    def mAddFavor(self, *event):
1178        gameid = self.app.game.id
1179        if gameid not in self.app.opt.favorite_gameid:
1180            self.app.opt.favorite_gameid.append(gameid)
1181            self.updateFavoriteGamesMenu()
1182
1183    def mDelFavor(self, *event):
1184        gameid = self.app.game.id
1185        if gameid in self.app.opt.favorite_gameid:
1186            self.app.opt.favorite_gameid.remove(gameid)
1187            self.updateFavoriteGamesMenu()
1188
1189    def mOpen(self, *event):
1190        if self._cancelDrag(break_pause=False):
1191            return
1192        filename = self.game.filename
1193        if filename:
1194            idir, ifile = os.path.split(os.path.normpath(filename))
1195        else:
1196            idir, ifile = "", ""
1197        if not idir:
1198            idir = self.app.dn.savegames
1199        d = tkinter_tkfiledialog.Open()
1200        filename = d.show(filetypes=self.FILETYPES,
1201                          defaultextension=self.DEFAULTEXTENSION,
1202                          initialdir=idir, initialfile=ifile)
1203        if filename:
1204            filename = os.path.normpath(filename)
1205            # filename = os.path.normcase(filename)
1206            if os.path.isfile(filename):
1207                self.game.loadGame(filename)
1208
1209    def mExportCurrentLayout(self, *event):
1210        if self._cancelDrag(break_pause=False):
1211            return
1212        game = self.game
1213        if not self.menustate.save_as:
1214            return
1215        if not game.Solver_Class:
1216            d = self._calc_MfxMessageDialog()(
1217                self.top, title=_('Export game error'),
1218                text=_('''
1219Unsupported game for export.
1220'''),
1221                bitmap='error')
1222            return
1223
1224        filename = game.filename
1225        if not filename:
1226            filename = self.app.getGameSaveName(self.game.id)
1227            if os.name == "posix" or os.path.supports_unicode_filenames:
1228                filename += "-" + self.game.getGameNumber(format=0)
1229            else:
1230                filename += "-01"
1231            filename += ".board"
1232        idir, ifile = os.path.split(os.path.normpath(filename))
1233        if not idir:
1234            idir = self.app.dn.savegames
1235        # print self.game.filename, ifile
1236        d = tkinter_tkfiledialog.SaveAs()
1237        filename = d.show(filetypes=self.FILETYPES,
1238                          defaultextension=self.DEFAULTEXTENSION,
1239                          initialdir=idir, initialfile=ifile)
1240        if filename:
1241            filename = os.path.normpath(filename)
1242            # filename = os.path.normcase(filename)
1243            with open(filename, 'w') as fh:
1244                game = self.game
1245                fh.write(game.Solver_Class(game, self).calcBoardString())
1246            self.updateMenus()
1247
1248    def mImportStartingLayout(self, *event):
1249        if self._cancelDrag(break_pause=False):
1250            return
1251        game = self.game
1252        if not game.Solver_Class:
1253            d = self._calc_MfxMessageDialog()(
1254                self.top, title=_('Import game error'),
1255                text=_('''
1256Unsupported game for import.
1257'''),
1258                bitmap='error')
1259            return
1260
1261        filename = self.game.filename
1262        if filename:
1263            idir, ifile = os.path.split(os.path.normpath(filename))
1264        else:
1265            idir, ifile = "", ""
1266        if not idir:
1267            idir = self.app.dn.savegames
1268        d = tkinter_tkfiledialog.Open()
1269        key = 'PYSOL_DEBUG_IMPORT'
1270        if key not in os.environ:
1271            filename = d.show(filetypes=self.FILETYPES,
1272                              defaultextension=self.DEFAULTEXTENSION,
1273                              initialdir=idir, initialfile=ifile)
1274        else:
1275            filename = os.environ[key]
1276        if filename:
1277            filename = os.path.normpath(filename)
1278            # filename = os.path.normcase(filename)
1279            if os.path.isfile(filename):
1280                with open(filename, 'r+b') as fh:
1281                    game = self.game
1282                    try:
1283                        game.Solver_Class(game, self).importFile(
1284                            fh, game, self)
1285                    except PySolHintLayoutImportError as err:
1286                        self._calc_MfxMessageDialog()(
1287                            self.top,
1288                            title=_('Import game error'),
1289                            text=err.format(),
1290                            bitmap='error'
1291                        )
1292                        game.busy = False
1293                        game.endGame()
1294                        game.newGame()
1295
1296    def mSaveAs(self, *event):
1297        if self._cancelDrag(break_pause=False):
1298            return
1299        if not self.menustate.save_as:
1300            return
1301        filename = self.game.filename
1302        if not filename:
1303            filename = self.app.getGameSaveName(self.game.id)
1304            if os.name == "posix":
1305                filename = filename + "-" + self.game.getGameNumber(format=0)
1306            elif os.path.supports_unicode_filenames:  # new in python 2.3
1307                filename = filename + "-" + self.game.getGameNumber(format=0)
1308            else:
1309                filename = filename + "-01"
1310            filename = filename + self.DEFAULTEXTENSION
1311        idir, ifile = os.path.split(os.path.normpath(filename))
1312        if not idir:
1313            idir = self.app.dn.savegames
1314        # print self.game.filename, ifile
1315        d = tkinter_tkfiledialog.SaveAs()
1316        filename = d.show(filetypes=self.FILETYPES,
1317                          defaultextension=self.DEFAULTEXTENSION,
1318                          initialdir=idir, initialfile=ifile)
1319        if filename:
1320            filename = os.path.normpath(filename)
1321            # filename = os.path.normcase(filename)
1322            self.game.saveGame(filename)
1323            self.updateMenus()
1324
1325    def mPause(self, *args):
1326        if not self.game:
1327            return
1328        if not self.game.pause:
1329            if self._cancelDrag():
1330                return
1331        self.game.doPause()
1332        self.tkopt.pause.set(self.game.pause)
1333
1334    def mOptSoundDialog(self, *args):
1335        if self._cancelDrag(break_pause=False):
1336            return
1337        self._calcSoundOptionsDialog()(
1338            self.top, _("Sound settings"), self.app)
1339        self.tkopt.sound.set(self.app.opt.sound)
1340
1341    def mOptAutoFaceUp(self, *args):
1342        if self._cancelDrag():
1343            return
1344        self.app.opt.autofaceup = self.tkopt.autofaceup.get()
1345        if self.app.opt.autofaceup:
1346            self.game.autoPlay()
1347
1348    def mOptAutoDrop(self, *args):
1349        if self._cancelDrag():
1350            return
1351        self.app.opt.autodrop = self.tkopt.autodrop.get()
1352        if self.app.opt.autodrop:
1353            self.game.autoPlay()
1354
1355    def mOptAutoDeal(self, *args):
1356        if self._cancelDrag():
1357            return
1358        self.app.opt.autodeal = self.tkopt.autodeal.get()
1359        if self.app.opt.autodeal:
1360            self.game.autoPlay()
1361
1362    def mOptQuickPlay(self, *args):
1363        if self._cancelDrag(break_pause=False):
1364            return
1365        self.app.opt.quickplay = self.tkopt.quickplay.get()
1366
1367    def mOptEnableUndo(self, *args):
1368        if self._cancelDrag(break_pause=False):
1369            return
1370        self.app.opt.undo = self.tkopt.undo.get()
1371        self.game.updateMenus()
1372
1373    def mOptEnableBookmarks(self, *args):
1374        if self._cancelDrag(break_pause=False):
1375            return
1376        self.app.opt.bookmarks = self.tkopt.bookmarks.get()
1377        self.game.updateMenus()
1378
1379    def mOptEnableHint(self, *args):
1380        if self._cancelDrag(break_pause=False):
1381            return
1382        self.app.opt.hint = self.tkopt.hint.get()
1383        self.game.updateMenus()
1384
1385    def mOptEnableShuffle(self, *args):
1386        if self._cancelDrag(break_pause=False):
1387            return
1388        self.app.opt.shuffle = self.tkopt.shuffle.get()
1389        self.game.updateMenus()
1390
1391    def mOptEnableHighlightPiles(self, *args):
1392        if self._cancelDrag(break_pause=False):
1393            return
1394        self.app.opt.highlight_piles = self.tkopt.highlight_piles.get()
1395        self.game.updateMenus()
1396
1397    def mOptEnableHighlightCards(self, *args):
1398        if self._cancelDrag(break_pause=False):
1399            return
1400        self.app.opt.highlight_cards = self.tkopt.highlight_cards.get()
1401        self.game.updateMenus()
1402
1403    def mOptEnableHighlightSameRank(self, *args):
1404        if self._cancelDrag(break_pause=False):
1405            return
1406        self.app.opt.highlight_samerank = self.tkopt.highlight_samerank.get()
1407        # self.game.updateMenus()
1408
1409    def mOptEnableHighlightNotMatching(self, *args):
1410        if self._cancelDrag(break_pause=False):
1411            return
1412        self.app.opt.highlight_not_matching = \
1413            self.tkopt.highlight_not_matching.get()
1414        # self.game.updateMenus()
1415
1416    def mOptAnimations(self, *args):
1417        if self._cancelDrag(break_pause=False):
1418            return
1419        self.app.opt.animations = self.tkopt.animations.get()
1420
1421    def mRedealAnimation(self, *args):
1422        if self._cancelDrag(break_pause=False):
1423            return
1424        self.app.opt.redeal_animation = self.tkopt.redeal_animation.get()
1425
1426    def mWinAnimation(self, *args):
1427        if self._cancelDrag(break_pause=False):
1428            return
1429        self.app.opt.win_animation = self.tkopt.win_animation.get()
1430
1431    def mOptShadow(self, *args):
1432        if self._cancelDrag(break_pause=False):
1433            return
1434        self.app.opt.shadow = self.tkopt.shadow.get()
1435
1436    def mOptShade(self, *args):
1437        if self._cancelDrag(break_pause=False):
1438            return
1439        self.app.opt.shade = self.tkopt.shade.get()
1440
1441    def mOptShrinkFaceDown(self, *args):
1442        if self._cancelDrag(break_pause=False):
1443            return
1444        self.app.opt.shrink_face_down = self.tkopt.shrink_face_down.get()
1445        self.game.endGame(bookmark=1)
1446        self.game.quitGame(bookmark=1)
1447
1448    def mOptShadeFilledStacks(self, *args):
1449        if self._cancelDrag(break_pause=False):
1450            return
1451        self.app.opt.shade_filled_stacks = self.tkopt.shade_filled_stacks.get()
1452        self.game.endGame(bookmark=1)
1453        self.game.quitGame(bookmark=1)
1454
1455    def mOptMahjonggShowRemoved(self, *args):
1456        if self._cancelDrag():
1457            return
1458        self.app.opt.mahjongg_show_removed = \
1459            self.tkopt.mahjongg_show_removed.get()
1460        # self.game.updateMenus()
1461        self.game.endGame(bookmark=1)
1462        self.game.quitGame(bookmark=1)
1463
1464    def mOptShisenShowHint(self, *args):
1465        if self._cancelDrag(break_pause=False):
1466            return
1467        self.app.opt.shisen_show_hint = self.tkopt.shisen_show_hint.get()
1468        # self.game.updateMenus()
1469
1470    def mOptAccordionDealAll(self, *args):
1471        if self._cancelDrag(break_pause=False):
1472            return
1473        self.app.opt.accordion_deal_all = self.tkopt.accordion_deal_all.get()
1474        # self.game.updateMenus()
1475
1476    def _updateCardSize(self):
1477        geom = (self.app.canvas.winfo_width(),
1478                self.app.canvas.winfo_height())
1479        self.app.opt.game_geometry = geom
1480        self.app.game.resizeGame(card_size_manually=True)
1481        if self.app.opt.auto_scale or self.app.opt.spread_stacks:
1482            w, h = self.app.opt.game_geometry
1483            self.app.canvas.setInitialSize(w, h, scrollregion=False)
1484            # Resize a second time to auto scale
1485            self.app.game.resizeGame(card_size_manually=False)
1486        else:
1487            w = int(round(self.app.game.width * self.app.opt.scale_x))
1488            h = int(round(self.app.game.height * self.app.opt.scale_y))
1489            self.app.canvas.setInitialSize(w, h)
1490            self.app.top.wm_geometry("")    # cancel user-specified geometry
1491        # self.app.top.update_idletasks()
1492        self.app.setTile(self.app.tabletile_index)
1493        self.tkopt.tabletile.set(self.app.tabletile_index)
1494
1495        self.updateMenus()
1496
1497    def mIncreaseCardset(self, *event):
1498        if self._cancelDrag(break_pause=True):
1499            return
1500        if self.app.opt.scale_x < 4:
1501            self.app.opt.scale_x += 0.1
1502        else:
1503            return
1504        if self.app.opt.scale_y < 4:
1505            self.app.opt.scale_y += 0.1
1506        else:
1507            return
1508        self.app.opt.auto_scale = False
1509        self.tkopt.auto_scale.set(False)
1510        self._updateCardSize()
1511
1512    def mDecreaseCardset(self, *event):
1513        if self._cancelDrag(break_pause=True):
1514            return
1515        if self.app.opt.scale_x > 0.5:
1516            self.app.opt.scale_x -= 0.1
1517        else:
1518            return
1519        if self.app.opt.scale_y > 0.5:
1520            self.app.opt.scale_y -= 0.1
1521        else:
1522            return
1523        self.app.opt.auto_scale = False
1524        self.tkopt.auto_scale.set(False)
1525        self._updateCardSize()
1526
1527    def mResetCardset(self, *event):
1528        if self._cancelDrag(break_pause=True):
1529            return
1530        self.app.opt.scale_x = 1
1531        self.app.opt.scale_y = 1
1532
1533        self.app.opt.auto_scale = False
1534        self.tkopt.auto_scale.set(False)
1535        self._updateCardSize()
1536
1537    def mOptAutoScale(self, *event):
1538        if self._cancelDrag(break_pause=True):
1539            return
1540        auto_scale = not self.app.opt.auto_scale
1541
1542        # In the future, it should be possible to use both options together,
1543        # but the current logic conflicts, so not allowed for now.
1544        self.app.opt.spread_stacks = False
1545        self.tkopt.spread_stacks.set(False)
1546
1547        self.app.opt.auto_scale = auto_scale
1548        self.tkopt.auto_scale.set(auto_scale)
1549        self._updateCardSize()
1550
1551    def mOptPreserveAspectRatio(self, *event):
1552        if self._cancelDrag(break_pause=True):
1553            return
1554        preserve_aspect_ratio = not self.app.opt.preserve_aspect_ratio
1555
1556        self.app.opt.preserve_aspect_ratio = preserve_aspect_ratio
1557        self.tkopt.preserve_aspect_ratio.set(preserve_aspect_ratio)
1558        self._updateCardSize()
1559
1560    def mOptSpreadStacks(self, *event):
1561        if self._cancelDrag(break_pause=True):
1562            return
1563        spread_stacks = not self.app.opt.spread_stacks
1564
1565        # In the future, it should be possible to use both options together,
1566        # but the current logic conflicts, so not allowed for now.
1567        self.app.opt.auto_scale = False
1568        self.tkopt.auto_scale.set(False)
1569
1570        self.app.opt.spread_stacks = spread_stacks
1571        self.tkopt.spread_stacks.set(spread_stacks)
1572        self._updateCardSize()
1573
1574    def mOptCenterLayout(self, *event):
1575        if self._cancelDrag(break_pause=True):
1576            return
1577        self.app.opt.center_layout = not self.app.opt.center_layout
1578        self._updateCardSize()
1579
1580    def _mOptCardback(self, index):
1581        if self._cancelDrag(break_pause=False):
1582            return
1583        cs = self.app.cardset
1584        old_index = cs.backindex
1585        cs.updateCardback(backindex=index)
1586        if cs.backindex == old_index:
1587            return
1588        self.app.updateCardset(self.game.id)
1589        image = self.app.images.getBack(update=True)
1590        for card in self.game.cards:
1591            card.updateCardBackground(image=image)
1592        self.app.canvas.update_idletasks()
1593        self.tkopt.cardback.set(cs.backindex)
1594
1595    def mOptCardback(self, *event):
1596        self._mOptCardback(self.tkopt.cardback.get())
1597
1598    def mOptChangeCardback(self, *event):
1599        self._mOptCardback(self.app.cardset.backindex + 1)
1600
1601    def mOptChangeTableTile(self, *event):
1602        if self._cancelDrag(break_pause=False):
1603            return
1604        n = self.app.tabletile_manager.len()
1605        if n >= 2:
1606            i = (self.tkopt.tabletile.get() + 1) % n
1607            if self.app.setTile(i):
1608                self.tkopt.tabletile.set(i)
1609
1610    def mSelectTileDialog(self, *event):
1611        if self._cancelDrag(break_pause=False):
1612            return
1613        key = self.app.tabletile_index
1614        if key <= 0:
1615            key = self.app.opt.colors['table']  # .lower()
1616        d = self._calcSelectTileDialogWithPreview()(
1617            self.top, app=self.app,
1618            title=_("Select table background"),
1619            manager=self.app.tabletile_manager,
1620            key=key)
1621        if d.status == 0 and d.button == 0:
1622            if isinstance(d.key, str):
1623                tile = self.app.tabletile_manager.get(0)
1624                tile.color = d.key
1625                if self.app.setTile(0):
1626                    self.tkopt.tabletile.set(0)
1627            elif d.key > 0 and d.key != self.app.tabletile_index:
1628                if self.app.setTile(d.key):
1629                    self.tkopt.tabletile.set(d.key)
1630
1631    def mOptToolbar(self, *event):
1632        # if self._cancelDrag(break_pause=False): return
1633        self.setToolbarSide(self.tkopt.toolbar.get())
1634
1635    def mOptToolbarStyle(self, *event):
1636        # if self._cancelDrag(break_pause=False): return
1637        self.setToolbarStyle(self.tkopt.toolbar_style.get())
1638
1639    def mOptToolbarCompound(self, *event):
1640        # if self._cancelDrag(break_pause=False): return
1641        self.setToolbarCompound(self.tkopt.toolbar_compound.get())
1642
1643    def mOptToolbarSize(self, *event):
1644        # if self._cancelDrag(break_pause=False): return
1645        self.setToolbarSize(self.tkopt.toolbar_size.get())
1646
1647    def mOptToolbarConfig(self, w):
1648        self.toolbarConfig(w, self.tkopt.toolbar_vars[w].get())
1649
1650    def mOptStatusbar(self, *event):
1651        if self._cancelDrag(break_pause=False):
1652            return
1653        if not self.app.statusbar:
1654            return
1655        side = self.tkopt.statusbar.get()
1656        self.app.opt.statusbar = side
1657        resize = not self.app.opt.save_games_geometry
1658        if self.app.statusbar.show(side, resize=resize):
1659            self.top.update_idletasks()
1660
1661    def mOptNumCards(self, *event):
1662        if self._cancelDrag(break_pause=False):
1663            return
1664        self.app.opt.num_cards = self.tkopt.num_cards.get()
1665
1666    def mOptHelpbar(self, *event):
1667        if self._cancelDrag(break_pause=False):
1668            return
1669        if not self.app.helpbar:
1670            return
1671        show = self.tkopt.helpbar.get()
1672        self.app.opt.helpbar = show
1673        resize = not self.app.opt.save_games_geometry
1674        if self.app.helpbar.show(show, resize=resize):
1675            self.top.update_idletasks()
1676
1677    def mOptSaveGamesGeometry(self, *event):
1678        if self._cancelDrag(break_pause=False):
1679            return
1680        self.app.opt.save_games_geometry = self.tkopt.save_games_geometry.get()
1681
1682    def mOptDemoLogo(self, *event):
1683        if self._cancelDrag(break_pause=False):
1684            return
1685        self.app.opt.demo_logo = self.tkopt.demo_logo.get()
1686
1687    def mOptSplashscreen(self, *event):
1688        if self._cancelDrag(break_pause=False):
1689            return
1690        self.app.opt.splashscreen = self.tkopt.splashscreen.get()
1691
1692    def mOptMouseType(self, *event):
1693        if self._cancelDrag(break_pause=False):
1694            return
1695        self.app.opt.mouse_type = self.tkopt.mouse_type.get()
1696
1697    def mOptMouseUndo(self, *event):
1698        if self._cancelDrag(break_pause=False):
1699            return
1700        self.app.opt.mouse_undo = self.tkopt.mouse_undo.get()
1701
1702    def mOptNegativeBottom(self, *event):
1703        if self._cancelDrag():
1704            return
1705        self.app.opt.negative_bottom = self.tkopt.negative_bottom.get()
1706        self.app.updateCardset()
1707        self.game.endGame(bookmark=1)
1708        self.game.quitGame(bookmark=1)
1709
1710    #
1711    # toolbar support
1712    #
1713
1714    def setToolbarSide(self, side):
1715        if self._cancelDrag(break_pause=False):
1716            return
1717        self.app.opt.toolbar = side
1718        self.tkopt.toolbar.set(side)                    # update radiobutton
1719        resize = not self.app.opt.save_games_geometry
1720        if self.app.toolbar.show(side, resize=resize):
1721            self.top.update_idletasks()
1722
1723    def setToolbarSize(self, size):
1724        if self._cancelDrag(break_pause=False):
1725            return
1726        self.app.opt.toolbar_size = size
1727        self.tkopt.toolbar_size.set(size)                # update radiobutton
1728        dir = self.app.getToolbarImagesDir()
1729        if self.app.toolbar.updateImages(dir, size):
1730            self.game.updateStatus(player=self.app.opt.player)
1731            self.top.update_idletasks()
1732
1733    def setToolbarStyle(self, style):
1734        if self._cancelDrag(break_pause=False):
1735            return
1736        self.app.opt.toolbar_style = style
1737        self.tkopt.toolbar_style.set(style)                # update radiobutton
1738        dir = self.app.getToolbarImagesDir()
1739        size = self.app.opt.toolbar_size
1740        if self.app.toolbar.updateImages(dir, size):
1741            # self.game.updateStatus(player=self.app.opt.player)
1742            self.top.update_idletasks()
1743
1744    def setToolbarCompound(self, compound):
1745        if self._cancelDrag(break_pause=False):
1746            return
1747        self.app.opt.toolbar_compound = compound
1748        self.tkopt.toolbar_compound.set(compound)          # update radiobutton
1749        if self.app.toolbar.setCompound(compound):
1750            self.game.updateStatus(player=self.app.opt.player)
1751            self.top.update_idletasks()
1752
1753    def wizardDialog(self, edit=False):
1754        from pysollib.wizardutil import write_game, reset_wizard
1755        WizardDialog = self._calcWizardDialog()
1756
1757        if edit:
1758            reset_wizard(self.game)
1759        else:
1760            reset_wizard(None)
1761        d = WizardDialog(self.top, _('Solitaire Wizard'), self.app)
1762        if d.status == 0 and d.button == 0:
1763            try:
1764                if edit:
1765                    gameid = write_game(self.app, game=self.game)
1766                else:
1767                    gameid = write_game(self.app)
1768            except Exception as err:
1769                # if False:
1770                #    traceback.print_exc()
1771                self._calc_MfxMessageDialog()(
1772                    self.top, title=_('Save game error'),
1773                    text=_('''
1774Error while saving game.
1775
1776%s
1777''') % str(err),
1778                    bitmap='error')
1779                return
1780
1781            if SELECT_GAME_MENU:
1782                self.updateCustomGamesMenu()
1783
1784            self.tkopt.gameid.set(gameid)
1785            self._mSelectGame(gameid, force=True)
1786
1787    def mWizard(self, *event):
1788        if self._cancelDrag(break_pause=False):
1789            return
1790        self.wizardDialog()
1791
1792    def mWizardEdit(self, *event):
1793        if self._cancelDrag(break_pause=False):
1794            return
1795        self.wizardDialog(edit=True)
1796
1797    def mWizardDelete(self, *event):
1798        if self._cancelDrag(break_pause=False):
1799            return
1800        if not self.game.areYouSure(_("Delete game"),
1801                                    _("Delete the game %s?")
1802                                    % self.game.gameinfo.name):
1803            return
1804        from pysollib.wizardutil import delete_game
1805        delete_game(self.game)
1806        self.game.endGame()
1807        self.game.quitGame(2)
1808
1809        if SELECT_GAME_MENU:
1810            self.updateCustomGamesMenu()
1811
1812    def toolbarConfig(self, w, v):
1813        if self._cancelDrag(break_pause=False):
1814            return
1815        self.app.opt.toolbar_vars[w] = v
1816        self.app.toolbar.config(w, v)
1817        self.top.update_idletasks()
1818
1819    #
1820    # stacks descriptions
1821    #
1822
1823    def mStackDesk(self, *event):
1824        if self.game.stackdesc_list:
1825            self.game.deleteStackDesc()
1826        else:
1827            if self._cancelDrag(break_pause=True):
1828                return
1829            self.game.showStackDesc()
1830