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