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
24from pysollib.mfxutil import Image, ImageTk
25from pysollib.ui.tktile.Canvas2 import CanvasText, Group, Line, Rectangle
26from pysollib.ui.tktile.Canvas2 import ImageItem as ImageItem2
27from pysollib.ui.tktile.tkutil import loadImage, unbind_destroy
28
29from six.moves import tkinter
30
31
32# ************************************************************************
33# * canvas items
34# ************************************************************************
35
36class MfxCanvasGroup(Group):
37    def __init__(self, canvas, tag=None):
38        Group.__init__(self, canvas=canvas, tag=tag)
39        # register ourself so that we can unbind from the canvas
40        assert self.id not in self.canvas.items
41        self.canvas.items[self.id] = self
42
43    def addtag(self, tag, option="withtag"):
44        self.canvas.addtag(tag, option, self.id)
45
46    def delete(self):
47        del self.canvas.items[self.id]
48        Group.delete(self)
49
50    def gettags(self):
51        return self.canvas.tk.splitlist(self._do("gettags"))
52
53
54class MfxCanvasImage(ImageItem2):
55    def __init__(self, canvas, x, y, **kwargs):
56        self.init_coord = x, y
57        group = None
58        if 'group' in kwargs:
59            group = kwargs['group']
60            del kwargs['group']
61        if 'image' in kwargs:
62            self._image = kwargs['image']
63        ImageItem2.__init__(self, canvas, x, y, **kwargs)
64        if group:
65            self.addtag(group)
66
67    def moveTo(self, x, y):
68        c = self.coords()
69        self.move(x - int(c[0]), y - int(c[1]))
70
71    def show(self):
72        self.config(state='normal')
73
74    def hide(self):
75        self.config(state='hidden')
76
77
78MfxCanvasLine = Line
79
80
81class MfxCanvasRectangle(Rectangle):
82    def __init__(self, canvas, *args, **kwargs):
83        group = None
84        if 'group' in kwargs:
85            group = kwargs['group']
86            del kwargs['group']
87        Rectangle.__init__(self, canvas, *args, **kwargs)
88        if group:
89            self.addtag(group)
90
91
92class MfxCanvasText(CanvasText):
93    def __init__(self, canvas, x, y, preview=-1, **kwargs):
94        self.init_coord = x, y
95        self.x, self.y = x, y
96        if preview < 0:
97            preview = canvas.preview
98        if preview > 1:
99            return
100        if "fill" not in kwargs:
101            kwargs["fill"] = canvas._text_color
102        group = None
103        if 'group' in kwargs:
104            group = kwargs['group']
105            del kwargs['group']
106        CanvasText.__init__(self, canvas, x, y, **kwargs)
107        self.text_format = None
108        canvas._text_items.append(self)
109        if group:
110            self.addtag(group)
111
112    def moveTo(self, x, y):
113        dx, dy = x - self.x, y - self.y
114        self.x, self.y = x, y
115        self.move(dx, dy)
116
117
118# ************************************************************************
119# * canvas
120# ************************************************************************
121
122class MfxCanvas(tkinter.Canvas):
123    def __init__(self, *args, **kw):
124        tkinter.Canvas.__init__(self, *args, **kw)
125        self.preview = 0
126        self.busy = False
127        # this is also used by lib-tk/Canvas.py
128        self.items = {}
129        # private
130        self.__tileimage = None
131        self.__tiles = []           # id of canvas items
132        self.__topimage = None
133        self.__tops = []            # id of canvas items
134        # friend MfxCanvasText
135        self._text_color = "#000000"
136        self._stretch_bg_image = 0
137        self._save_aspect_bg_image = 0
138        self._text_items = []
139        #
140        self.xmargin, self.ymargin = 10, 10
141        # resize bg image
142        self.bind('<Configure>', self.setBackgroundImage)
143
144    def setBackgroundImage(self, event=None):
145        # print 'setBackgroundImage', self._bg_img
146        if not hasattr(self, '_bg_img'):
147            return
148        if not self._bg_img:  # solid color
149            return
150        stretch = self._stretch_bg_image
151        save_aspect = self._save_aspect_bg_image
152        if Image:
153            if stretch:
154                w, h = self._geometry()
155                if save_aspect:
156                    w0, h0 = self._bg_img.size
157                    a = min(float(w0)/w, float(h0)/h)
158                    w0, h0 = int(w0/a), int(h0/a)
159                    im = self._bg_img.resize((w0, h0))
160                else:
161                    im = self._bg_img.resize((w, h))
162                image = ImageTk.PhotoImage(im)
163            else:
164                image = ImageTk.PhotoImage(self._bg_img)
165        else:  # not Image
166            stretch = 0
167            image = self._bg_img
168        for id in self.__tiles:
169            self.delete(id)
170        self.__tiles = []
171        # must keep a reference to the image, otherwise Python will
172        # garbage collect it...
173        self.__tileimage = image
174        if stretch:
175            #
176            if self.preview:
177                dx, dy = 0, 0
178            else:
179                dx, dy = -self.xmargin, -self.ymargin
180            id = self._x_create("image", dx, dy, image=image, anchor="nw")
181            self.tag_lower(id)          # also see tag_lower above
182            self.__tiles.append(id)
183        else:
184            iw, ih = image.width(), image.height()
185            sw, sh = self._geometry()
186            for x in range(-self.xmargin, sw, iw):
187                for y in range(-self.ymargin, sh, ih):
188                    id = self._x_create(
189                        "image", x, y, image=image, anchor="nw")
190                    self.tag_lower(id)          # also see tag_lower above
191                    self.__tiles.append(id)
192        return 1
193
194    def _geometry(self):
195        w = max(self.winfo_width(), int(self.cget('width')))
196        h = max(self.winfo_height(), int(self.cget('height')))
197        scrollregion = self.cget('scrollregion')
198        if not scrollregion:
199            return w, h
200        x, y, sw, sh = [int(i) for i in scrollregion.split()]
201        sw -= x
202        sh -= y
203        w = max(w, sw)
204        h = max(h, sh)
205        return w, h
206
207    #
208    # top-image support
209    #
210
211    def _x_create(self, itemType, *args, **kw):
212        return tkinter.Canvas._create(self, itemType, args, kw)
213
214    def _create(self, itemType, args, kw):
215        # print "_create:", itemType, args, kw
216        id = tkinter.Canvas._create(self, itemType, args, kw)
217        if self.__tops:
218            self.tk.call(self._w, "lower", id, self.__tops[0])
219        return id
220
221    def tag_raise(self, id, aboveThis=None):
222        # print "tag_raise:", id, aboveThis
223        if aboveThis is None and self.__tops:
224            self.tk.call(self._w, "lower", id, self.__tops[0])
225        else:
226            self.tk.call(self._w, "raise", id, aboveThis)
227
228    def tag_lower(self, id, belowThis=None):
229        # print "tag_lower:", id, belowThis
230        if belowThis is None and self.__tiles:
231            self.tk.call(self._w, "raise", id, self.__tiles[-1])
232        else:
233            self.tk.call(self._w, "lower", id, belowThis)
234
235    def setInitialSize(self, width, height, margins=True, scrollregion=True):
236        # print 'Canvas.setInitialSize:', width, height, scrollregion
237        if self.preview:
238            self.config(width=width, height=height,
239                        scrollregion=(0, 0, width, height))
240        else:
241            # add margins
242            dx, dy = self.xmargin, self.ymargin
243            if margins:
244                w, h = dx+width+dx, dy+height+dy
245            else:
246                w, h = width, height
247            self.config(width=w, height=h)
248            if scrollregion:
249                self.config(scrollregion=(-dx, -dy, width+dx, height+dy))
250            else:
251                # no scrolls
252                self.config(scrollregion=(-dx, -dy, dx, dy))
253
254    # delete all CanvasItems, but keep the background and top tiles
255    def deleteAllItems(self):
256        self._text_items = []
257        for id in list(self.items.keys()):
258            assert id not in self.__tiles   # because the tile is created by id
259            unbind_destroy(self.items[id])
260            self.items[id].delete()
261        assert self.items == {}
262
263    def findCard(self, stack, event):
264        if isinstance(stack.cards[0].item, Group):
265            current = self.gettags("current")           # get tags
266            for i in range(len(stack.cards)):
267                if stack.cards[i].item.tag in current:
268                    return i
269        else:
270            #  current = self.find("withtag", "current")   # get item ids
271            #  for i in range(len(stack.cards)):
272            #      if stack.cards[i].item.id in current:
273            #          return i
274            if self.preview:
275                dx, dy = 0, 0
276            else:
277                dx, dy = -self.xmargin, -self.ymargin
278            x = event.x+dx+self.xview()[0]*int(self.cget('width'))
279            y = event.y+dy+self.yview()[0]*int(self.cget('height'))
280            # x, y = event.x, event.y
281            items = list(self.find_overlapping(x, y, x, y))
282            items.reverse()
283            for item in items:
284                for i in range(len(stack.cards)):
285                    if stack.cards[i].item.id == item:
286                        return i
287        return -1
288
289    def setTextColor(self, color):
290        if color is None:
291            c = self.cget("bg")
292            if not isinstance(c, str) or c[0] != "#" or len(c) != 7:
293                return
294            v = []
295            for i in (1, 3, 5):
296                v.append(int(c[i:i+2], 16))
297            luminance = (0.212671 * v[0] + 0.715160 * v[1] + 0.072169 * v[2]) \
298                / 255
299            # print c, ":", v, "luminance", luminance
300            color = ("#000000", "#ffffff")[luminance < 0.3]
301        if self._text_color != color:
302            self._text_color = color
303            for item in self._text_items:
304                item.config(fill=self._text_color)
305
306    def setTile(self, image, stretch=0, save_aspect=0):
307        # print 'setTile:', image, stretch
308        if image:
309            if Image:
310                try:
311                    self._bg_img = Image.open(image)
312                except Exception:
313                    return 0
314            else:
315                try:
316                    self._bg_img = loadImage(file=image, dither=1)
317                except Exception:
318                    return 0
319            self._stretch_bg_image = stretch
320            self._save_aspect_bg_image = save_aspect
321            self.setBackgroundImage()
322        else:
323            for id in self.__tiles:
324                self.delete(id)
325            self.__tiles = []
326            self._bg_img = None
327        return 1
328
329    def setTopImage(self, image, cw=0, ch=0):
330        try:
331            if image and isinstance(image, str):
332                image = loadImage(file=image)
333        except tkinter.TclError:
334            return 0
335        if len(self.__tops) == 1 and image is self.__tops[0]:
336            return 1
337        for id in self.__tops:
338            self.delete(id)
339        self.__tops = []
340        # must keep a reference to the image, otherwise Python will
341        # garbage collect it...
342        self.__topimage = image
343        if image is None:
344            return 1
345        iw, ih = image.width(), image.height()
346        if cw <= 0:
347            # cw = max(int(self.cget("width")), self.winfo_width())
348            cw = self.winfo_width()
349        if ch <= 0:
350            # ch = max(int(self.cget("height")),  self.winfo_height())
351            ch = self.winfo_height()
352        # print iw, ih, cw, ch
353        x = (cw-iw)//2-self.xmargin+self.xview()[0]*int(self.cget('width'))
354        y = (ch-ih)//2-self.ymargin+self.yview()[0]*int(self.cget('height'))
355        id = self._x_create("image", x, y, image=image, anchor="nw")
356        self.tk.call(self._w, "raise", id)
357        self.__tops.append(id)
358        return 1
359
360    #
361    # Pause support
362    #
363
364    def hideAllItems(self):
365        for item in list(self.items.values()):
366            item.config(state='hidden')
367
368    def showAllItems(self):
369        for item in list(self.items.values()):
370            item.config(state='normal')
371
372    #
373    # restricted but fast _bind and _substitute
374    #
375
376    def _bind(self, what, sequence, func, add, needcleanup=1):
377        funcid = self._register(func, self._substitute, needcleanup)
378        cmd = ('%sif {"[%s %s]" == "break"} break\n' %
379               (add and '+' or '', funcid, "%x %y"))
380        self.tk.call(what + (sequence, cmd))
381        return funcid
382
383    def _substitute(self, *args):
384        e = tkinter.Event()
385        try:
386            # Tk changed behavior in 8.4.2, returning "??" rather more often.
387            e.x = int(args[0])
388        except ValueError:
389            e.x = args[0]
390        try:
391            e.y = int(args[1])
392        except ValueError:
393            e.y = args[1]
394        return (e,)
395