1#!/usr/bin/env python
2# -*- mode: python; coding: utf-8; -*-
3# ---------------------------------------------------------------------------
4#
5# Copyright (C) 1998-2003 Markus Franz Xaver Johannes Oberhumer
6# Copyright (C) 2003 Mt. Hood Playing Card Co.
7# Copyright (C) 2005-2009 Skomoroh
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program.  If not, see <http://www.gnu.org/licenses/>.
21#
22# ---------------------------------------------------------------------------
23
24import os
25import time
26
27from pysollib.mfxutil import KwStruct, kwdefault
28from pysollib.mfxutil import format_time
29from pysollib.mygettext import _
30from pysollib.settings import TOP_TITLE
31from pysollib.stats import ProgressionFormatter, PysolStatsFormatter
32from pysollib.ui.tktile.tkutil import bind, loadImage
33
34from six.moves import tkinter
35from six.moves import tkinter_font
36
37from .tkwidget import MfxDialog, MfxMessageDialog
38from .tkwidget import MfxScrolledCanvas
39
40# FIXME - this file is a quick hack and needs a rewrite
41
42
43class SingleGame_StatsDialog(MfxDialog):
44    def __init__(self, parent, title, app, player, gameid, **kw):
45        self.app = app
46        self.selected_game = None
47        kw = self.initKw(kw)
48        MfxDialog.__init__(self, parent, title, kw.resizable, kw.default)
49        top_frame, bottom_frame = self.createFrames(kw)
50        self.top_frame = top_frame
51        self.createBitmaps(top_frame, kw)
52        #
53        self.player = player or _("Demo games")
54        self.top.wm_minsize(200, 200)
55        self.button = kw.default
56        #
57        # createChart = self.create3DBarChart
58        createChart = self.createPieChart
59        # createChart = self.createSimpleChart
60        #  if parent.winfo_screenwidth() < 800 or parent.winfo_screenheight() <
61        #        600:
62        #      createChart = self.createPieChart
63        #      createChart = self.createSimpleChart
64        #
65        self.font = self.app.getFont("default")
66        self.tk_font = tkinter_font.Font(self.top, self.font)
67        self.font_metrics = self.tk_font.metrics()
68        self._calc_tabs()
69        #
70        won, lost = app.stats.getStats(player, gameid)
71        createChart(app, won, lost, _("Total"))
72        won, lost = app.stats.getSessionStats(player, gameid)
73        createChart(app, won, lost, _("Current session"))
74        #
75        focus = self.createButtons(bottom_frame, kw)
76        self.mainloop(focus, kw.timeout)
77
78    #
79    # helpers
80    #
81
82    def _calc_tabs(self):
83        #
84        font = self.tk_font
85        t0 = 160
86        t = ''
87        for i in (_("Won:"),
88                  _("Lost:"),
89                  _("Total:")):
90            if len(i) > len(t):
91                t = i
92        t1 = font.measure(t)
93        #  t1 = max(font.measure(_("Won:")),
94        #           font.measure(_("Lost:")),
95        #           font.measure(_("Total:")))
96        t1 += 10
97        # t2 = font.measure('99999')+10
98        t2 = 45
99        # t3 = font.measure('100%')+10
100        t3 = 45
101        tx = (t0, t0+t1+t2, t0+t1+t2+t3)
102        #
103        ls = self.font_metrics['linespace']
104        ls += 5
105        ls = max(ls, 20)
106        ty = (ls, 2*ls, 3*ls+15, 3*ls+25)
107        #
108        self.tab_x, self.tab_y = tx, ty
109
110    def _getPwon(self, won, lost):
111        pwon, plost = 0.0, 0.0
112        if won + lost > 0:
113            pwon = float(won) / (won + lost)
114            pwon = min(max(pwon, 0.00001), 0.99999)
115            plost = 1.0 - pwon
116        return pwon, plost
117
118    def _createChartInit(self, text):
119        w, h = self.tab_x[-1]+20, self.tab_y[-1]+20
120        c = tkinter.Canvas(self.top_frame, width=w, height=h)
121        c.pack(side='top', fill='both', expand=False, padx=20, pady=10)
122        self.canvas = c
123        # self.fg = c.cget("insertbackground")
124        self.fg = c.option_get('foreground', '') or c.cget("insertbackground")
125        #
126        c.create_rectangle(2, 7, w, h, fill="", outline="#7f7f7f")
127        label = tkinter.Label(c, text=text, font=self.font, bd=0, padx=3,
128                              pady=1)
129        dy = int(self.font_metrics['ascent']) - 10
130        dy //= 2
131        c.create_window(20, -dy, window=label, anchor="nw")
132
133    def _createChartTexts(self, tx, ty, won, lost):
134        c, tfont, fg = self.canvas, self.font, self.fg
135        pwon, plost = self._getPwon(won, lost)
136        #
137        x = tx[0]
138        dy = int(self.font_metrics['ascent']) - 10
139        dy //= 2
140        c.create_text(
141            x, ty[0]-dy, text=_("Won:"), anchor="nw", font=tfont, fill=fg)
142        c.create_text(
143            x, ty[1]-dy, text=_("Lost:"), anchor="nw", font=tfont, fill=fg)
144        c.create_text(
145            x, ty[2]-dy, text=_("Total:"), anchor="nw", font=tfont, fill=fg)
146        x = tx[1] - 16
147        c.create_text(
148            x, ty[0]-dy, text="%d" % won, anchor="ne", font=tfont, fill=fg)
149        c.create_text(
150            x, ty[1]-dy, text="%d" % lost, anchor="ne", font=tfont, fill=fg)
151        c.create_text(
152            x, ty[2]-dy, text="%d" % (won + lost), anchor="ne", font=tfont,
153            fill=fg)
154        y = ty[2] - 11
155        c.create_line(tx[0], y, x, y, fill=fg)
156        if won + lost > 0:
157            x = tx[2]
158            pw = int(round(100.0 * pwon))
159            c.create_text(
160                x, ty[0]-dy, text="%d%%" % pw, anchor="ne",
161                font=tfont, fill=fg)
162            c.create_text(
163                x, ty[1]-dy, text="%d%%" % (100-pw), anchor="ne", font=tfont,
164                fill=fg)
165
166#      def _createChart3DBar(self, canvas, perc, x, y, p, col):
167#          if perc < 0.005:
168#              return
169#          # translate and scale
170#          p = list(p[:])
171#          for i in (0, 1, 2, 3):
172#              p[i] = (x + p[i][0], y + p[i][1])
173#              j = i + 4
174#              dx = int(round(p[j][0] * perc))
175#              dy = int(round(p[j][1] * perc))
176#              p[j] = (p[i][0] + dx, p[i][1] + dy)
177#          # draw rects
178#          def draw_rect(a, b, c, d, col, canvas=canvas, p=p):
179#              points = (p[a][0], p[a][1], p[b][0], p[b][1],
180#                        p[c][0], p[c][1], p[d][0], p[d][1])
181#              canvas.create_polygon(points, fill=col)
182#          draw_rect(0, 1, 5, 4, col[0])
183#          draw_rect(1, 2, 6, 5, col[1])
184#          draw_rect(4, 5, 6, 7, col[2])
185#          # draw lines
186#          def draw_line(a, b, canvas=canvas, p=p):
187#              # print a, b, p[a], p[b]
188#              canvas.create_line(p[a][0], p[a][1], p[b][0], p[b][1])
189#          draw_line(0, 1)
190#          draw_line(1, 2)
191#          draw_line(0, 4)
192#          draw_line(1, 5)
193#          draw_line(2, 6)
194#          #  draw_line(3, 7)     # test
195#          draw_line(4, 5)
196#          draw_line(5, 6)
197#          draw_line(6, 7)
198#          draw_line(7, 4)
199#      def createSimpleChart(self, app, won, lost, text):
200#          #c, tfont, fg = self._createChartInit(frame, 300, 100, text)
201#          self._createChartInit(300, 100, text)
202#          c, tfont, fg = self.canvas, self.font, self.fg
203#          #
204#          tx = (90, 180, 210)
205#          ty = (21, 41, 75)
206#          self._createChartTexts(tx, ty, won, lost)
207#      def create3DBarChart(self, app, won, lost, text):
208#          image = app.gimages.stats[0]
209#          iw, ih = image.width(), image.height()
210#          #c, tfont, fg = self._createChartInit(frame, iw+160, ih, text)
211#          self._createChartInit(iw+160, ih, text)
212#          c, tfont, fg = self.canvas, self.font, self.fg
213#          pwon, plost = self._getPwon(won, lost)
214#          #
215#          tx = (iw+20, iw+110, iw+140)
216#          yy = ih//2 # + 7
217#          ty = (yy+21-46, yy+41-46, yy+75-46)
218#          #
219#          c.create_image(0, 7, image=image, anchor="nw")
220#          #
221#          p = ((0, 0), (44, 6), (62, -9), (20, -14),
222#               (-3, -118), (-1, -120), (-1, -114), (-4, -112))
223#          col = ("#00ff00", "#008200", "#00c300")
224#          self._createChart3DBar(c, pwon,  102, 145+7, p, col)
225#          p = ((0, 0), (49, 6), (61, -10), (15, -15),
226#               (1, -123), (3, -126), (4, -120), (1, -118))
227#          col = ("#ff0000", "#860400", "#c70400")
228#          self._createChart3DBar(c, plost, 216, 159+7, p, col)
229#          #
230#          self._createChartTexts(tx, ty, won, lost)
231#          c.create_text(tx[0], ty[0]-48, text=self.player, anchor="nw",
232#          font=tfont, fill=fg)
233
234    def createPieChart(self, app, won, lost, text):
235        # c, tfont, fg = self._createChartInit(frame, 300, 100, text)
236        #
237        self._createChartInit(text)
238        c, tfont = self.canvas, self.font
239        pwon, plost = self._getPwon(won, lost)
240        #
241        # tx = (160, 250, 280)
242        # ty = (21, 41, 75)
243        #
244        tx, ty = self.tab_x, self.tab_y
245        if won + lost > 0:
246            # s, ewon, elost = 90.0, -360.0 * pwon, -360.0 * plost
247            s, ewon, elost = 0.0, 360.0 * pwon, 360.0 * plost
248            c.create_arc(
249                20, 25+9, 110, 75+9,  fill="#007f00", start=s, extent=ewon)
250            c.create_arc(
251                20, 25+9, 110, 75+9,  fill="#7f0000", start=s+ewon,
252                extent=elost)
253            c.create_arc(
254                20, 25,   110, 75,    fill="#00ff00", start=s, extent=ewon)
255            c.create_arc(
256                20, 25,   110, 75,    fill="#ff0000", start=s+ewon,
257                extent=elost)
258            x, y = tx[0] - 25, ty[0]
259            c.create_rectangle(x, y, x+10, y+10, fill="#00ff00")
260            y = ty[1]
261            c.create_rectangle(x, y, x+10, y+10, fill="#ff0000")
262        else:
263            c.create_oval(20, 25+10, 110, 75+10, fill="#7f7f7f")
264            c.create_oval(20, 25,    110, 75,    fill="#f0f0f0")
265            c.create_text(
266                65, 50, text=_("No games"), anchor="center",
267                font=tfont, fill="#bfbfbf")
268        #
269        self._createChartTexts(tx, ty, won, lost)
270
271    #
272    #
273    #
274
275    def initKw(self, kw):
276        kw = KwStruct(
277            kw,
278            strings=(_("&OK"),
279                     (_("&All games..."), 102),
280                     (TOP_TITLE+"...", 105),
281                     (_("&Reset..."), 302)), default=0,
282            image=self.app.gimages.logos[5],
283            padx=10, pady=10,
284        )
285        return MfxDialog.initKw(self, kw)
286
287
288# ************************************************************************
289# *
290# ************************************************************************
291
292class CanvasFormatter(PysolStatsFormatter):
293    def __init__(self, app, canvas, parent_window, font, w, h):
294        self.app = app
295        self.canvas = canvas
296        self.parent_window = parent_window
297        # self.fg = canvas.cget("insertbackground")
298        self.fg = canvas.option_get('foreground', '') or \
299            canvas.cget("insertbackground")
300        self.font = font
301        self.w = w
302        self.h = h
303        # self.x = self.y = 0
304        self.gameid = None
305        self.gamenumber = None
306        self.canvas.config(yscrollincrement=h)
307        self._tabs = None
308
309    def _addItem(self, id):
310        self.canvas.dialog.nodes[id] = (self.gameid, self.gamenumber)
311
312    def _calc_tabs(self, arg):
313        tw = 15*self.w
314        # tw = 160
315        self._tabs = [tw]
316        font = tkinter_font.Font(self.canvas, self.font)
317        for t in arg[1:]:
318            tw = font.measure(t)+20
319            self._tabs.append(tw)
320        self._tabs.append(10)
321
322    def pstats(self, y, args, gameid=None):
323        x = 1
324        t1, t2, t3, t4, t5, t6, t7 = args
325        self.gameid = gameid
326        if gameid is None:              # header
327            self.gameid = 'header'
328        for var, text, anchor, tab in (
329            ('name',    t1, 'nw', self._tabs[0]+self._tabs[1]),
330            ('played',  t2, 'ne', self._tabs[2]),
331            ('won',     t3, 'ne', self._tabs[3]),
332            ('lost',    t4, 'ne', self._tabs[4]),
333            ('time',    t5, 'ne', self._tabs[5]),
334            ('moves',   t6, 'ne', self._tabs[6]),
335            ('percent', t7, 'ne', self._tabs[7]),
336                ):
337            self.gamenumber = None
338            if gameid is None:          # header
339                self.gamenumber = var
340            id = self.canvas.create_text(x, y, text=text, anchor=anchor,
341                                         font=self.font, fill=self.fg)
342            self._addItem(id)
343            x += tab
344        self.pstats_perc(x, y, t7)
345
346    def pstats_perc(self, x, y, t):
347        if not (t and "0" <= t[0] <= "9"):
348            return
349        perc = int(round(float(str(t))))
350        if perc < 1:
351            return
352        rx, ry, rw, rh = x, y+1, 2 + 8*10, self.h-5
353        if 1:
354            w = int(round(rw*perc/100.0))
355            if 1 and w < 1:
356                return
357            if w > 0:
358                w = max(3, w)
359                w = min(rw - 2, w)
360                self.canvas.create_rectangle(
361                    rx, ry, rx+w, ry+rh, width=1,
362                    fill="#00ff00", outline="#000000")
363            if w < rw:
364                self.canvas.create_rectangle(
365                    rx+w, ry, rx+rw, ry+rh, width=1,
366                    fill="#ff0000", outline="#000000")
367            return
368        # fill = "#ffffff"
369        # fill = self.canvas["bg"]
370        fill = None
371        self.canvas.create_rectangle(rx, ry, rx+rw, ry+rh, width=1,
372                                     fill=fill, outline="#808080")
373        if 1:
374            rx, rw = rx + 1, rw - 1
375            ry, rh = ry + 1, rh - 1
376            w = int(round(rw*perc/100.0))
377            if w > 0:
378                self.canvas.create_rectangle(rx, ry, rx+w, ry+rh, width=0,
379                                             fill="#00ff00", outline="")
380            if w < rw:
381                self.canvas.create_rectangle(
382                    rx+w, ry, rx+rw, ry+rh, width=0,
383                    fill="#ff0000", outline="")
384            return
385        p = 1.0
386        ix = rx + 2
387        for i in (1, 11, 21, 31, 41, 51, 61, 71, 81, 91):
388            if perc < i:
389                break
390            # c = "#ff8040"
391            r, g, b = 255, 128*p, 64*p
392            c = "#%02x%02x%02x" % (int(r), int(g), int(b))
393            self.canvas.create_rectangle(ix, ry+2, ix+6, ry+rh-2, width=0,
394                                         fill=c, outline=c)
395            ix = ix + 8
396            p = max(0.0, p - 0.1)
397
398    def writeStats(self, player, sort_by='name'):
399        header = self.getStatHeader()
400        y = 0
401        if self._tabs is None:
402            self._calc_tabs(header)
403        self.pstats(y, header)
404        #
405        y += 2*self.h
406        for result in self.getStatResults(player, sort_by):
407            gameid = result.pop()
408            self.pstats(y, result, gameid)
409            y += self.h
410        #
411        y += self.h
412        total, played, won, lost, time_, moves, perc = self.getStatSummary()
413        s = _("Total (%(played)d out of %(total)d games)") % {
414            'played': played, 'total': total}
415        self.pstats(y, (s, won+lost, won, lost, time_, moves, perc))
416
417    def writeLog(self, player, prev_games):
418        y = 0
419        header = self.getLogHeader()
420        t1, t2, t3, t4 = header
421        s = "%-25s %-20s  %-17s  %s" % header
422        id = self.canvas.create_text(1, y, text=s, anchor="nw",
423                                     font=self.font, fill=self.fg)
424        self._addItem(id)
425        y += 2*self.h
426        if not player or not prev_games:
427            return 0
428        for result in self.getLogResults(player, prev_games):
429            s = "%-25s %-20s  %-17s  %s" % tuple(result[:4])
430            id = self.canvas.create_text(1, y, text=s, anchor="nw",
431                                         font=self.font, fill=self.fg)
432            y += self.h
433        return 1
434
435    def writeFullLog(self, player):
436        prev_games = self.app.stats.prev_games.get(player)
437        return self.writeLog(player, prev_games)
438
439    def writeSessionLog(self, player):
440        prev_games = self.app.stats.session_games.get(player)
441        return self.writeLog(player, prev_games)
442
443
444# ************************************************************************
445# *
446# ************************************************************************
447
448class AllGames_StatsDialogScrolledCanvas(MfxScrolledCanvas):
449    pass
450
451
452class AllGames_StatsDialog(MfxDialog):
453
454    YVIEW = 0
455    FONT_TYPE = "default"
456
457    def __init__(self, parent, title, app, player, **kw):
458        lines = 25
459        # if parent and parent.winfo_screenheight() < 600:
460        #    lines = 20
461        #
462        self.font = app.getFont(self.FONT_TYPE)
463        font = tkinter_font.Font(parent, self.font)
464        self.font_metrics = font.metrics()
465        self.CHAR_H = self.font_metrics['linespace']
466        self.CHAR_W = font.measure('M')
467        self.app = app
468        #
469        self.player = player
470        self.title = title
471        self.sort_by = 'name'
472        self.selected_game = None
473        #
474        kwdefault(kw, width=self.CHAR_W*64, height=lines*self.CHAR_H)
475        kw = self.initKw(kw)
476        MfxDialog.__init__(self, parent, title, kw.resizable, kw.default)
477        top_frame, bottom_frame = self.createFrames(kw)
478        self.createBitmaps(top_frame, kw)
479        #
480        self.top.wm_minsize(200, 200)
481        self.button = kw.default
482        #
483        self.sc = AllGames_StatsDialogScrolledCanvas(
484            top_frame, width=kw.width, height=kw.height)
485        self.sc.pack(fill='both', expand=True, padx=kw.padx, pady=kw.pady)
486        #
487        self.nodes = {}
488        self.canvas = self.sc.canvas
489        self.canvas.dialog = self
490        from pysollib.options import calcCustomMouseButtonsBinding
491        bind(
492            self.canvas,
493            calcCustomMouseButtonsBinding("<{mouse_button1}>"),
494            self.singleClick
495        )
496        self.fillCanvas(player, title)
497        bbox = self.canvas.bbox("all")
498        # print bbox
499        # self.canvas.config(scrollregion=bbox)
500        dx, dy = 4, 0
501        self.canvas.config(scrollregion=(-dx, -dy, bbox[2]+dx, bbox[3]+dy))
502        self.canvas.xview_moveto(-dx)
503        self.canvas.yview_moveto(self.YVIEW)
504        #
505        focus = self.createButtons(bottom_frame, kw)
506        self.mainloop(focus, kw.timeout)
507
508    def initKw(self, kw):
509        kw = KwStruct(
510            kw,
511            strings=(_("&OK"),
512                     (_("&Save to file"), 202),
513                     (_("&Reset all..."), 301),),
514            default=0,
515            resizable=True,
516            padx=10, pady=10,
517            # width=900,
518        )
519        return MfxDialog.initKw(self, kw)
520
521    def destroy(self):
522        self.app = None
523        self.canvas.dialog = None
524        self.nodes = {}
525        self.sc.destroy()
526        MfxDialog.destroy(self)
527
528    def rearrange(self, sort_by):
529        if self.sort_by == sort_by:
530            return
531        self.sort_by = sort_by
532        self.fillCanvas(self.player, self.title)
533
534    def singleClick(self, event=None):
535        id = self.canvas.find_withtag('current')
536        if not id:
537            return
538        # print 'singleClick:', id, self.nodes.get(id[0])
539        gameid, gamenumber = self.nodes.get(id[0], (None, None))
540        if gameid == 'header':
541            if self.sort_by == gamenumber:
542                return
543            self.sort_by = gamenumber
544            self.fillCanvas(self.player, self.title)
545            return
546        #  FIXME / TODO
547        return
548        if gameid and gamenumber:
549            print(gameid, gamenumber)
550        elif gameid:
551            print(gameid)
552
553    #
554    #
555    #
556
557    def fillCanvas(self, player, header):
558        self.canvas.delete('all')
559        self.nodes = {}
560        writer = CanvasFormatter(self.app, self.canvas, self,
561                                 self.font, self.CHAR_W, self.CHAR_H)
562        writer.writeStats(player, self.sort_by)
563
564
565# ************************************************************************
566# *
567# ************************************************************************
568
569class FullLog_StatsDialog(AllGames_StatsDialog):
570    YVIEW = 1
571    FONT_TYPE = "fixed"
572
573    def fillCanvas(self, player, header):
574        writer = CanvasFormatter(self.app, self.canvas, self,
575                                 self.font, self.CHAR_W, self.CHAR_H)
576        writer.writeFullLog(player)
577
578    def initKw(self, kw):
579        kw = KwStruct(kw,
580                      strings=(_("&OK"), (_("Session &log..."), 104),
581                               (_("&Save to file"), 203)), default=0,
582                      width=76*self.CHAR_W,
583                      )
584        return AllGames_StatsDialog.initKw(self, kw)
585
586
587class SessionLog_StatsDialog(FullLog_StatsDialog):
588    def fillCanvas(self, player, header):
589        PysolStatsFormatter()
590        writer = CanvasFormatter(self.app, self.canvas, self,
591                                 self.font, self.CHAR_W, self.CHAR_H)
592        writer.writeSessionLog(player)
593
594    def initKw(self, kw):
595        kw = KwStruct(
596            kw,
597            strings=(_("&OK"), (_("&Full log..."), 103),
598                     (_("&Save to file"), 204)),
599            default=0,)
600        return FullLog_StatsDialog.initKw(self, kw)
601
602# ************************************************************************
603# *
604# ************************************************************************
605
606
607class Status_StatsDialog(MfxMessageDialog):
608    def __init__(self, parent, game):
609        stats, gstats = game.stats, game.gstats
610        w1 = w2 = ""
611        n = 0
612        for s in game.s.foundations:
613            n = n + len(s.cards)
614        w1 = (_("Highlight piles: ") + str(stats.highlight_piles) + "\n" +
615              _("Highlight cards: ") + str(stats.highlight_cards) + "\n" +
616              _("Highlight same rank: ") + str(stats.highlight_samerank) +
617              "\n")
618        if game.s.talon:
619            if game.gameinfo.redeals != 0:
620                w2 = w2 + _("\nRedeals: ") + str(game.s.talon.round - 1)
621            w2 = w2 + _("\nCards in Talon: ") + str(len(game.s.talon.cards))
622        if game.s.waste and game.s.waste not in game.s.foundations:
623            w2 = w2 + _("\nCards in Waste: ") + str(len(game.s.waste.cards))
624        if game.s.foundations:
625            w2 = w2 + _("\nCards in Foundations: ") + str(n)
626        #
627        date = time.strftime(
628            "%Y-%m-%d %H:%M",
629            time.localtime(game.gstats.start_time))
630        MfxMessageDialog.__init__(
631            self, parent, title=_("Game status"),
632            text=game.getTitleName() + "\n" +
633            game.getGameNumber(format=1) + "\n" +
634            _("Playing time: ") + game.getTime() + "\n" +
635            _("Started at: ") + date + "\n\n" +
636            _("Moves: ") + str(game.moves.index) + "\n" +
637            _("Undo moves: ") + str(stats.undo_moves) + "\n" +
638            _("Bookmark moves: ") + str(gstats.goto_bookmark_moves) + "\n" +
639            _("Demo moves: ") + str(stats.demo_moves) + "\n" +
640            _("Total player moves: ") + str(stats.player_moves) + "\n" +
641            _("Total moves in this game: ") + str(stats.total_moves) + "\n" +
642            _("Hints: ") + str(stats.hints) + "\n" +
643            "\n" +
644            w1 + w2,
645            strings=(_("&OK"),
646                     (_("&Statistics..."), 101),
647                     (TOP_TITLE+"...", 105), ),
648            image=game.app.gimages.logos[3],
649            image_side="left", image_padx=20,
650            padx=20,
651            )
652
653# ************************************************************************
654# *
655# ************************************************************************
656
657
658class _TopDialog(MfxDialog):
659    def __init__(self, parent, title, top, **kw):
660        kw = self.initKw(kw)
661        MfxDialog.__init__(self, parent, title, kw.resizable, kw.default)
662        top_frame, bottom_frame = self.createFrames(kw)
663        self.createBitmaps(top_frame, kw)
664
665        cnf = {'master': top_frame,
666               'highlightthickness': 1,
667               'highlightbackground': 'black',
668               }
669        frame = tkinter.Frame(**cnf)
670        frame.pack(expand=True, fill='both', padx=10, pady=10)
671        frame.columnconfigure(0, weight=1)
672        cnf['master'] = frame
673        cnf['text'] = _('N')
674        label = tkinter.Label(**cnf)
675        label.grid(row=0, column=0, sticky='ew')
676        cnf['text'] = _('Game number')
677        label = tkinter.Label(**cnf)
678        label.grid(row=0, column=1, sticky='ew')
679        cnf['text'] = _('Started at')
680        label = tkinter.Label(**cnf)
681        label.grid(row=0, column=2, sticky='ew')
682        cnf['text'] = _('Result')
683        label = tkinter.Label(**cnf)
684        label.grid(row=0, column=3, sticky='ew')
685
686        row = 1
687        for i in top:
688            # N
689            cnf['text'] = str(row)
690            label = tkinter.Label(**cnf)
691            label.grid(row=row, column=0, sticky='ew')
692            # Game number
693            cnf['text'] = '#'+str(i.game_number)
694            label = tkinter.Label(**cnf)
695            label.grid(row=row, column=1, sticky='ew')
696            # Start time
697            t = time.strftime(
698                '%Y-%m-%d %H:%M', time.localtime(i.game_start_time))
699            cnf['text'] = t
700            label = tkinter.Label(**cnf)
701            label.grid(row=row, column=2, sticky='ew')
702            # Result
703            if isinstance(i.value, float):
704                # time
705                s = format_time(i.value)
706            else:
707                # moves
708                s = str(i.value)
709            cnf['text'] = s
710            label = tkinter.Label(**cnf)
711            label.grid(row=row, column=3, sticky='ew')
712            row += 1
713
714        focus = self.createButtons(bottom_frame, kw)
715        self.mainloop(focus, kw.timeout)
716
717    def initKw(self, kw):
718        kw = KwStruct(kw, strings=(_('&OK'),), default=0, separator=True)
719        return MfxDialog.initKw(self, kw)
720
721
722class Top_StatsDialog(MfxDialog):
723    def __init__(self, parent, title, app, player, gameid, **kw):
724        self.app = app
725        kw = self.initKw(kw)
726        MfxDialog.__init__(self, parent, title, kw.resizable, kw.default)
727        top_frame, bottom_frame = self.createFrames(kw)
728        self.createBitmaps(top_frame, kw)
729
730        frame = tkinter.Frame(top_frame)
731        frame.pack(expand=True, fill='both', padx=10, pady=10)
732        frame.columnconfigure(0, weight=1)
733
734        if (player in app.stats.games_stats and
735            gameid in app.stats.games_stats[player] and
736                app.stats.games_stats[player][gameid].time_result.top):
737
738            tkinter.Label(frame, text=_('Minimum')).grid(row=0, column=1)
739            tkinter.Label(frame, text=_('Maximum')).grid(row=0, column=2)
740            tkinter.Label(frame, text=_('Average')).grid(row=0, column=3)
741            # tkinter.Label(frame, text=_('Total')).grid(row=0, column=4)
742
743            s = app.stats.games_stats[player][gameid]
744            row = 1
745            ll = [
746                (_('Playing time:'),
747                 format_time(s.time_result.min),
748                 format_time(s.time_result.max),
749                 format_time(s.time_result.average),
750                 format_time(s.time_result.total),
751                 s.time_result.top,
752                 ),
753                (_('Moves:'),
754                 s.moves_result.min,
755                 s.moves_result.max,
756                 round(s.moves_result.average, 2),
757                 s.moves_result.total,
758                 s.moves_result.top,
759                 ),
760                (_('Total moves:'),
761                 s.total_moves_result.min,
762                 s.total_moves_result.max,
763                 round(s.total_moves_result.average, 2),
764                 s.total_moves_result.total,
765                 s.total_moves_result.top,
766                 ),
767                ]
768            #  if s.score_result.min:
769            #      ll.append(('Score:',
770            #                 s.score_result.min,
771            #                 s.score_result.max,
772            #                 round(s.score_result.average, 2),
773            #                 s.score_result.top,
774            #                 ))
775            #  if s.score_casino_result.min:
776            #      ll.append(('Casino Score:',
777            #                 s.score_casino_result.min,
778            #                 s.score_casino_result.max,
779            #                 round(s.score_casino_result.average, 2), ))
780            for label, min, max, avr, tot, top in ll:
781                tkinter.Label(frame, text=label).grid(row=row, column=0)
782                tkinter.Label(frame, text=str(min)).grid(row=row, column=1)
783                tkinter.Label(frame, text=str(max)).grid(row=row, column=2)
784                tkinter.Label(frame, text=str(avr)).grid(row=row, column=3)
785                # tkinter.Label(frame, text=str(tot)).grid(row=row, column=4)
786                b = tkinter.Button(frame, text=TOP_TITLE+' ...', width=10,
787                                   command=lambda top=top: self.showTop(top))
788                b.grid(row=row, column=5)
789                row += 1
790        else:
791            tkinter.Label(frame, text=_('No TOP for this game')).pack()
792
793        focus = self.createButtons(bottom_frame, kw)
794        self.mainloop(focus, kw.timeout)
795
796    def showTop(self, top):
797        # print top
798        _TopDialog(self.top, TOP_TITLE, top)
799
800    def initKw(self, kw):
801        kw = KwStruct(kw,
802                      strings=(_('&OK'),),
803                      default=0,
804                      image=self.app.gimages.logos[4],
805                      separator=True,
806                      )
807        return MfxDialog.initKw(self, kw)
808
809
810# ************************************************************************
811# *
812# ************************************************************************
813
814class ProgressionDialog(MfxDialog):
815    def __init__(self, parent, title, app, player, gameid, **kw):
816
817        font_name = app.getFont('default')
818        font = tkinter_font.Font(parent, font_name)
819        tkfont = tkinter_font.Font(parent, font)
820        font_metrics = font.metrics()
821        measure = tkfont.measure
822        self.text_height = font_metrics['linespace']
823        self.text_width = measure('XX.XX.XX')
824
825        self.items = []
826        self.formatter = ProgressionFormatter(app, player, gameid)
827
828        kw = self.initKw(kw)
829        MfxDialog.__init__(self, parent, title, kw.resizable, kw.default)
830        top_frame, bottom_frame = self.createFrames(kw)
831        self.createBitmaps(top_frame, kw)
832
833        frame = tkinter.Frame(top_frame)
834        frame.pack(expand=True, fill='both', padx=5, pady=10)
835        frame.columnconfigure(0, weight=1)
836
837        # constants
838        self.canvas_width, self.canvas_height = 600, 250
839        if parent.winfo_screenwidth() < 800 or \
840                parent.winfo_screenheight() < 600:
841            self.canvas_width, self.canvas_height = 400, 200
842        self.xmargin, self.ymargin = 10, 10
843        self.graph_dx, self.graph_dy = 10, 10
844        self.played_color = '#ff7ee9'
845        self.won_color = '#00dc28'
846        self.percent_color = 'blue'
847        # create canvas
848        self.canvas = canvas = tkinter.Canvas(frame, bg='#dfe8ff',
849                                              highlightthickness=1,
850                                              highlightbackground='black',
851                                              width=self.canvas_width,
852                                              height=self.canvas_height)
853        canvas.pack(side='left', padx=5)
854        #
855        dir = os.path.join('images', 'stats')
856        try:
857            fn = app.dataloader.findImage('progression', dir)
858            self.bg_image = loadImage(fn)
859            canvas.create_image(0, 0, image=self.bg_image, anchor='nw')
860        except Exception:
861            pass
862        #
863        tw = max(measure(_('Games/day')),
864                 measure(_('Games/week')),
865                 measure(_('% won')))
866        self.left_margin = self.xmargin+tw//2
867        self.right_margin = self.xmargin+tw//2
868        self.top_margin = 15+self.text_height
869        self.bottom_margin = 15+self.text_height+10+self.text_height
870        #
871        x0, y0 = self.left_margin, self.canvas_height-self.bottom_margin
872        x1, y1 = self.canvas_width-self.right_margin, self.top_margin
873        canvas.create_rectangle(x0, y0, x1, y1, fill='white')
874        # horizontal axis
875        canvas.create_line(x0, y0, x1, y0, width=3)
876
877        # left vertical axis
878        canvas.create_line(x0, y0, x0, y1, width=3)
879        t = _('Games/day')
880        self.games_text_id = canvas.create_text(x0-4, y1-4, anchor='s', text=t)
881
882        # right vertical axis
883        canvas.create_line(x1, y0, x1, y1, width=3)
884        canvas.create_text(x1+4, y1-4, anchor='s', text=_('% won'))
885
886        # caption
887        d = self.text_height
888        x, y = self.xmargin, self.canvas_height-self.ymargin
889        canvas.create_rectangle(x, y, x+d, y-d, outline='black',
890                                fill=self.played_color)
891        x += d+5
892        canvas.create_text(x, y, anchor='sw', text=_('Played'))
893        x += measure(_('Played'))+20
894        canvas.create_rectangle(x, y, x+d, y-d, outline='black',
895                                fill=self.won_color)
896        x += d+5
897        canvas.create_text(x, y, anchor='sw', text=_('Won'))
898        x += measure(_('Won'))+20
899        canvas.create_rectangle(x, y, x+d, y-d, outline='black',
900                                fill=self.percent_color)
901        x += d+5
902        canvas.create_text(x, y, anchor='sw', text=_('% won'))
903
904        # right frame
905        right_frame = tkinter.Frame(frame)
906        right_frame.pack(side='left', fill='x', padx=5)
907        self.all_games_variable = var = tkinter.StringVar()
908        var.set('all')
909        b = tkinter.Radiobutton(right_frame, text=_('All games'),
910                                variable=var, value='all',
911                                command=self.updateGraph,
912                                justify='left', anchor='w'
913                                )
914        b.pack(fill='x', expand=True, padx=3, pady=1)
915        b = tkinter.Radiobutton(right_frame, text=_('Current game'),
916                                variable=var, value='current',
917                                command=self.updateGraph,
918                                justify='left', anchor='w'
919                                )
920        b.pack(fill='x', expand=True, padx=3, pady=1)
921        label_frame = tkinter.LabelFrame(right_frame, text=_('Statistics for'))
922        label_frame.pack(side='top', fill='x', pady=10)
923        self.variable = var = tkinter.StringVar()
924        var.set('week')
925        for v, t in (
926            ('week',  _('Last 7 days')),
927            ('month', _('Last month')),
928            ('year',  _('Last year')),
929            ('all',   _('All time')),
930                ):
931            b = tkinter.Radiobutton(label_frame, text=t, variable=var, value=v,
932                                    command=self.updateGraph,
933                                    justify='left', anchor='w'
934                                    )
935            b.pack(fill='x', expand=True, padx=3, pady=1)
936        label_frame = tkinter.LabelFrame(right_frame, text=_('Show graphs'))
937        label_frame.pack(side='top', fill='x')
938        self.played_graph_var = tkinter.BooleanVar()
939        self.played_graph_var.set(True)
940        b = tkinter.Checkbutton(label_frame, text=_('Played'),
941                                command=self.updateGraph,
942                                variable=self.played_graph_var,
943                                justify='left', anchor='w'
944                                )
945        b.pack(fill='x', expand=True, padx=3, pady=1)
946        self.won_graph_var = tkinter.BooleanVar()
947        self.won_graph_var.set(True)
948        b = tkinter.Checkbutton(label_frame, text=_('Won'),
949                                command=self.updateGraph,
950                                variable=self.won_graph_var,
951                                justify='left', anchor='w'
952                                )
953        b.pack(fill='x', expand=True, padx=3, pady=1)
954        self.percent_graph_var = tkinter.BooleanVar()
955        self.percent_graph_var.set(True)
956        b = tkinter.Checkbutton(label_frame, text=_('% won'),
957                                command=self.updateGraph,
958                                variable=self.percent_graph_var,
959                                justify='left', anchor='w'
960                                )
961        b.pack(fill='x', expand=True, padx=3, pady=1)
962
963        self.updateGraph()
964
965        focus = self.createButtons(bottom_frame, kw)
966        self.mainloop(focus, kw.timeout)
967
968    def initKw(self, kw):
969        kw = KwStruct(kw, strings=(_('&OK'),), default=0, separator=True)
970        return MfxDialog.initKw(self, kw)
971
972    def updateGraph(self, *args):
973        interval = self.variable.get()
974        canvas = self.canvas
975        if self.items:
976            canvas.delete(*self.items)
977        self.items = []
978
979        all_games = (self.all_games_variable.get() == 'all')
980        result = self.formatter.getResults(interval, all_games)
981
982        if interval in ('week', 'month'):
983            t = _('Games/day')
984        else:
985            t = _('Games/week')
986        canvas.itemconfig(self.games_text_id, text=t)
987
988        graph_width = self.canvas_width-self.left_margin-self.right_margin
989        graph_height = self.canvas_height-self.top_margin-self.bottom_margin
990        dx = (graph_width-2*self.graph_dx)//(len(result)-1)
991        graph_dx = (graph_width-(len(result)-1)*dx)//2
992        dy = (graph_height-self.graph_dy)//5
993        x0, y0 = self.left_margin, self.canvas_height-self.bottom_margin
994        x1, y1 = self.canvas_width-self.right_margin, self.top_margin
995        td = self.text_height//2
996
997        # vertical scale
998        x = x0+graph_dx
999        xx = -100
1000        for res in result:
1001            if res[0] is not None and x > xx+self.text_width+4:
1002                # id = canvas.create_line(x, y0, x, y0-5, width=3)
1003                # self.items.append(id)
1004                id = canvas.create_line(x, y0, x, y1, stipple='gray50')
1005                self.items.append(id)
1006                id = canvas.create_text(x, y0+td, anchor='n', text=res[0])
1007                self.items.append(id)
1008                xx = x
1009            else:
1010                id = canvas.create_line(x, y0, x, y0-3, width=1)
1011                self.items.append(id)
1012            x += dx
1013
1014        # horizontal scale
1015        max_games = max([i[1] for i in result])
1016        games_delta = max_games//5+1
1017        percent = 0
1018        games = 0
1019        for y in range(y0, y1, -dy):
1020            if y != y0:
1021                id = canvas.create_line(x0, y, x1, y, stipple='gray50')
1022                self.items.append(id)
1023            id = canvas.create_text(x0-td, y, anchor='e', text=str(games))
1024            self.items.append(id)
1025            id = canvas.create_text(x1+td, y, anchor='w', text=str(percent))
1026            self.items.append(id)
1027            games += games_delta
1028            percent += 20
1029
1030        # draw result
1031        games_resolution = float(dy)/games_delta
1032        percent_resolution = float(dy)/20
1033        played_coords = []
1034        won_coords = []
1035        percent_coords = []
1036        x = x0+graph_dx
1037        for res in result:
1038            played, won = res[1], res[2]
1039            y = y0 - int(games_resolution*played)
1040            played_coords += [x, y]
1041            y = y0 - int(games_resolution*won)
1042            won_coords += [x, y]
1043            if played > 0:
1044                percent = int(100.*won/played)
1045            else:
1046                percent = 0
1047            y = y0 - int(percent_resolution*percent)
1048            percent_coords += [x, y]
1049            x += dx
1050        if self.played_graph_var.get():
1051            id = canvas.create_line(fill=self.played_color, width=3,
1052                                    *played_coords)
1053            self.items.append(id)
1054        if self.won_graph_var.get():
1055            id = canvas.create_line(fill=self.won_color, width=3,
1056                                    *won_coords)
1057            self.items.append(id)
1058        if self.percent_graph_var.get():
1059            id = canvas.create_line(fill=self.percent_color, width=3,
1060                                    *percent_coords)
1061            self.items.append(id)
1062