diff --git a/AUTHORS b/AUTHORS index 233f27d3..a8310bd8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -22,3 +22,10 @@ Matthew Hohlfeld Nicola Larosa John Stoneham David Svoboda + + +Translations +============ + +Holger Schäkel (de) +Jerzy Trzeciak (pl) diff --git a/pysollib/app.py b/pysollib/app.py index e14e87ae..3b3c255d 100644 --- a/pysollib/app.py +++ b/pysollib/app.py @@ -31,6 +31,7 @@ from mfxutil import destruct, Struct from mfxutil import pickle, unpickle, UnpicklingError from mfxutil import getusername, getprefdir from mfxutil import latin1_to_ascii, print_err +from mfxutil import USE_PIL from util import CARDSET, IMAGE_EXTENSIONS from settings import PACKAGE, VERSION_TUPLE, WIN_SYSTEM from resource import CSI, CardsetConfig, Cardset, CardsetManager @@ -536,11 +537,10 @@ class Application: pass self.wm_save_state() # save game geometry + geom = (self.canvas.winfo_width(), self.canvas.winfo_height()) if self.opt.save_games_geometry and not self.opt.wm_maximized: - w = self.canvas.winfo_width() - h = self.canvas.winfo_height() - geom = (w, h) self.opt.games_geometry[self.game.id] = geom + self.opt.game_geometry = geom self.freeGame() # if self.nextgame.id <= 0: @@ -1020,8 +1020,18 @@ Please select a %s type %s. if d.status != 0 or d.button != 1: return None cs = self.cardset_manager.get(d.key) - if cs is None or d.key == key: + changed = (self.opt.scale_x, + self.opt.scale_y, + self.opt.auto_scale, + self.opt.preserve_aspect_ratio) != d.scale_values + if cs is None: return None + if d.key == key and not changed: + return None + (self.opt.scale_x, + self.opt.scale_y, + self.opt.auto_scale, + self.opt.preserve_aspect_ratio) = d.scale_values return cs diff --git a/pysollib/game.py b/pysollib/game.py index efe15ec8..adf7e38b 100644 --- a/pysollib/game.py +++ b/pysollib/game.py @@ -31,7 +31,7 @@ from cStringIO import StringIO # PySol imports from mfxutil import Pickler, Unpickler, UnpicklingError -from mfxutil import Image, ImageTk +from mfxutil import Image, ImageTk, USE_PIL from mfxutil import destruct, Struct, SubclassResponsibility from mfxutil import uclock, usleep from mfxutil import format_time, print_err @@ -135,12 +135,15 @@ class Game: remaining = [], # list of stacks in no region # data = [], # raw data + init_info = [], # init info (at the start) ) + self.init_size = (0,0) self.event_handled = False # if click event handled by Stack (???) self.reset() # main constructor def create(self, app): + #print 'Game.create' old_busy = self.busy self.__createCommon(app) self.setCursor(cursor=CURSOR_WATCH) @@ -186,13 +189,46 @@ class Game: ##self.top.bind('<3>', self.top._sleepEvent) # update display properties self.canvas.busy = True - self.canvas.setInitialSize(self.width, self.height) - if self.app.opt.save_games_geometry and \ + # geometry + if not USE_PIL and self.app.opt.save_games_geometry and \ self.id in self.app.opt.games_geometry: # restore game geometry w, h = self.app.opt.games_geometry[self.id] self.canvas.config(width=w, height=h) - self.top.wm_geometry("") # cancel user-specified geometry + if USE_PIL: + if self.app.opt.auto_scale: + w, h = self.app.opt.game_geometry + self.canvas.setInitialSize(w, h, margins=False, + scrollregion=False) + ## self.canvas.config(width=w, height=h) + ## dx, dy = self.canvas.xmargin, self.canvas.ymargin + ## self.canvas.config(scrollregion=(-dx, -dy, dx, dy)) + else: + w = int(round(self.width * self.app.opt.scale_x)) + h = int(round(self.height * self.app.opt.scale_y)) + self.canvas.setInitialSize(w, h) + self.top.wm_geometry("") # cancel user-specified geometry + # preserve texts positions + for t in ('info', 'help', 'misc', 'score', 'base_rank'): + item = getattr(self.texts, t) + if item: + coords = self.canvas.coords(item) + setattr(self.init_texts, t, coords) + # + for item in self.texts.list: + coords = self.canvas.coords(item) + self.init_texts.list.append(coords) + # resize + self.resizeGame() + # fix coords of cards (see self.createCards) + x, y = self.s.talon.x, self.s.talon.y + for c in self.cards: + c.moveTo(x, y) + else: + # no PIL + self.canvas.setInitialSize(self.width, self.height) + self.top.wm_geometry("") # cancel user-specified geometry + # done; update view self.top.update_idletasks() self.canvas.busy = False if DEBUG >= 4: @@ -200,12 +236,11 @@ class Game: width=2, fill=None, outline='green') # self.stats.update_time = time.time() - self.busy = old_busy self.showHelp() # just in case hint_class = self.getHintClass() if hint_class is not None: self.Stuck_Class = hint_class(self, 0) - ##self.reallocateStacks() + self.busy = old_busy def _checkGame(self): @@ -262,7 +297,8 @@ class Game: bind(self.canvas, "<2>", self.dropHandler) bind(self.canvas, "<3>", self.redoHandler) bind(self.canvas, '', self._unmapHandler) - bind(self.canvas, '', self.configureHandler, add=True) + bind(self.canvas, '', self._configureHandler, add=True) + def __createCommon(self, app): self.busy = 1 @@ -293,6 +329,16 @@ class Game: misc = None, # score = None, # for displaying the score base_rank = None, # for displaying the base_rank + list = [], # list of texts (if we use many text) + ) + # initial position of the texts + self.init_texts = Struct( + info = None, # misc info text + help = None, # a static help text + misc = None, # + score = None, # for displaying the score + base_rank = None, # for displaying the base_rank + list = [], ) def createPreview(self, app): @@ -409,6 +455,8 @@ class Game: # this is called from within createGame() def setSize(self, w, h): self.width, self.height = int(round(w)), int(round(h)) + dx, dy = self.canvas.xmargin, self.canvas.ymargin + self.init_size = self.width+2*dx, self.height+2*dy def setCursor(self, cursor): if self.canvas: @@ -424,6 +472,7 @@ class Game: # start a new name def newGame(self, random=None, restart=0, autoplay=1): + #print 'Game.newGame' self.finished = False old_busy, self.busy = self.busy, 1 self.setCursor(cursor=CURSOR_WATCH) @@ -538,8 +587,8 @@ class Game: self.stats.update_time = time.time() self.busy = old_busy # - ##self.configureHandler() # reallocateCards - after(self.top, 200, self.configureHandler) # wait for canvas is mapped + ##self._configureHandler() # reallocateCards + after(self.top, 200, self._configureHandler) # wait for canvas is mapped # if TOOLKIT == 'gtk': ## FIXME @@ -561,6 +610,7 @@ class Game: self.busy = old_busy def resetGame(self): + #print 'Game.resetGame' self.hints.list = None self.s.talon.removeAllCards() for stack in self.allstacks: @@ -638,22 +688,73 @@ class Game: self.endGame(restart=1) self.newGame(restart=1, random=self.random) - def reallocateStacks(self): - w0, h0 = self.width, self.height - iw = int(self.canvas.cget('width')) - ih = int(self.canvas.cget('height')) - vw = self.canvas.winfo_width() - vh = self.canvas.winfo_height() - if vw <= iw or vh <= ih: + def resizeImages(self): + # resizing images and cards + if self.app.opt.auto_scale: + if self.canvas.winfo_ismapped(): + # apparent size of canvas + vw = self.canvas.winfo_width() + vh = self.canvas.winfo_height() + else: + # we have no a real size of canvas (winfo_width / winfo_reqwidth) + # so we use a saved size + vw, vh = self.app.opt.game_geometry + if not vw: + # first run of the game + return 1, 1 + iw, ih = self.init_size # requested size of canvas (createGame -> setSize) + # calculate factor of resizing + xf = float(vw)/iw + yf = float(vh)/ih + if self.app.opt.preserve_aspect_ratio: + xf = yf = min(xf, yf) + else: + xf, yf = self.app.opt.scale_x, self.app.opt.scale_y + # images + self.app.images.resize(xf, yf) + # cards + for card in self.cards: + card.update(card.id, card.deck, card.suit, card.rank, self) + return xf, yf + + def resizeGame(self): + #if self.busy: + # return + if not USE_PIL: return - xf = float(vw)/iw - yf = float(vh)/ih + self.deleteStackDesc() + xf, yf = self.resizeImages() + #print 'resizeGame', xf, yf + # stacks for stack in self.allstacks: x0, y0 = stack.init_coord - x, y = int(x0*xf), int(y0*yf) - if x == stack.x and y == stack.y: - continue - stack.moveTo(x, y) + x, y = int(round(x0*xf)), int(round(y0*yf)) + #if x == stack.x and y == stack.y: + # continue + stack.resize(xf, yf) + stack.updatePositions() + # regions + info = [] + for stacks, rect in self.regions.init_info: + rect = (int(round(rect[0]*xf)), int(round(rect[1]*yf)), + int(round(rect[2]*xf)), int(round(rect[3]*yf))) + info.append((stacks, rect)) + self.regions.info = tuple(info) + # texts + for t in ('info', 'help', 'misc', 'score', 'base_rank'): + init_coord = getattr(self.init_texts, t) + if init_coord: + item = getattr(self.texts, t) + x, y = int(round(init_coord[0]*xf)), int(round(init_coord[1]*yf)) + self.canvas.coords(item, x, y) + for i in range(len(self.texts.list)): + init_coord = self.init_texts.list[i] + item = self.texts.list[i] + x, y = int(round(init_coord[0]*xf)), int(round(init_coord[1]*yf)) + self.canvas.coords(item, x, y) + # + #self.canvas.update() + #self.canvas.update_idletasks() def createRandom(self, random): if random is None: @@ -985,11 +1086,27 @@ class Game: if self.app and not self.pause: self.app.menubar.mPause() - def configureHandler(self, event=None): + _resizeHandlerID = None + def _resizeHandler(self): + #print '_resizeHandler' + self._resizeHandlerID = None + self.resizeGame() + + def _configureHandler(self, event=None): + #print 'configureHandler' + if not USE_PIL: + return + if not self.app: + return if not self.canvas: return - for stack in self.allstacks: - stack.updatePositions() + if not self.app.opt.auto_scale: + return + if self.preview: + return + if self._resizeHandlerID: + self.canvas.after_cancel(self._resizeHandlerID) + self._resizeHandlerID = self.canvas.after(250, self._resizeHandler) # # sound support @@ -1504,13 +1621,20 @@ class Game: assert len(stacks) > 0 assert len(rect) == 4 and rect[0] < rect[2] and rect[1] < rect[3] if DEBUG >= 2: - MfxCanvasRectangle(self.canvas, rect[0], rect[1], rect[2], rect[3], + xf, yf = self.app.images._xfactor, self.app.images._yfactor + MfxCanvasRectangle(self.canvas, + xf*rect[0], yf*rect[1], xf*rect[2], yf*rect[3], width=2, fill=None, outline='red') for s in stacks: assert s and s in self.allstacks # verify that the stack lies within the rectangle - x, y, r = s.x, s.y, rect - ##print x, y, r + r = rect + if USE_PIL: + x, y = s.init_coord + #print 'setRegion:', x, y, r + else: + x, y = s.x, s.y + #print 'setRegion:', x, y, r assert r[0] <= x <= r[2] and r[1] <= y <= r[3] # verify that the stack is not already in another region # with the same priority @@ -1538,7 +1662,7 @@ class Game: while stack in remaining: remaining.remove(stack) self.regions.remaining = tuple(remaining) - ##print self.regions.info + self.regions.init_info = self.regions.info def getInvisibleCoords(self): # for InvisibleStack, etc @@ -1917,6 +2041,7 @@ Congratulations, you did it ! if self.pause: return 0 self.stopWinAnimation() + cw, ch = self.app.images.getSize() items = [] for s, c1, c2, color in info: assert c1 in s.cards and c2 in s.cards @@ -1943,33 +2068,33 @@ Congratulations, you did it ! x2, y2 = s.getPositionFor(c2) if sx0 != 0 and sy0 == 0: # horizontal stack - y2 = y2 + self.app.images.CARDH + y2 += ch if c2 is s.cards[-1]: # top card - x2 = x2 + self.app.images.CARDW + x2 += cw else: if sx0 > 0: # left to right - x2 = x2 + sx0 + x2 += sx0 else: # right to left - x1 = x1 + self.app.images.CARDW - x2 = x2 + self.app.images.CARDW + sx0 + x1 += cw + x2 += cw + sx0 elif sx0 == 0 and sy0 != 0: # vertical stack - x2 = x2 + self.app.images.CARDW + x2 += cw if c2 is s.cards[-1]: # top card - y2 = y2 + self.app.images.CARDH + y2 += ch else: if sy0 > 0: # up to down y2 = y2 + sy0 else: # down to up - y1 = y1 + self.app.images.CARDH - y2 = y2 + self.app.images.CARDH + sy0 + y1 += ch + y2 += ch + sy0 else: - x2 = x2 + self.app.images.CARDW - y2 = y2 + self.app.images.CARDH + x2 += cw + y2 += ch tkraise = True ##print c1, c2, x1, y1, x2, y2 x1, x2 = x1-delta[0], x2+delta[1] @@ -2237,19 +2362,21 @@ Congratulations, you did it ! images = self.app.images x1, y1 = from_stack.getPositionFor(from_stack.cards[-ncards]) x2, y2 = to_stack.getPositionFor(to_stack.getCard()) - x1, y1 = x1 + images.CARD_DX, y1 + images.CARD_DY - x2, y2 = x2 + images.CARD_DX, y2 + images.CARD_DY + cw, ch = images.getSize() + dx, dy = images.getDelta() + x1, y1 = x1 + dx, y1 + dy + x2, y2 = x2 + dx, y2 + dy if ncards == 1: - x1 = x1 + images.CARDW / 2 - y1 = y1 + images.CARDH / 2 + x1 += cw / 2 + y1 += ch / 2 elif from_stack.CARD_XOFFSET[0]: - x1 = x1 + from_stack.CARD_XOFFSET[0] / 2 - y1 = y1 + images.CARDH / 2 + x1 += from_stack.CARD_XOFFSET[0] / 2 + y1 += ch / 2 else: - x1 = x1 + images.CARDW / 2 - y1 = y1 + from_stack.CARD_YOFFSET[0] / 2 - x2 = x2 + images.CARDW / 2 - y2 = y2 + images.CARDH / 2 + x1 += cw / 2 + y1 += from_stack.CARD_YOFFSET[0] / 2 + x2 += cw / 2 + y2 += ch / 2 # draw the hint arrow = MfxCanvasLine(self.canvas, x1, y1, x2, y2, width=7, fill=self.app.opt.colors['hintarrow'], diff --git a/pysollib/games/auldlangsyne.py b/pysollib/games/auldlangsyne.py index 98c2dae1..208d8e85 100644 --- a/pysollib/games/auldlangsyne.py +++ b/pysollib/games/auldlangsyne.py @@ -32,7 +32,6 @@ from pysollib.stack import * from pysollib.game import Game from pysollib.layout import Layout from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint -from pysollib.pysoltk import MfxCanvasText from numerica import Numerica_Hint diff --git a/pysollib/games/curdsandwhey.py b/pysollib/games/curdsandwhey.py index e9662ab4..ddf828d0 100644 --- a/pysollib/games/curdsandwhey.py +++ b/pysollib/games/curdsandwhey.py @@ -34,7 +34,6 @@ from pysollib.stack import * from pysollib.game import Game from pysollib.layout import Layout from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint -from pysollib.pysoltk import MfxCanvasText # ************************************************************************ diff --git a/pysollib/games/diplomat.py b/pysollib/games/diplomat.py index 76f3692b..9261d7f1 100644 --- a/pysollib/games/diplomat.py +++ b/pysollib/games/diplomat.py @@ -33,7 +33,6 @@ from pysollib.stack import * from pysollib.game import Game from pysollib.layout import Layout from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint -from pysollib.pysoltk import MfxCanvasText from fortythieves import FortyThieves_Hint from spider import Spider_Hint diff --git a/pysollib/games/fortythieves.py b/pysollib/games/fortythieves.py index 3d33b411..338d8926 100644 --- a/pysollib/games/fortythieves.py +++ b/pysollib/games/fortythieves.py @@ -32,7 +32,6 @@ from pysollib.stack import * from pysollib.game import Game from pysollib.layout import Layout from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint -from pysollib.pysoltk import MfxCanvasText from gypsy import DieRussische_Foundation diff --git a/pysollib/games/grandduchess.py b/pysollib/games/grandduchess.py index 9b983b55..6873c3db 100644 --- a/pysollib/games/grandduchess.py +++ b/pysollib/games/grandduchess.py @@ -34,7 +34,6 @@ from pysollib.stack import * from pysollib.game import Game from pysollib.layout import Layout from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint -from pysollib.pysoltk import MfxCanvasText # ************************************************************************ diff --git a/pysollib/games/gypsy.py b/pysollib/games/gypsy.py index ec9a1707..72bd52c1 100644 --- a/pysollib/games/gypsy.py +++ b/pysollib/games/gypsy.py @@ -35,7 +35,6 @@ from pysollib.game import Game from pysollib.layout import Layout from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint from pysollib.hint import KlondikeType_Hint, YukonType_Hint -from pysollib.pysoltk import MfxCanvasText from spider import Spider_SS_Foundation, Spider_RowStack, Spider_Hint diff --git a/pysollib/games/harp.py b/pysollib/games/harp.py index dbefad5f..a67cc52f 100644 --- a/pysollib/games/harp.py +++ b/pysollib/games/harp.py @@ -35,7 +35,6 @@ from pysollib.game import Game from pysollib.layout import Layout from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint from pysollib.hint import KlondikeType_Hint -from pysollib.pysoltk import MfxCanvasText from spider import Spider_RowStack, Spider_SS_Foundation, Spider_Hint diff --git a/pysollib/games/larasgame.py b/pysollib/games/larasgame.py index 9f28f2fe..274dc215 100644 --- a/pysollib/games/larasgame.py +++ b/pysollib/games/larasgame.py @@ -32,7 +32,6 @@ from pysollib.stack import * from pysollib.game import Game from pysollib.layout import Layout from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint -from pysollib.pysoltk import MfxCanvasText # ************************************************************************ diff --git a/pysollib/games/mahjongg/mahjongg.py b/pysollib/games/mahjongg/mahjongg.py index 69132581..d1b36930 100644 --- a/pysollib/games/mahjongg/mahjongg.py +++ b/pysollib/games/mahjongg/mahjongg.py @@ -276,7 +276,7 @@ class Mahjongg_RowStack(OpenStack): drag.shade_img.delete() #game.canvas.delete(drag.shade_img) drag.shade_img = None - img = game.app.images.getShadowCard(card.deck, card.suit, card.rank) + img = game.app.images.getHighlightedCard(card.deck, card.suit, card.rank) if img is None: return 1 img = MfxCanvasImage(game.canvas, self.x, self.y, image=img, @@ -896,7 +896,7 @@ a solvable configuration.'''), assert c1 in s.cards tkraise = False x, y = s.x, s.y - img = self.app.images.getHighlightCard(c1.deck, c1.suit, c1.rank) + img = self.app.images.getHighlightedCard(c1.deck, c1.suit, c1.rank, 'black') if img is None: continue img = MfxCanvasImage(self.canvas, x, y, image=img, diff --git a/pysollib/games/mahjongg/mahjongg1.py b/pysollib/games/mahjongg/mahjongg1.py index c4f26ba7..f60e68d3 100644 --- a/pysollib/games/mahjongg/mahjongg1.py +++ b/pysollib/games/mahjongg/mahjongg1.py @@ -123,7 +123,7 @@ r(5078, "Squaring", layout="0caaacaceaciaakacmacqaasacuacyaaAacCaaacaecaicdkcamc r(5079, "Stairs", layout="0aoaaebaybeacdccagcaicakcbmccocbqcascaucawcdAceCcaedayddaeaieaoeauedCebefbyfaagaigaogaugaCgbchcehbghakhbmhbqhashbwhcyhbAhaaiaiiaoiauiaCibejbyjdakaikaokaukdCkaelayleamdcmagmaimakmbmmcombqmasmaumawmdAmeCmaenaynaoohechychofhahkohhChhojhemhym") r(5080, "Star Ship", layout="0eoaaabdmbdqbaCbaccckccscaAcaadbidbudaCdbceagecoeawebAeaafaefamfaqfayfaCfecgaggaigbkgdogbsgaugawgeAgaahaehamhaqhayhaChbciagicoiawibAiaajbijbujaCjackckkcskaAkaaldmldqlaCleomhachCchaehCehaghegimgiqghyghCghaihCihakhCkoadoCdoafoCfoahoChoajoCjvaevCevagvCgvaivCiCafCCfCahCCh") # -r(5081, "Step Pyramid", layout="0aaaacaaeaagaaiaakaamaaoaaqaaacaccaecagcaicakcamcaocaqcaaeaceaoeaqeaagacgaogaqgaaiaciaoiaqiaakackaekagkaikakkamkaokaqkaamacmaemagmaimakmammaomaqmhbbhdbhfbhhbhjbhlbhnbhpbhbdhddhfdhhdhjdhldhndhpdhbfhdfhnfhpfhbhhdhhnhhphhbjhdjhfjhhjhjjhljhnjhpjhblhdlhflhhlhjlhllhnlhplpccoecogcoicokcomcpococepeepgepiepkepmeooeocgpegpmgoogocipeipgipiipkipmiooipckoekogkoikokkomkpokCffChfCjfClfCfhChhCjhClh") +r(5081, "Steps Pyramid", layout="0aaaacaaeaagaaiaakaamaaoaaqaaacaccaecagcaicakcamcaocaqcaaeaceaoeaqeaagacgaogaqgaaiaciaoiaqiaakackaekagkaikakkamkaokaqkaamacmaemagmaimakmammaomaqmhbbhdbhfbhhbhjbhlbhnbhpbhbdhddhfdhhdhjdhldhndhpdhbfhdfhnfhpfhbhhdhhnhhphhbjhdjhfjhhjhjjhljhnjhpjhblhdlhflhhlhjlhllhnlhplpccoecogcoicokcomcpococepeepgepiepkepmeooeocgpegpmgoogocipeipgipiipkipmiooipckoekogkoikokkomkpokCffChfCjfClfCfhChhCjhClh") r(5082, "Stonehenge", layout="0cdachackacoacracvacyacCacaccFcajeaneareavecagcFgddhdhhdlhdphdthdxhdBhcajcFjajkankarkavkcancFncdpchpckpcopcrpcvpcypcCpveavgavlavnavsavuavzavBavadvFdvafvFfvakvFkvamvFmvepvgpvlpvnpvspvupvzpvBpCehCghCihCkhCmhCohCqhCshCuhCwhCyhCAh") r(5083, "SunMoon", layout="0dgaciabkaamabyadebbrbbBbdccbvccaddcecheckecnebDecafbtfbAfdcgdjgdlgbxgcahchhcnhdcidjidlibribDicajbvjdckchkckkcnkbAkcalbsldcmbxmdenbBndgociobkoamobuovaevagvaivakCkh") r(5084, "Temple", layout="0baaacaaeaalaanaapaaraataaAaaCabEaaacaccalcbncbpcbrcatcaCcaEcajdavdaaeblebnebpebrebteaEeaffahfajfavfaxfazfblgbngbpgbrgbtgadhafhahhajhavhaxhazhaBhblibnibpibribtiafjahjajjavjaxjazjaakblkbnkbpkbrkbtkaEkajlavlaamacmalmbnmbpmbrmatmaCmaEmbaoacoaeoaloanoapoaroatoaAoaCobEohhghjghvghxghhihjihvihxiooeoqeokgomgoogoqgosgougokiomiooioqiosiouiookoqkvpgvpi") diff --git a/pysollib/games/mahjongg/shisensho.py b/pysollib/games/mahjongg/shisensho.py index c34b0c76..71804a32 100644 --- a/pysollib/games/mahjongg/shisensho.py +++ b/pysollib/games/mahjongg/shisensho.py @@ -250,33 +250,31 @@ class Shisen_RowStack(Mahjongg_RowStack): def drawArrow(self, other_stack, sleep): game = self.game + images = game.app.images + cs = game.app.cardset path = self.acceptsCards(other_stack, [other_stack.cards[-1]]) #print path x0, y0 = game.XMARGIN, game.YMARGIN - #print x0, y0 - images = game.app.images - cs = game.app.cardset + cardw, cardh = images.CARDW, images.CARDH if cs.version >= 6: - cardw = images.CARDW-cs.SHADOW_XOFFSET - cardh = images.CARDH-cs.SHADOW_YOFFSET - else: - cardw = images.CARDW - cardh = images.CARDH + cardw -= cs.SHADOW_XOFFSET + cardh -= cs.SHADOW_YOFFSET coords = [] dx, dy = game._delta_x, game._delta_y + xf, yf = images._xfactor, images._yfactor for x, y in path: if x == 0: coords.append(6) elif x == game.L[0]+1: - coords.append(x0+cardw*(x-1)+10+dx) + coords.append(int(round(xf * (x0+cardw*(x-1)+10+dx)))) else: - coords.append(x0+cardw/2+cardw*(x-1)+dx) + coords.append(int(round(xf * (x0+cardw/2+cardw*(x-1)+dx)))) if y == 0: coords.append(6) elif y == game.L[1]+1: - coords.append(y0+cardh*(y-1)+6) + coords.append(int(round(yf * (y0+cardh*(y-1)+6)))) else: - coords.append(y0+cardh/2+cardh*(y-1)) + coords.append(int(round(yf * (y0+cardh/2+cardh*(y-1))))) #print coords ##s1 = min(cardw/2, cardh/2, 30) ##w = min(s1/3, 7) diff --git a/pysollib/games/pasdedeux.py b/pysollib/games/pasdedeux.py index 212fc864..47728922 100644 --- a/pysollib/games/pasdedeux.py +++ b/pysollib/games/pasdedeux.py @@ -33,7 +33,6 @@ from pysollib.stack import * from pysollib.game import Game from pysollib.layout import Layout from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint -from pysollib.pysoltk import MfxCanvasText # ************************************************************************ diff --git a/pysollib/games/pushpin.py b/pysollib/games/pushpin.py index ccb0f994..e184af47 100644 --- a/pysollib/games/pushpin.py +++ b/pysollib/games/pushpin.py @@ -64,7 +64,7 @@ class PushPin_Talon(DealRowTalonStack): if not r.cards: return self.dealRowAvail(rows=[r], sound=sound) return self.dealRowAvail(rows=[self.game.s.rows[0]], sound=sound) - getBottomImage = Stack._getBlankBottomImage + getBottomImage = Stack._getNoneBottomImage class PushPin_RowStack(ReserveStack): diff --git a/pysollib/games/royalcotillion.py b/pysollib/games/royalcotillion.py index 244a4a6c..8f68c72e 100644 --- a/pysollib/games/royalcotillion.py +++ b/pysollib/games/royalcotillion.py @@ -33,7 +33,6 @@ from pysollib.stack import * from pysollib.game import Game from pysollib.layout import Layout from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint -from pysollib.pysoltk import MfxCanvasText from unionsquare import UnionSquare_Foundation diff --git a/pysollib/games/special/poker.py b/pysollib/games/special/poker.py index c7c2df4c..08e11fed 100644 --- a/pysollib/games/special/poker.py +++ b/pysollib/games/special/poker.py @@ -81,6 +81,7 @@ Straight Three of a Kind Two Pair One Pair''')) + self.texts.list.append(t) bb = t.bbox() x = bb[1][0] + 16 h = bb[1][1] - bb[0][1] @@ -91,6 +92,7 @@ One Pair''')) t = MfxCanvasText(self.canvas, x, y, anchor="nw", font=self.app.getFont("canvas_default"), text="100\n75\n50\n25\n20\n15\n10\n5\n2") + self.texts.list.append(t) x = t.bbox()[1][0] + 16 self.texts.misc = MfxCanvasText(self.canvas, x, y, anchor="nw", font=self.app.getFont("canvas_default"), @@ -113,18 +115,17 @@ One Pair''')) # create texts 2) if self.preview <= 1: - self.texts.addattr(hands=[]) for i in (4, 9, 14, 19, 24): tx, ty, ta, tf = l.getTextAttr(s.rows[i], anchor="e") t = MfxCanvasText(self.canvas, tx+8, ty, anchor=ta, font=self.app.getFont("canvas_default")) - self.texts.hands.append(t) + self.texts.list.append(t) for i in range(20, 25): tx, ty, ta, tf = l.getTextAttr(s.rows[i], anchor="ss") t = MfxCanvasText(self.canvas, tx, ty, anchor=ta, font=self.app.getFont("canvas_default")) - self.texts.hands.append(t) + self.texts.list.append(t) self.texts.score = MfxCanvasText(self.canvas, l.XM, 5*l.YS, anchor="sw", font=self.app.getFont("canvas_large")) @@ -171,7 +172,7 @@ One Pair''')) type, value = self.getHandScore(self.poker_hands[i]) if 0 <= type <= 8: count[type] = count[type] + 1 - self.texts.hands[i].config(text=str(value)) + self.texts.list[i+2].config(text=str(value)) score = score + value t = '\n'.join(map(str, count)) self.texts.misc.config(text=t) diff --git a/pysollib/games/sthelena.py b/pysollib/games/sthelena.py index 8b38b477..54229c51 100644 --- a/pysollib/games/sthelena.py +++ b/pysollib/games/sthelena.py @@ -34,7 +34,6 @@ from pysollib.stack import * from pysollib.game import Game from pysollib.layout import Layout from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint -from pysollib.pysoltk import MfxCanvasText # ************************************************************************ diff --git a/pysollib/games/sultan.py b/pysollib/games/sultan.py index 2f3e4c2d..dc81093a 100644 --- a/pysollib/games/sultan.py +++ b/pysollib/games/sultan.py @@ -34,7 +34,7 @@ from pysollib.stack import * from pysollib.game import Game from pysollib.layout import Layout from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint -from pysollib.pysoltk import MfxCanvasText + # ************************************************************************ # * Sultan diff --git a/pysollib/games/tournament.py b/pysollib/games/tournament.py index fd2246c2..c474fe18 100644 --- a/pysollib/games/tournament.py +++ b/pysollib/games/tournament.py @@ -34,7 +34,6 @@ from pysollib.stack import * from pysollib.game import Game from pysollib.layout import Layout from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint -from pysollib.pysoltk import MfxCanvasText # ************************************************************************ diff --git a/pysollib/games/ultra/hanafuda.py b/pysollib/games/ultra/hanafuda.py index 7c6095ef..1d64d4e9 100644 --- a/pysollib/games/ultra/hanafuda.py +++ b/pysollib/games/ultra/hanafuda.py @@ -64,10 +64,12 @@ class FlowerClock(AbstractFlowerGame): for i in range(12): x0 = x + xoffset[i] * l.XS y0 = y + yoffset[i] * l.YS - s.foundations.append(FlowerClock_Foundation(x0, y0, self, ANY_SUIT, base_rank=3)) + stack = FlowerClock_Foundation(x0, y0, self, ANY_SUIT, base_rank=3) + s.foundations.append(stack) t = MfxCanvasText(self.canvas, x0 + l.CW / 2, y0 + l.YS, anchor="center", font=font, text=self.SUITS[i]) + stack.texts.misc = t # Create row stacks for j in range(2): @@ -311,15 +313,15 @@ class Pagoda(AbstractFlowerGame): self.setSize(l.XM + l.XS * 11, l.YS * 6) # Create foundations - self.foundation_texts = [] id = 0 for j in range(4): x, y = l.XM + l.XS * 8, l.YM * 3 + l.YS * j * 1.5 for i in range(3): - s.foundations.append(Pagoda_Foundation(x, y, self, id)) + stack = Pagoda_Foundation(x, y, self, id) + s.foundations.append(stack) t = MfxCanvasText(self.canvas, x + l.CW / 2, y - 12, anchor="center", font=font) - self.foundation_texts.append(t) + stack.texts.misc = t x = x + l.XS id = id + 1 @@ -370,7 +372,7 @@ class Pagoda(AbstractFlowerGame): text = _("Rising") else: text = _("Setting") - self.foundation_texts[i].config(text=text) + s.texts.misc.config(text=text) # # Game over rides @@ -468,13 +470,14 @@ class GreatWall(AbstractFlowerGame): self.setSize(w, h) # Create foundations - self.foundation_texts = [] x, y = (l.XM, l.XM, w - l.XS, w - l.XS), (l.YM, h / 2, l.YM, h / 2) for i in range(4): - s.foundations.append(GreatWall_FoundationStack(x[i], y[i] + l.YM, self, -1, base_rank=i)) - self.foundation_texts.append(MfxCanvasText(self.canvas, - x[i] + l.CW / 2, y[i], - anchor="center", font=font)) + stack = GreatWall_FoundationStack(x[i], y[i] + l.YM, self, -1, + base_rank=i) + s.foundations.append(stack) + stack.texts.misc = MfxCanvasText(self.canvas, + x[i] + l.CW / 2, y[i], + anchor="center", font=font) # Create row stacks x = l.XM + l.XS * 1.5 @@ -501,14 +504,15 @@ class GreatWall(AbstractFlowerGame): if self.preview > 1: return for i in range(4): - l = len(self.s.foundations[i].cards) / 12 + stack = self.s.foundations[i] + l = len(stack.cards) / 12 if l == 0: - t = "" + text = "" elif l == 4: - t = _("Filled") + text = _("Filled") else: - t = str(l) + (_("st"), _("nd"), _("rd"), _("th"))[l - 1] + _(" Deck") - self.foundation_texts[i].config(text=str(t)) + text = str(l) + (_("st"), _("nd"), _("rd"), _("th"))[l - 1] + _(" Deck") + stack.texts.misc.config(text=text) # # Game over rides @@ -568,11 +572,13 @@ class FourWinds(AbstractFlowerGame): for i in range(4): x0 = x + (xoffset[i] * l.XS) y0 = y + (yoffset[i] * l.YS) - s.foundations.append(FourWinds_Foundation(x0, y0, self, -1, - max_cards=12, max_accept=1, base_rank=i)) + stack = FourWinds_Foundation(x0, y0, self, -1, + max_cards=12, max_accept=1, base_rank=i) + s.foundations.append(stack) t = MfxCanvasText(self.canvas, x0 + l.CW / 2, y0 + l.YS + 5, anchor="center", font=font, text=TEXTS[i]) + stack.texts.misc = t # Create rows xoffset = (1.25, 3.75, 3.75, 1.25) @@ -580,11 +586,14 @@ class FourWinds(AbstractFlowerGame): for i in range(4): x0 = x + (xoffset[i] * l.XS) y0 = y + (yoffset[i] * l.YS) - s.rows.append(FourWinds_RowStack(x0, y0, self, yoffset=10, - max_cards=3, max_accept=1, base_rank=ANY_RANK)) + stack = FourWinds_RowStack(x0, y0, self, yoffset=10, + max_cards=3, max_accept=1, + base_rank=ANY_RANK) + s.rows.append(stack) t = MfxCanvasText(self.canvas, x0 + l.CW / 2, y0 + l.YS + 5, anchor="center", font=font, text=TEXTS[i+4]) + stack.texts.misc = t self.setRegion(s.rows, (x + l.XS, y + l.YS * 0.65, x + l.XS * 4 + 5, y + l.YS * 3 + 5)) # Create talon diff --git a/pysollib/games/ultra/hexadeck.py b/pysollib/games/ultra/hexadeck.py index 50e150ce..e635b3c3 100644 --- a/pysollib/games/ultra/hexadeck.py +++ b/pysollib/games/ultra/hexadeck.py @@ -297,10 +297,12 @@ class BitsNBytes(Game): for j in range(4): x = l.XM * 4 + l.XS * 7 for i in range(4): - s.rows.append(Bits_RowStack(x, y, self, max_cards=1, - max_accept=1, base_suit=j, max_move=0)) - self.bit_texts.append(MfxCanvasText(self.canvas, x + l.CW / 2 , y + l.CH / 2, - anchor="center", font=font)) + stack = Bits_RowStack(x, y, self, max_cards=1, max_accept=1, + base_suit=j, max_move=0) + s.rows.append(stack) + stack.texts.misc = MfxCanvasText(self.canvas, + x + l.CW / 2 , y + l.CH / 2, + anchor="center", font=font) x = x - l.XS y = y + l.YS @@ -347,7 +349,8 @@ class BitsNBytes(Game): break s = self.s.foundations[j].cards[-1].rank + 1 for i in range(4): - self.bit_texts[i + j * 4].config(text = str(s % 2)) + stack = self.s.rows[i + j * 4] + stack.texts.misc.config(text = str(s % 2)) s = int(s / 2) def _shuffleHook(self, cards): diff --git a/pysollib/games/zodiac.py b/pysollib/games/zodiac.py index e71ef25e..903a3f5b 100644 --- a/pysollib/games/zodiac.py +++ b/pysollib/games/zodiac.py @@ -34,8 +34,6 @@ from pysollib.stack import * from pysollib.game import Game from pysollib.layout import Layout from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint -from pysollib.pysoltk import MfxCanvasText - # ************************************************************************ diff --git a/pysollib/images.py b/pysollib/images.py index 4377ac32..2f7246c0 100644 --- a/pysollib/images.py +++ b/pysollib/images.py @@ -29,10 +29,10 @@ import traceback # PySol imports from resource import CSI from settings import TOOLKIT -from mfxutil import Image, ImageTk +from mfxutil import Image, ImageTk, USE_PIL # Toolkit imports -from pysoltk import loadImage, copyImage, createImage, shadowImage, createBottom +from pysoltk import loadImage, copyImage, createImage, shadowImage, createBottom, resizeBottom # ************************************************************************ @@ -54,37 +54,26 @@ class Images: self.d = dataloader self.cs = cs self.reduced = r + self._xfactor = 1.0 + self._yfactor = 1.0 if cs is None: return - self.use_pil = False - if TOOLKIT == 'tk' and Image and Image.VERSION >= '1.1.7': - self.use_pil = True - # copy from cardset - self.CARDW, self.CARDH, self.CARDD = cs.CARDW/r, cs.CARDH/r, cs.CARDD/r - self.CARD_XOFFSET = cs.CARD_XOFFSET - self.CARD_YOFFSET = cs.CARD_YOFFSET - if r > 1: - self.CARD_XOFFSET = max(10, cs.CARD_XOFFSET)/r - self.CARD_YOFFSET = max(10, cs.CARD_YOFFSET)/r - self.SHADOW_XOFFSET, self.SHADOW_YOFFSET = cs.SHADOW_XOFFSET/r, cs.SHADOW_YOFFSET/r - self.CARD_DX, self.CARD_DY = cs.CARD_DX/r, cs.CARD_DY/r - # other - self._shade_index = 0 + self._setSize() self._card = [] self._back = [] - self._bottom = [] - self._bottom_negative = [] - self._bottom_positive = [] - self._blank_bottom = None - self._letter = [] + self._bottom = [] # bottom of stack (link to _bottom_negative/_bottom_positive) + self._bottom_negative = [] # negative bottom of stack (white) + self._bottom_positive = [] # positive bottom of stack (black) + self._blank_bottom = None # blank (transparent) bottom of stack + self._letter = [] # images of letter self._letter_negative = [] self._letter_positive = [] - self._shadow = [] - self._xshadow = [] - self._shade = [] - self._shadow_cards = {} # key: (suit, rank) + self._shadow = [] # vertical shadow of card (used when we drag a card) + self._xshadow = [] # horizontal shadow of card self._pil_shadow = {} # key: (width, height) - self._pil_shadow_image = None + self._highlight = [] # highlight of card (tip) + self._highlight_index = 0 # + self._highlighted_images = {} # key: (suit, rank) def destruct(self): pass @@ -115,9 +104,13 @@ class Images: imagedir = self.d.findDir(cs_type, d) except: pass - if not self.use_pil or imagedir is None: + if not USE_PIL or imagedir is None: # load image - return self.__loadCard(filename+self.cs.ext, check_w, check_h) + img = self.__loadCard(filename+self.cs.ext, check_w, check_h) + if USE_PIL: + # we have no bottom images (data/images/cards/bottoms/) + img = img.resize(self._xfactor, self._yfactor) + return img # create image d = os.path.join('images', 'cards', 'bottoms', cs_type) try: @@ -134,9 +127,10 @@ class Images: self._back.append(ImagesCardback(len(self._back), name, im1, im2)) def _createMissingImages(self): + cw, ch = self.getSize() # back if not self._back: - im = createImage(self.CARDW, self.CARDH, fill="#a0a0a0", outline="#000000") + im = createImage(cw, ch, fill="#a0a0a0", outline="#000000") name = "" self.__addBack(im, name) self.cs.backnames = tuple(self.cs.backnames) + (name,) @@ -145,21 +139,21 @@ class Images: neg_bottom = None while len(self._bottom_positive) < 7: if bottom is None: - bottom = createImage(self.CARDW, self.CARDH, fill=None, outline="#000000") + bottom = createImage(cw, ch, fill=None, outline="#000000") self._bottom_positive.append(bottom) while len(self._bottom_negative) < 7: if neg_bottom is None: - neg_bottom = createImage(self.CARDW, self.CARDH, fill=None, outline="#ffffff") + neg_bottom = createImage(cw, ch, fill=None, outline="#ffffff") self._bottom_negative.append(neg_bottom) while len(self._letter_positive) < 4: if bottom is None: - bottom = createImage(self.CARDW, self.CARDH, fill=None, outline="#000000") + bottom = createImage(cw, ch, fill=None, outline="#000000") self._letter_positive.append(bottom) while len(self._letter_negative) < 4: if neg_bottom is None: - neg_bottom = createImage(self.CARDW, self.CARDH, fill=None, outline="#ffffff") + neg_bottom = createImage(cw, ch, fill=None, outline="#ffffff") self._letter_negative.append(neg_bottom) - self._blank_bottom = createImage(self.CARDW, self.CARDH, fill=None, outline=None) + self._blank_bottom = createImage(cw, ch, fill=None, outline=None) def load(self, app, progress=None): ext = self.cs.ext[1:] @@ -199,7 +193,7 @@ class Images: self._letter_negative.append(self.__loadBottom(name, color='white')) if progress: progress.update(step=pstep) # shadow - if not self.use_pil: + if not USE_PIL: for i in range(self.cs.nshadows): name = "shadow%02d.%s" % (i, ext) im = self.__loadCard(name, check_w=0, check_h=0) @@ -210,10 +204,10 @@ class Images: self._xshadow.append(im) if progress: progress.update(step=pstep) # shade - if self.use_pil: - self._shade.append(self._getShadow(self._card[0], None, '#3896f8')) + if USE_PIL: + self._highlight.append(self._getHighlight(self._card[0], None, '#3896f8')) else: - self._shade.append(self.__loadCard("shade." + ext)) + self._highlight.append(self.__loadCard("shade." + ext)) if progress: progress.update(step=pstep) # create missing self._createMissingImages() @@ -278,8 +272,9 @@ class Images: x1, y1 = stack.getPositionFor(cards[-1]) x0, x1 = min(x1, x0), max(x1, x0) y0, y1 = min(y1, y0), max(y1, y0) - x1 += self.CARDW - y1 += self.CARDH + cw, ch = self.getSize() + x1 += cw + y1 += ch w, h = x1-x0, y1-y0 if (w,h) in self._pil_shadow: return self._pil_shadow[(w,h)] @@ -298,43 +293,43 @@ class Images: mask = mask.crop((sx,sy,w,h)) tmp = Image.new('RGBA', (w-sx,h-sy)) shadow.paste(tmp, (0,0), mask) - # shadow = ImageTk.PhotoImage(shadow) self._pil_shadow[(w,h)] = shadow return shadow def getShade(self): - return self._shade[self._shade_index] + # highlight + return self._highlight[self._highlight_index] - def _getShadow(self, image, card, color='#3896f8', factor=0.3): - if self.use_pil: - # use alpha image; one for each color - if color in self._shadow_cards: - shade = self._shadow_cards[color] + def _getHighlight(self, image, card, color='#3896f8', factor=0.3): + if USE_PIL: + # use semitransparent image; one for each color (PIL >= 1.1.7) + if color in self._highlighted_images: + shade = self._highlighted_images[color] else: shade = shadowImage(image, color, factor) - self._shadow_cards[color] = shade + self._highlighted_images[color] = shade else: - if card in self._shadow_cards: - shade = self._shadow_cards[card] + # use alpha blending (PIL <= 1.1.6) + if card in self._highlighted_images: + shade = self._highlighted_images[card] else: shade = shadowImage(image, color, factor) - self._shadow_cards[card] = shade + self._highlighted_images[card] = shade if not shade: + # we have not PIL return self.getShade() return shade - def getShadowCard(self, deck, suit, rank): + def getHighlightedCard(self, deck, suit, rank, color=None): image = self.getFace(deck, suit, rank) - return self._getShadow(image, (suit, rank)) + if color: + return self._getHighlight(image, (suit, rank, color), color) + return self._getHighlight(image, (suit, rank)) - def getHighlightCard(self, deck, suit, rank): - image = self.getFace(deck, suit, rank) - return self._getShadow(image, (suit, rank, 'black'), 'black') - - def getShadowBack(self): + def getHighlightedBack(self): image = self.getBack() - return self._getShadow(image, 'back') + return self._getHighlight(image, 'back') def getCardbacks(self): return self._back @@ -347,6 +342,83 @@ class Images: self._bottom = self._bottom_positive self._letter = self._letter_positive + def _setSize(self, xf=1, yf=1): + #print 'Images._setSize', xf, yf + self._xfactor = xf + self._yfactor = yf + cs = self.cs + if cs is None: + return + r = self.reduced + xf = float(xf)/r + yf = float(yf)/r + # copy from cardset + self.CARDW, self.CARDH = int(cs.CARDW*xf), int(cs.CARDH*yf) + if r > 1: + self.CARD_XOFFSET = max(10/r, int(cs.CARD_XOFFSET*xf)) + self.CARD_YOFFSET = max(10/r, int(cs.CARD_YOFFSET*yf)) + else: + self.CARD_XOFFSET = int(cs.CARD_XOFFSET*xf) + self.CARD_YOFFSET = int(cs.CARD_YOFFSET*yf) + self.SHADOW_XOFFSET = int(cs.SHADOW_XOFFSET*xf) + self.SHADOW_YOFFSET = int(cs.SHADOW_YOFFSET*yf) + self.CARD_DX, self.CARD_DY = int(cs.CARD_DX*xf), int(cs.CARD_DY*yf) + + def getSize(self): + return (int(self.CARDW * self._xfactor), + int(self.CARDH * self._yfactor)) + def getOffsets(self): + return (int(self.CARD_XOFFSET * self._xfactor), + int(self.CARD_YOFFSET * self._yfactor)) + def getDelta(self): + return (int(self.CARD_DX * self._xfactor), + int(self.CARD_DY * self._yfactor)) + + def resize(self, xf, yf): + #print 'Images.resize:', xf, yf, self._card[0].width() + if self._xfactor == xf and self._yfactor == yf: + #print 'no resize' + return + self._xfactor = xf + self._yfactor = yf + # cards + cards = [] + for c in self._card: + c = c.resize(xf, yf) + cards.append(c) + self._card = cards + # back + for b in self._back: + b.image = b.image.resize(xf, yf) + # stack bottom image + neg = self._bottom is self._bottom_negative + self._bottom_negative = [] + self._bottom_positive = [] + for i in range(self.cs.nbottoms): + name = "bottom%02d" % (i + 1) + self._bottom_positive.append(self.__loadBottom(name, color='black')) + name = "bottom%02d-n" % (i + 1) + self._bottom_negative.append(self.__loadBottom(name, color='white')) + # letters + self._letter_positive = [] + self._letter_negative = [] + for rank in range(self.cs.nletters): + name = "l%02d" % (rank + 1) + self._letter_positive.append(self.__loadBottom(name, color='black')) + name = "l%02d-n" % (rank + 1) + self._letter_negative.append(self.__loadBottom(name, color='white')) + self._createMissingImages() + self.setNegative(neg) + # + self._highlighted_images = {} + self._highlight = [] + self._highlight.append(self._getHighlight(self._card[0], None, '#3896f8')) + self._pil_shadow = {} + + def reset(self): + print 'Image.reset' + self.resize(1, 1) + # ************************************************************************ # * @@ -371,9 +443,9 @@ class SubsampledImages(Images): self._back.append(ImagesCardback(len(self._back), _back.name, im, im)) # CW, CH = self.CARDW, self.CARDH - for im in images._shade: - ##self._shade.append(None) - self._shade.append(copyImage(im, 0, 0, CW, CH)) + for im in images._highlight: + ##self._highlight.append(None) + self._highlight.append(copyImage(im, 0, 0, CW, CH)) def getShadow(self, ncards): return None diff --git a/pysollib/layout.py b/pysollib/layout.py index 6984249d..5347c058 100644 --- a/pysollib/layout.py +++ b/pysollib/layout.py @@ -89,10 +89,8 @@ class Layout: self.CH = images.CARDH self.XOFFSET = images.CARD_XOFFSET self.YOFFSET = images.CARD_YOFFSET - - self.XM = layout_x_margin # XMARGIN - self.YM = layout_y_margin # YMARGIN - + self.XM = layout_x_margin # XMARGIN + self.YM = layout_y_margin # YMARGIN if card_x_space is None: self.XS = self.CW + layout_card_x_space # XSPACE @@ -914,8 +912,9 @@ class Layout: w = XM * 2 + toprows * XS # set size so that at least 2/3 of a card is visible with 12 cards - h = CH * 2 / 3 + (playcards - 1) * self.YOFFSET - h = max(h, 2 * YS) + h1 = CH * 2 / 3 + (playcards - 1) * self.YOFFSET + h2 = (3 + reserves / decks) * YS + h = max(h1, h2) # create foundations x, y = w - XS * decks, YM diff --git a/pysollib/mfxutil.py b/pysollib/mfxutil.py index b9774a6a..bfd4dfce 100644 --- a/pysollib/mfxutil.py +++ b/pysollib/mfxutil.py @@ -36,7 +36,7 @@ try: except: thread = None -from settings import PACKAGE, TOOLKIT +from settings import PACKAGE, TOOLKIT, USE_TILE Image = ImageTk = ImageOps = None if TOOLKIT == 'tk': @@ -54,7 +54,13 @@ if TOOLKIT == 'tk': import BmpImagePlugin import PpmImagePlugin Image._initialized = 2 +USE_PIL = False +if TOOLKIT == 'tk' and USE_TILE and Image and Image.VERSION >= '1.1.7': + USE_PIL = True +# debug +#Image = None +#USE_PIL = False # ************************************************************************ # * exceptions diff --git a/pysollib/options.py b/pysollib/options.py index 55f15a25..290abb50 100644 --- a/pysollib/options.py +++ b/pysollib/options.py @@ -95,6 +95,7 @@ randomize_place = boolean save_cardsets = boolean dragcursor = boolean save_games_geometry = boolean +game_geometry = int_list(min=2, max=2) sound = boolean sound_mode = integer(0, 1) sound_sample_volume = integer(0, 128) @@ -166,7 +167,11 @@ highlight_piles = float(0.2, 9.9) 7 = string_list(min=2, max=2) 8 = string_list(min=2, max=2) 9 = string_list(min=2, max=2) - +scale_cards = boolean +scale_x = float +scale_y = float +auto_scale = boolean +preserve_aspect_ratio = boolean '''.splitlines() @@ -237,7 +242,6 @@ class Options: #('favorite_gameid', 'list'), ] - def __init__(self): self._config = None # configobj.ConfigObj instance self._config_encoding = 'utf-8' @@ -367,10 +371,17 @@ class Options: self.wm_maximized = 0 self.save_games_geometry = False self.games_geometry = {} # saved games geometry (gameid: (width, height)) + self.game_geometry = (0, 0) # game geometry before exit # self.randomize_place = False self.save_cardsets = True self.dragcursor = True + # + self.scale_cards = False + self.scale_x = 1.0 + self.scale_y = 1.0 + self.auto_scale = False + self.preserve_aspect_ratio = True def setDefaults(self, top=None): WIN_SYSTEM = settings.WIN_SYSTEM @@ -477,11 +488,15 @@ class Options: # cardsets for key, val in self.cardset.items(): config['cardsets'][str(key)] = val + for key in ('scale_cards', 'scale_x', 'scale_y', + 'auto_scale', 'preserve_aspect_ratio'): + config['cardsets'][key] = getattr(self, key) # games_geometry config['games_geometry'].clear() for key, val in self.games_geometry.items(): config['games_geometry'][str(key)] = val + config['general']['game_geometry'] = self.game_geometry config.write() ##config.write(sys.stdout); print @@ -628,6 +643,14 @@ class Options: self.cardset[int(key)] = val except: traceback.print_exc() + for key, t in (('scale_cards', 'bool'), + ('scale_x', 'float'), + ('scale_y', 'float'), + ('auto_scale', 'bool'), + ('preserve_aspect_ratio', 'bool')): + val = self._getOption('cardsets', key, t) + if val is not None: + setattr(self, key, val) # games_geometry for key, val in config['games_geometry'].items(): @@ -637,5 +660,11 @@ class Options: self.games_geometry[int(key)] = val except: traceback.print_exc() + game_geometry = self._getOption('general', 'game_geometry', 'list') + if game_geometry is not None: + try: + self.game_geometry = tuple(int(i) for i in game_geometry) + except: + traceback.print_exc() diff --git a/pysollib/settings.py b/pysollib/settings.py index 6f06c1a0..74acf705 100644 --- a/pysollib/settings.py +++ b/pysollib/settings.py @@ -30,8 +30,8 @@ PACKAGE = 'PySolFC' TITLE = 'PySol' PACKAGE_URL = 'http://pysolfc.sourceforge.net/' -VERSION = '2.0.1' -VERSION_TUPLE = (2,0,1) +VERSION = '3.0' +VERSION_TUPLE = (3,0,0) # Tk windowing system (auto set up in init.py) WIN_SYSTEM = 'x11' # win32, x11, aqua, classic diff --git a/pysollib/stack.py b/pysollib/stack.py index 73a18b64..7ceefe06 100644 --- a/pysollib/stack.py +++ b/pysollib/stack.py @@ -97,7 +97,7 @@ import types # PySol imports from mfxutil import Struct, kwdefault, SubclassResponsibility -from mfxutil import Image, ImageTk +from mfxutil import Image, ImageTk, USE_PIL from util import ACE, KING from util import ANY_SUIT, ANY_COLOR, ANY_RANK, NO_RANK from pysoltk import EVENT_HANDLED, EVENT_PROPAGATE @@ -238,7 +238,6 @@ class Stack: mapkey = (x, y) ###assert not game.stackmap.has_key(mapkey) ## can happen in PyJonngg game.stackmap[mapkey] = id - self.init_coord = (x, y) # # setup our pseudo MVC scheme @@ -282,15 +281,16 @@ class Stack: # # view # + self.init_coord = (x, y) view.x = x view.y = y view.canvas = game.canvas view.CARD_XOFFSET = 0 view.CARD_YOFFSET = 0 + view.INIT_CARD_OFFSETS = (0,0) view.INIT_CARD_YOFFSET = 0 # for reallocateCards view.group = MfxCanvasGroup(view.canvas) view.shrink_face_down = 1 - ##view.group.move(view.x, view.y) # image items view.images = Struct( bottom = None, # canvas item @@ -366,6 +366,11 @@ class Stack: self.CARD_YOFFSET = (oy,) else: self.CARD_YOFFSET = tuple([int(round(y)) for y in oy]) + + # preserve offsets + self.INIT_CARD_OFFSETS = (self.CARD_XOFFSET, self.CARD_YOFFSET) # for resize() + self.INIT_CARD_YOFFSET = self.CARD_YOFFSET # for reallocateCards + if self.can_hide_cards < 0: self.can_hide_cards = self.is_visible if self.cap.max_cards < 3: @@ -398,7 +403,7 @@ class Stack: # bottom image if self.is_visible: self.prepareBottom() - self.INIT_CARD_YOFFSET = self.CARD_YOFFSET # for reallocateCards + # stack bottom image def prepareBottom(self): @@ -520,26 +525,6 @@ class Stack: x, y = self.getPositionFor(card) card.moveTo(x, y) - # move stack to new coords - def moveTo(self, x, y): - dx, dy = x-self.x, y-self.y - self.x, self.y = x, y - for c in self.cards: - x, y = self.getPositionFor(c) - c.moveTo(x, y) - if self.images.bottom: - self.images.bottom.move(dx, dy) - if self.images.redeal: - self.images.redeal.move(dx, dy) - if self.texts.ncards: - self.texts.ncards.move(dx, dy) - if self.texts.rounds: - self.texts.rounds.move(dx, dy) - if self.texts.redeal: - self.texts.redeal.move(dx, dy) - if self.texts.misc: - self.texts.misc.move(dx, dy) - # find card def _findCard(self, event): model, view = self, self @@ -553,11 +538,12 @@ class Stack: model, view = self, self if cards is None: cards = model.cards images = self.game.app.images + cw, ch = images.getSize() index = -1 for i in range(len(cards)): c = cards[i] - r = (c.x, c.y, c.x + images.CARDW, c.y + images.CARDH) - if x >= r[0] and x < r[2] and y >= r[1] and y < r[3]: + r = (c.x, c.y, c.x + cw, c.y + ch) + if r[0] <= x < r[2] and r[1] <= y < r[3]: index = i return index @@ -667,6 +653,10 @@ class Stack: def resetGame(self): # Called when starting a new game. self.CARD_YOFFSET = self.INIT_CARD_YOFFSET + self.items.shade_item = None + self.images.shade_img = None + #self.items.bottom = None + #self.images.bottom = None def __repr__(self): # Return a string for debug print statements. @@ -874,7 +864,7 @@ class Stack: if not self.canvas.winfo_ismapped(): return False yoffset = self.CARD_YOFFSET[0] - cardh = self.game.app.images.CARDH / 2 # 1/2 of a card is visible + cardh = self.game.app.images.getSize()[0] / 2 # 1/2 of a card is visible num_face_up = len([c for c in self.cards if c.face_up]) num_face_down = len(self.cards) - num_face_up stack_height = int(self.y + @@ -882,13 +872,19 @@ class Stack: num_face_up * yoffset + cardh) visible_height = self.canvas.winfo_height() - game_height = self.game.height + 2*self.canvas.ymargin + if USE_PIL and self.game.app.opt.auto_scale: + # use visible_height only + game_height = 0 + else: + game_height = self.game.height + 2*self.canvas.ymargin height = max(visible_height, game_height) + #print 'reallocateCards:', stack_height, height, visible_height, game_height if stack_height > height: # compact stack n = num_face_down / self.shrink_face_down + num_face_up dy = float(height - self.y - cardh) / n if dy < yoffset: + #print 'compact:', dy self.CARD_YOFFSET = (dy,) return True elif stack_height < height: @@ -898,12 +894,62 @@ class Stack: n = num_face_down / self.shrink_face_down + num_face_up dy = float(height - self.y - cardh) / n dy = min(dy, self.INIT_CARD_YOFFSET[0]) + #print 'expande:', dy self.CARD_YOFFSET = (dy,) return True return False def resize(self, xf, yf): - print 'resize', self + # resize and move stack + # xf, yf - a multiplicative factor (from the original values) + #print 'Stack.resize:', self, self.is_visible, xf, yf + x0, y0 = self.init_coord + x, y = int(round(x0*xf)), int(round(y0*yf)) + self.x, self.y = x, y + # offsets + xoffset = tuple(int(round(i*xf)) for i in self.INIT_CARD_OFFSETS[0]) + yoffset = tuple(int(round(i*yf)) for i in self.INIT_CARD_OFFSETS[1]) + self.CARD_XOFFSET = xoffset + self.CARD_YOFFSET = yoffset + self.INIT_CARD_YOFFSET = yoffset + #print '* resize offset:', self.INIT_CARD_XOFFSET, + # move cards + for c in self.cards: + cx, cy = self.getPositionFor(c) + c.moveTo(cx, cy) + # --- + if not self.is_visible: + return + # bottom and shade + if self.images.bottom: + img = self.getBottomImage() + self.images.bottom['image'] = img + self.images.bottom.moveTo(x, y) + if self.items.shade_item: + c = self.cards[-1] + img = self.game.app.images.getHighlightedCard(c.deck, c.suit, c.rank) + if img: + self.items.shade_item['image'] = img + self.items.shade_item.moveTo(x, y) + # + def move(item): + ix, iy = item.init_coord + x = int(round(ix*xf)) + y = int(round(iy*yf)) + item.moveTo(x, y) + # images + if self.images.redeal: + move(self.images.redeal) + # texts + if self.texts.ncards: + move(self.texts.ncards) + if self.texts.rounds: + move(self.texts.rounds) + if self.texts.redeal: + move(self.texts.redeal) + if self.texts.misc: + move(self.texts.misc) + def basicShallHighlightSameRank(self, card): # by default all open stacks are available for highlighting @@ -1003,14 +1049,14 @@ class Stack: return 0 ##print self.cards[i] self.cards[i].item.tkraise() - self.game.canvas.update_idletasks() + self.canvas.update_idletasks() self.game.sleep(self.game.app.opt.timeouts['raise_card']) if TOOLKIT == 'tk': self.cards[i].item.lower(self.cards[i+1].item) elif TOOLKIT == 'gtk': for c in self.cards[i+1:]: c.tkraise() - self.game.canvas.update_idletasks() + self.canvas.update_idletasks() return 1 def controlmiddleclickHandler(self, event): @@ -1026,7 +1072,7 @@ class Stack: if not face_up: self.cards[i].showFace() self.cards[i].item.tkraise() - self.game.canvas.update_idletasks() + self.canvas.update_idletasks() self.game.sleep(self.game.app.opt.timeouts['raise_card']) if not face_up: self.cards[i].showBack() @@ -1036,7 +1082,7 @@ class Stack: elif TOOLKIT == 'gtk': for c in self.cards[i+1:]: c.tkraise() - self.game.canvas.update_idletasks() + self.canvas.update_idletasks() return 1 def rightclickHandler(self, event): @@ -1074,8 +1120,7 @@ class Stack: x0, y0 = drag.stack.getPositionFor(c) x1, y1 = c.x, c.y dx, dy = abs(x0-x1), abs(y0-y1) - w = self.game.app.images.CARDW - h = self.game.app.images.CARDH + w, h = self.game.app.images.getSize() if dx > 2*w or dy > 2*h: self.game.animatedMoveTo(drag.stack, drag.stack, drag.cards, x0, y0, frames=-1) @@ -1185,7 +1230,7 @@ class Stack: if self.game.app.opt.mouse_type == 'point-n-click': if self.acceptsCards(self.game.drag.stack, self.game.drag.cards): - self.game.canvas.config(cursor=CURSOR_DOWN_ARROW) + self.canvas.config(cursor=CURSOR_DOWN_ARROW) self.current_cursor = CURSOR_DOWN_ARROW self.cursor_changed = True else: @@ -1203,13 +1248,13 @@ class Stack: if self.game.app.opt.mouse_type == 'drag-n-drop': return EVENT_HANDLED if self.cursor_changed: - self.game.canvas.config(cursor='') + self.canvas.config(cursor='') self.current_cursor = '' self.cursor_changed = False drag_stack = self.game.drag.stack if self is drag_stack: x, y = event.x, event.y - w, h = self.game.canvas.winfo_width(), self.game.canvas.winfo_height() + w, h = self.canvas.winfo_width(), self.canvas.winfo_height() if x < 0 or y < 0 or x >= w or y >= h: # cancel drag if mouse leave canvas drag_stack.cancelDrag(event) @@ -1261,10 +1306,11 @@ class Stack: ##sx, sy = 0, 0 sx, sy = -images.SHADOW_XOFFSET, -images.SHADOW_YOFFSET dx, dy = 0, 0 + cw, ch = images.getSize() if game.app.opt.mouse_type == 'sticky-mouse': # return cards under mouse - dx = event.x - (x_offset+images.CARDW+sx) - game.canvas.xmargin - dy = event.y - (y_offset+images.CARDH+sy) - game.canvas.ymargin + dx = event.x - (x_offset+cw+sx) - game.canvas.xmargin + dy = event.y - (y_offset+ch+sy) - game.canvas.ymargin if dx < 0: dx = 0 if dy < 0: dy = 0 for s in drag.shadows: @@ -1316,13 +1362,14 @@ class Stack: images = self.game.app.images cx, cy = cards[0].x, cards[0].y ddx, ddy = cx-cards[-1].x, cy-cards[-1].y - if TOOLKIT == 'tk' and Image and Image.VERSION >= '1.1.7': # use PIL + cw, ch = images.getSize() + if USE_PIL: c0 = cards[-1] if self.CARD_XOFFSET[0] < 0: c0 = cards[0] if self.CARD_YOFFSET[0] < 0: c0 = cards[0] img = images.getShadowPIL(self, cards) - cx, cy = c0.x + images.CARDW + dx, c0.y + images.CARDH + dy - s = MfxCanvasImage(self.game.canvas, cx, cy, + cx, cy = c0.x + cw + dx, c0.y + ch + dy + s = MfxCanvasImage(self.canvas, cx, cy, image=img, anchor=ANCHOR_SE) s.lower(c0.item) return (s,) @@ -1346,10 +1393,10 @@ class Stack: else: return () if img0 and img1: - cx, cy = c0.x + images.CARDW + dx, c0.y + images.CARDH + dy - s1 = MfxCanvasImage(self.game.canvas, cx, cy - img0.height(), + cx, cy = c0.x + cw + dx, c0.y + ch + dy + s1 = MfxCanvasImage(self.canvas, cx, cy - img0.height(), image=img1, anchor=ANCHOR_SE) - s2 = MfxCanvasImage(self.game.canvas, cx, cy, + s2 = MfxCanvasImage(self.canvas, cx, cy, image=img0, anchor=ANCHOR_SE) if TOOLKIT == 'tk': s1.lower(c0.item) @@ -1412,9 +1459,9 @@ class Stack: if sstack.cards: card = sstack.cards[-1] if card.face_up: - img = images.getShadowCard(card.deck, card.suit, card.rank) + img = images.getHighlightedCard(card.deck, card.suit, card.rank) else: - img = images.getShadowBack() + img = images.getHighlightedBack() else: img = images.getShade() if not img: @@ -1440,11 +1487,11 @@ class Stack: ## self.CARD_YOFFSET != (0,)): ## return card = self.cards[-1] - img = self.game.app.images.getShadowCard(card.deck, card.suit, card.rank) + img = self.game.app.images.getHighlightedCard(card.deck, card.suit, card.rank) if img is None: return - #self.game.canvas.update_idletasks() - item = MfxCanvasImage(self.game.canvas, card.x, card.y, + #self.canvas.update_idletasks() + item = MfxCanvasImage(self.canvas, card.x, card.y, image=img, anchor=ANCHOR_NW, group=self.group) #item.tkraise() self.items.shade_item = item @@ -1463,8 +1510,9 @@ class Stack: x1, y1 = self.getPositionFor(cards[-1]) x0, x1 = min(x1, x0), max(x1, x0) y0, y1 = min(y1, y0), max(y1, y0) - x1 = x1 + self.game.app.images.CARDW - y1 = y1 + self.game.app.images.CARDH + cw, ch = self.game.app.images.getSize() + x1 += cw + y1 += ch xx0, yy0 = x0, y0 w, h = x1-x0, y1-y0 # @@ -1491,7 +1539,7 @@ class Stack: # shade = markImage(shade) tkshade = ImageTk.PhotoImage(shade) - im = MfxCanvasImage(self.game.canvas, xx0, yy0, + im = MfxCanvasImage(self.canvas, xx0, yy0, image=tkshade, anchor=ANCHOR_NW, group=self.group) drag.shadows.append(im) @@ -1512,7 +1560,7 @@ class Stack: # finish a drag operation def finishDrag(self, event=None): if self.game.app.opt.dragcursor: - self.game.canvas.config(cursor='') + self.canvas.config(cursor='') drag = self.game.drag.copy() if self.game.app.opt.mouse_type == 'point-n-click': drag.stack._stopDrag() @@ -1528,7 +1576,7 @@ class Stack: # cancel a drag operation def cancelDrag(self, event=None): if self.game.app.opt.dragcursor: - self.game.canvas.config(cursor='') + self.canvas.config(cursor='') drag = self.game.drag.copy() if self.game.app.opt.mouse_type == 'point-n-click': drag.stack._stopDrag() @@ -1726,6 +1774,11 @@ class TalonStack(Stack, Stack.__init__(self, x, y, game, cap=cap) self.max_rounds = max_rounds self.num_deal = num_deal + self.init_redeal = Struct( + top_bottom = None, + img_coord = None, + txt_coord = None, + ) self.resetGame() def resetGame(self): @@ -1787,22 +1840,34 @@ class TalonStack(Stack, self.texts.redeal.config(text=t) self.texts.redeal_str = t - def prepareView(self): - Stack.prepareView(self) + def _addRedealImage(self): + # add or remove the redeal image/text if not self.is_visible or self.images.bottom is None: return - if self.images.redeal is not None or self.texts.redeal is not None: - return if self.game.preview > 1: return images = self.game.app.images - cx, cy, ca = self.x + images.CARDW/2, self.y + images.CARDH/2, "center" - if images.CARDW >= 54 and images.CARDH >= 54: + cw, ch = images.getSize() + cx, cy = self.init_redeal.img_coord + ca = 'center' + tx, ty = self.init_redeal.txt_coord + + if self.images.redeal: + self.canvas.delete(self.images.redeal) + self.images.redeal = None + self.images.redeal_img = None + if self.texts.redeal: + self.canvas.delete(self.texts.redeal) + self.texts.redeal = None + self.texts.redeal_str = '' + self.top_bottom = self.init_redeal.top_bottom + + if cw >= 60 and ch >= 60: # add a redeal image above the bottom image img = (self.getRedealImages())[self.max_rounds != 1] if img is not None: self.images.redeal_img = img - self.images.redeal = MfxCanvasImage(self.game.canvas, + self.images.redeal = MfxCanvasImage(self.canvas, cx, cy, image=img, anchor="center", group=self.group) @@ -1812,19 +1877,21 @@ class TalonStack(Stack, ### FIXME pass self.top_bottom = self.images.redeal - if images.CARDH >= 90: - cy, ca = self.y + images.CARDH - 4, "s" + if ch >= 90: + cy, ca = ty, "s" else: ca = None font = self.game.app.getFont("canvas_default") text_width = get_text_width(_('Redeal'), font=font, - root=self.game.canvas) - if images.CARDW >= text_width+4 and ca: - # add a redeal text above the bottom image + root=self.canvas) + if cw >= text_width+4 and ca: + # add a redeal text below the bottom image if self.max_rounds != 1: + # FIXME: sometimes canvas do not show the text + #print 'add txt', cx, cy self.texts.redeal_str = "" images = self.game.app.images - self.texts.redeal = MfxCanvasText(self.game.canvas, cx, cy, + self.texts.redeal = MfxCanvasText(self.canvas, cx, cy, anchor=ca, font=font, group=self.group) if TOOLKIT == 'tk': @@ -1834,6 +1901,22 @@ class TalonStack(Stack, pass self.top_bottom = self.texts.redeal + def prepareView(self): + Stack.prepareView(self) + if 0: + if not self.is_visible or self.images.bottom is None: + return + if self.images.redeal is not None or self.texts.redeal is not None: + return + if self.game.preview > 1: + return + images = self.game.app.images + self.init_redeal.top_bottom = self.top_bottom + cx, cy, ca = self.x + images.CARDW/2, self.y + images.CARDH/2, "center" + ty = self.y + images.CARDH - 4 + self.init_redeal.img_coord = cx, cy + self.init_redeal.txt_coord = cx, ty + getBottomImage = Stack._getTalonBottomImage def getRedealImages(self): @@ -1853,6 +1936,10 @@ class TalonStack(Stack, #def getBaseCard(self): # return self._getBaseCard() + def resize(self, xf, yf): + self._addRedealImage() + Stack.resize(self, xf, yf) + # A single click deals one card to each of the RowStacks. class DealRowTalonStack(TalonStack): diff --git a/pysollib/tile/card.py b/pysollib/tile/card.py index 70bd2a3d..7d135761 100644 --- a/pysollib/tile/card.py +++ b/pysollib/tile/card.py @@ -107,6 +107,17 @@ class _OneImageCard(_HideableCard): item = self.item item.canvas.tk.call(item.canvas._w, "move", item.id, dx, dy) + # for resize + def update(self, id, deck, suit, rank, game): + self._face_image = game.getCardFaceImage(deck, suit, rank) + self._back_image = game.getCardBackImage(deck, suit, rank) + self._shade_image = game.getCardShadeImage() + if self.face_up: + img = self._face_image + else: + img = self._back_image + self.item.config(image=img) + self._active_image = img # ************************************************************************ diff --git a/pysollib/tile/menubar.py b/pysollib/tile/menubar.py index 392e880b..47539ff7 100644 --- a/pysollib/tile/menubar.py +++ b/pysollib/tile/menubar.py @@ -31,7 +31,7 @@ import tkFileDialog # PySol imports from pysollib.mfxutil import Struct, kwdefault -from pysollib.mfxutil import Image +from pysollib.mfxutil import Image, USE_PIL from pysollib.util import CARDSET from pysollib.settings import TITLE, WIN_SYSTEM from pysollib.settings import SELECT_GAME_MENU @@ -210,6 +210,7 @@ class PysolMenubarTk: mahjongg_show_removed = Tkinter.BooleanVar(), shisen_show_hint = Tkinter.BooleanVar(), sound = Tkinter.BooleanVar(), + auto_scale = Tkinter.BooleanVar(), cardback = Tkinter.IntVar(), tabletile = Tkinter.IntVar(), animations = Tkinter.IntVar(), @@ -260,6 +261,7 @@ class PysolMenubarTk: tkopt.mahjongg_show_removed.set(opt.mahjongg_show_removed) tkopt.shisen_show_hint.set(opt.shisen_show_hint) tkopt.sound.set(opt.sound) + tkopt.auto_scale.set(opt.auto_scale) tkopt.cardback.set(self.app.cardset.backindex) tkopt.tabletile.set(self.app.tabletile_index) tkopt.animations.set(opt.animations) @@ -448,6 +450,11 @@ class PysolMenubarTk: else: menu.add_checkbutton(label=label, variable=self.tkopt.sound, command=self.mOptSoundDialog) # cardsets + if USE_PIL: + submenu = MfxMenu(menu, label=n_("Card si&ze")) + submenu.add_command(label=n_("&Increase the card size"), command=self.mIncreaseCardset, accelerator=m+"+") + submenu.add_command(label=n_("&Decrease the card size"), command=self.mDecreaseCardset, accelerator=m+"-") + submenu.add_checkbutton(label=n_("&Auto scaling"), variable=self.tkopt.auto_scale, command=self.mOptAutoScale, accelerator=m+'0') #manager = self.app.cardset_manager #n = manager.len() menu.add_command(label=n_("Cards&et..."), command=self.mSelectCardsetDialog, accelerator=m+"E") @@ -489,7 +496,7 @@ class PysolMenubarTk: submenu.add_checkbutton(label=n_("Show &statusbar"), variable=self.tkopt.statusbar, command=self.mOptStatusbar) submenu.add_checkbutton(label=n_("Show &number of cards"), variable=self.tkopt.num_cards, command=self.mOptNumCards) submenu.add_checkbutton(label=n_("Show &help bar"), variable=self.tkopt.helpbar, command=self.mOptHelpbar) - menu.add_checkbutton(label=n_("Save games &geometry"), variable=self.tkopt.save_games_geometry, command=self.mOptSaveGamesGeometry) + #menu.add_checkbutton(label=n_("Save games &geometry"), variable=self.tkopt.save_games_geometry, command=self.mOptSaveGamesGeometry) menu.add_checkbutton(label=n_("&Demo logo"), variable=self.tkopt.demo_logo, command=self.mOptDemoLogo) menu.add_checkbutton(label=n_("Startup splash sc&reen"), variable=self.tkopt.splashscreen, command=self.mOptSplashscreen) ### menu.add_separator() @@ -538,6 +545,11 @@ class PysolMenubarTk: self._bindKey("", "F3", self.mFindCard) self._bindKey(ctrl, "d", self.mDemo) self._bindKey(ctrl, "e", self.mSelectCardsetDialog) + if USE_PIL: + self._bindKey(ctrl, "plus", self.mIncreaseCardset) + self._bindKey(ctrl, "equal", self.mIncreaseCardset) + self._bindKey(ctrl, "minus", self.mDecreaseCardset) + self._bindKey(ctrl, "0", self.mOptAutoScale) self._bindKey(ctrl, "b", self.mOptChangeCardback) # undocumented self._bindKey(ctrl, "i", self.mOptChangeTableTile) # undocumented self._bindKey(ctrl, "p", self.mOptPlayerOptions) # undocumented @@ -1134,6 +1146,56 @@ class PysolMenubarTk: self.app.opt.shisen_show_hint = self.tkopt.shisen_show_hint.get() ##self.game.updateMenus() + def _updateCardSize(self): + geom = (self.app.canvas.winfo_width(), + self.app.canvas.winfo_height()) + self.app.opt.game_geometry = geom + self.app.game.resizeGame() + if self.app.opt.auto_scale: + w, h = self.app.opt.game_geometry + self.app.canvas.setInitialSize(w, h, scrollregion=False) + else: + w = int(round(self.app.game.width * self.app.opt.scale_x)) + h = int(round(self.app.game.height * self.app.opt.scale_y)) + self.app.canvas.setInitialSize(w, h) + self.app.top.wm_geometry("") # cancel user-specified geometry + ##self.app.top.update_idletasks() + + def mIncreaseCardset(self, *event): + if self._cancelDrag(break_pause=True): return + if self.app.opt.scale_x < 4: + self.app.opt.scale_x += 0.1 + else: + return + if self.app.opt.scale_y < 4: + self.app.opt.scale_y += 0.1 + else: + return + self.app.opt.auto_scale = False + self.tkopt.auto_scale.set(False) + self._updateCardSize() + + def mDecreaseCardset(self, *event): + if self._cancelDrag(break_pause=True): return + if self.app.opt.scale_x > 0.5: + self.app.opt.scale_x -= 0.1 + else: + return + if self.app.opt.scale_y > 0.5: + self.app.opt.scale_y -= 0.1 + else: + return + self.app.opt.auto_scale = False + self.tkopt.auto_scale.set(False) + self._updateCardSize() + + def mOptAutoScale(self, *event): + if self._cancelDrag(break_pause=True): return + auto_scale = not self.app.opt.auto_scale + self.app.opt.auto_scale = auto_scale + self.tkopt.auto_scale.set(auto_scale) + self._updateCardSize() + def mSelectCardsetDialog(self, *event): if self._cancelDrag(break_pause=False): return t = CARDSET @@ -1141,14 +1203,30 @@ class PysolMenubarTk: d = SelectCardsetDialogWithPreview(self.top, title=_("Select ")+t, app=self.app, manager=self.app.cardset_manager, key=key) cs = self.app.cardset_manager.get(d.key) - if cs is None or d.key == self.app.cardset.index: + if d.status != 0 or d.button != 0 or cs is None: return - if d.status == 0 and d.button == 0 and d.key >= 0: + if USE_PIL: + changed = (self.app.opt.scale_x, + self.app.opt.scale_y, + self.app.opt.auto_scale, + self.app.opt.preserve_aspect_ratio) != d.scale_values + else: + changed = False + if d.key == self.app.cardset.index and not changed: + return + if d.key >= 0: self.app.nextgame.cardset = cs - if d.button == 0: - self._cancelDrag() - self.game.endGame(bookmark=1) - self.game.quitGame(bookmark=1) + if USE_PIL: + (self.app.opt.scale_x, + self.app.opt.scale_y, + self.app.opt.auto_scale, + self.app.opt.preserve_aspect_ratio) = d.scale_values + if not self.app.opt.auto_scale: + self.app.images.resize(self.app.opt.scale_x, + self.app.opt.scale_y) + self._cancelDrag() + self.game.endGame(bookmark=1) + self.game.quitGame(bookmark=1) def _mOptCardback(self, index): if self._cancelDrag(break_pause=False): return @@ -1323,7 +1401,7 @@ class PysolMenubarTk: self.game.showStackDesc() # - # Tlie + # Tile (ttk) # def mOptTheme(self, *event): diff --git a/pysollib/tile/selectcardset.py b/pysollib/tile/selectcardset.py index 81a76624..8ce90754 100644 --- a/pysollib/tile/selectcardset.py +++ b/pysollib/tile/selectcardset.py @@ -29,13 +29,13 @@ import Tkinter import ttk # PySol imports -from pysollib.mfxutil import KwStruct +from pysollib.mfxutil import KwStruct, USE_PIL from pysollib.util import CARDSET from pysollib.resource import CSI # Toolkit imports from tkutil import loadImage -from tkwidget import MfxDialog, MfxScrolledCanvas +from tkwidget import MfxDialog, MfxScrolledCanvas, PysolScale from tkcanvas import MfxCanvasImage from selecttree import SelectDialogTreeLeaf, SelectDialogTreeNode from selecttree import SelectDialogTreeData, SelectDialogTreeCanvas @@ -179,6 +179,7 @@ class SelectCardsetDialogWithPreview(MfxDialog): key = manager.getSelected() self.manager = manager self.key = key + self.app = app #padx, pady = kw.padx, kw.pady padx, pady = 5, 5 if self.TreeDataHolder_Class.data is None: @@ -186,7 +187,7 @@ class SelectCardsetDialogWithPreview(MfxDialog): # self.top.wm_minsize(400, 200) if self.top.winfo_screenwidth() >= 800: - w1, w2 = 216, 400 + w1, w2 = 240, 400 else: w1, w2 = 200, 300 paned_window = ttk.PanedWindow(top_frame, orient='horizontal') @@ -199,7 +200,56 @@ class SelectCardsetDialogWithPreview(MfxDialog): self.tree = self.Tree_Class(self, left_frame, key=key, default=kw.default, font=font, width=w1) - self.tree.frame.pack(fill='both', expand=True, padx=padx, pady=pady) + self.tree.frame.grid(row=0, column=0, sticky='nsew', + padx=padx, pady=pady) + if USE_PIL: + # + var = Tkinter.DoubleVar() + var.set(app.opt.scale_x) + self.scale_x = PysolScale( + left_frame, label=_('Scale X:'), + from_=0.5, to=4.0, resolution=0.1, + orient='horizontal', variable=var, + value=app.opt.scale_x, + command=self._updateScale) + self.scale_x.grid(row=1, column=0, sticky='ew', padx=padx, pady=pady) + # + var = Tkinter.DoubleVar() + var.set(app.opt.scale_y) + self.scale_y = PysolScale( + left_frame, label=_('Scale Y:'), + from_=0.5, to=4.0, resolution=0.1, + orient='horizontal', variable=var, + value=app.opt.scale_y, + command=self._updateScale) + self.scale_y.grid(row=2, column=0, sticky='ew', padx=padx, pady=pady) + # + self.auto_scale = Tkinter.BooleanVar() + self.auto_scale.set(app.opt.auto_scale) + check = ttk.Checkbutton( + left_frame, text=_('Auto scaling'), + variable=self.auto_scale, + takefocus=False, + command=self._updateAutoScale + ) + check.grid(row=3, column=0, columnspan=2, sticky='ew', + padx=padx, pady=pady) + # + self.preserve_aspect = Tkinter.BooleanVar() + self.preserve_aspect.set(app.opt.preserve_aspect_ratio) + self.aspect_check = ttk.Checkbutton( + left_frame, text=_('Preserve aspect ratio'), + variable=self.preserve_aspect, + takefocus=False, + #command=self._updateScale + ) + self.aspect_check.grid(row=4, column=0, sticky='ew', + padx=padx, pady=pady) + self._updateAutoScale() + # + left_frame.rowconfigure(0, weight=1) + left_frame.columnconfigure(0, weight=1) + # self.preview = MfxScrolledCanvas(right_frame, width=w2) self.preview.setTile(app, app.tabletile_index, force=True) self.preview.pack(fill='both', expand=True, padx=padx, pady=pady) @@ -207,6 +257,7 @@ class SelectCardsetDialogWithPreview(MfxDialog): # create a preview of the current state self.preview_key = -1 self.preview_images = [] + self.scale_images = [] self.updatePreview(key) # focus = self.createButtons(bottom_frame, kw) @@ -233,6 +284,20 @@ class SelectCardsetDialogWithPreview(MfxDialog): if button in (0, 1): # Load/Cancel self.key = self.tree.selection_key self.tree.n_expansions = 1 # save xyview in any case + if USE_PIL: + auto_scale = bool(self.auto_scale.get()) + if button == 1: + self.app.menubar.tkopt.auto_scale.set(auto_scale) + if auto_scale: + self.scale_values = (self.app.opt.scale_x, + self.app.opt.scale_y, + auto_scale, + bool(self.preserve_aspect.get())) + else: + self.scale_values = (self.scale_x.get(), + self.scale_y.get(), + auto_scale, + self.app.opt.preserve_aspect_ratio) if button == 10: # Info cs = self.manager.get(self.tree.selection_key) if not cs: @@ -243,9 +308,24 @@ class SelectCardsetDialogWithPreview(MfxDialog): return MfxDialog.mDone(self, button) - def updatePreview(self, key): + def _updateAutoScale(self, v=None): + if self.auto_scale.get(): + self.aspect_check.config(state='normal') + self.scale_x.state('disabled') + self.scale_y.state('disabled') + else: + self.aspect_check.config(state='disabled') + self.scale_x.state('!disabled') + self.scale_y.state('!disabled') + + def _updateScale(self, v): + self.updatePreview() + + def updatePreview(self, key=None): if key == self.preview_key: return + if key is None: + key = self.key canvas = self.preview.canvas canvas.deleteAllItems() self.preview_images = [] @@ -264,7 +344,16 @@ class SelectCardsetDialogWithPreview(MfxDialog): self.preview_images = [] return i, x, y, sx, sy, dx, dy = 0, 10, 10, 0, 0, cs.CARDW + 10, cs.CARDH + 10 + if USE_PIL: + xf = self.scale_x.get() + yf = self.scale_y.get() + dx = int(dx*xf) + dy = int(dy*yf) + self.scale_images = [] for image in self.preview_images: + if USE_PIL: + image = image.resize(xf, yf) + self.scale_images.append(image) MfxCanvasImage(canvas, x, y, anchor="nw", image=image) sx, sy = max(x, sx), max(y, sy) i = i + 1 @@ -272,13 +361,12 @@ class SelectCardsetDialogWithPreview(MfxDialog): x, y = 10, y + dy else: x = x + dx -## canvas.config(scrollregion=(0, 0, sx+dx, sy+dy)) -## canvas.config(width=sx+dx, height=sy+dy) canvas.config(scrollregion=(0, 0, sx+dx, sy+dy), width=sx+dx, height=sy+dy) #canvas.config(xscrollincrement=dx, yscrollincrement=dy) canvas.event_generate('') # update bg image self.preview_key = key + self.key = key class SelectCardsetByTypeDialogWithPreview(SelectCardsetDialogWithPreview): diff --git a/pysollib/tile/tkcanvas.py b/pysollib/tile/tkcanvas.py index 846e88b3..6ae4084a 100644 --- a/pysollib/tile/tkcanvas.py +++ b/pysollib/tile/tkcanvas.py @@ -58,14 +58,15 @@ class MfxCanvasGroup(Canvas.Group): return self.canvas.tk.splitlist(self._do("gettags")) class MfxCanvasImage(Canvas.ImageItem): - def __init__(self, canvas, *args, **kwargs): + def __init__(self, canvas, x, y, **kwargs): + self.init_coord = x, y group = None if 'group' in kwargs: group = kwargs['group'] del kwargs['group'] if 'image' in kwargs: self._image = kwargs['image'] - Canvas.ImageItem.__init__(self, canvas, *args, **kwargs) + Canvas.ImageItem.__init__(self, canvas, x, y, **kwargs) if group: self.addtag(group) def moveTo(self, x, y): @@ -90,6 +91,8 @@ class MfxCanvasRectangle(Canvas.Rectangle): class MfxCanvasText(Canvas.CanvasText): def __init__(self, canvas, x, y, preview=-1, **kwargs): + self.init_coord = x, y + self.x, self.y = x, y if preview < 0: preview = canvas.preview if preview > 1: @@ -105,6 +108,10 @@ class MfxCanvasText(Canvas.CanvasText): canvas._text_items.append(self) if group: self.addtag(group) + def moveTo(self, x, y): + dx, dy = x - self.x, y - self.y + self.x, self.y = x, y + self.move(dx, dy) # ************************************************************************ @@ -229,17 +236,24 @@ class MfxCanvas(Tkinter.Canvas): # # - def setInitialSize(self, width, height): - ##print 'setInitialSize:', width, height + def setInitialSize(self, width, height, margins=True, scrollregion=True): + #print 'Canvas.setInitialSize:', width, height, scrollregion if self.preview: self.config(width=width, height=height, scrollregion=(0, 0, width, height)) else: # add margins - ##dx, dy = 40, 40 dx, dy = self.xmargin, self.ymargin - self.config(width=dx+width+dx, height=dy+height+dy, - scrollregion=(-dx, -dy, width+dx, height+dy)) + if margins: + w, h = dx+width+dx, dy+height+dy + else: + w, h = width, height + self.config(width=w, height=h) + if scrollregion: + self.config(scrollregion=(-dx, -dy, width+dx, height+dy)) + else: + # no scrolls + self.config(scrollregion=(-dx, -dy, dx, dy)) # diff --git a/pysollib/tile/tkutil.py b/pysollib/tile/tkutil.py index 5271ecb5..49c2e940 100644 --- a/pysollib/tile/tkutil.py +++ b/pysollib/tile/tkutil.py @@ -41,6 +41,7 @@ __all__ = ['wm_withdraw', 'shadowImage', 'markImage', 'createBottom', + 'resizeBottom', 'get_text_width', ] @@ -248,11 +249,15 @@ def after_cancel(t): if Image: class PIL_Image(ImageTk.PhotoImage): - def __init__(self, file=None, image=None): + def __init__(self, file=None, image=None, pil_image_orig=None): if file: image = Image.open(file).convert('RGBA') ImageTk.PhotoImage.__init__(self, image) self._pil_image = image + if pil_image_orig: + self._pil_image_orig = pil_image_orig + else: + self._pil_image_orig = image def subsample(self, r): im = self._pil_image w, h = im.size @@ -260,6 +265,11 @@ if Image: im = im.resize((w, h)) im = PIL_Image(image=im) return im + def resize(self, xf, yf): + w, h = self._pil_image_orig.size + w0, h0 = int(w*xf), int(h*yf) + im = self._pil_image_orig.resize((w0,h0), Image.ANTIALIAS) + return PIL_Image(image=im, pil_image_orig=self._pil_image_orig) def makeImage(file=None, data=None, dither=None, alpha=None): @@ -360,14 +370,11 @@ def markImage(image): out = Image.composite(tmp, image, image) return out -def createBottom(image, color='white', backfile=None): - if not hasattr(image, '_pil_image'): - return None - im = image._pil_image +def _createBottomImage(image, color='white', backfile=None): th = 1 # thickness - sh = Image.new('RGBA', im.size, color) - out = Image.composite(sh, im, im) - w, h = im.size + sh = Image.new('RGBA', image.size, color) + out = Image.composite(sh, image, image) + w, h = image.size size = (w-th*2, h-th*2) tmp = Image.new('RGBA', size, color) tmp.putalpha(60) @@ -376,15 +383,27 @@ def createBottom(image, color='white', backfile=None): if backfile: back = Image.open(backfile).convert('RGBA') w0, h0 = back.size - w1, h1 = im.size + w1, h1 = w, h a = min(float(w1)/w0, float(h1)/h0) a = a*0.9 w0, h0 = int(w0*a), int(h0*a) back = back.resize((w0,h0), Image.ANTIALIAS) x, y = (w1 - w0) / 2, (h1 - h0) / 2 out.paste(back, (x,y), back) + return out + +def createBottom(maskimage, color='white', backfile=None): + if not hasattr(maskimage, '_pil_image'): + return None + maskimage = maskimage._pil_image + out = _createBottomImage(maskimage, color, backfile) return PIL_Image(image=out) +def resizeBottom(image, maskimage, color='white', backfile=None): + maskimage = maskimage._pil_image + out = _createBottomImage(maskimage, color, backfile) + image['image'] = out + # ************************************************************************ # * font utils diff --git a/pysollib/tile/tkwidget.py b/pysollib/tile/tkwidget.py index 8e6e3f8d..8094f67b 100644 --- a/pysollib/tile/tkwidget.py +++ b/pysollib/tile/tkwidget.py @@ -667,7 +667,7 @@ class StackDesc: font = game.app.getFont('canvas_small') ##print self.app.cardset.CARDW, self.app.images.CARDW - cardw = game.app.images.CARDW + cardw = game.app.images.getSize()[0] x, y = stack.x+cardw/2, stack.y text = stack.getHelp()+'\n'+stack.getBaseCard() text = text.strip() @@ -715,30 +715,26 @@ class MyPysolScale: kw['from_'] = kw['from_']/self.resolution if 'to' in kw: kw['to'] = kw['to']/self.resolution - if 'command' in kw: - self.command = kw['command'] - else: - self.command = None if 'variable' in kw: self.variable = kw['variable'] del kw['variable'] else: self.variable = None + value = None if 'value' in kw: value = kw['value'] del kw['value'] - if self.variable: - self.variable.set(value) - else: - value = None - if self.variable: - value = self.variable.get() - if self.variable: - self.variable.trace('w', self._trace_var) + elif self.variable: + value = self.variable.get() + self.value = value + self.command = command = None + if 'command' in kw: + command = kw['command'] kw['command'] = self._scale_command if 'label' in kw: self.label_text = kw['label'] width = len(self.label_text)+4 + #width = None del kw['label'] else: self.label_text = None @@ -753,13 +749,16 @@ class MyPysolScale: self.scale = ttk.Scale(self.frame, **kw) self.scale.pack(side=side, expand=True, fill='both', pady=4) + if self.variable: + self.variable.trace('w', self._trace_var) if value is not None: - if self.variable: - self.variable.set(self._round(value)) self._set_text(self._round(value)) + if self.variable: + self.variable.set(value) + self.command = command def _round(self, value): - return int(float(value)/self.resolution)*self.resolution + return int(round(float(value)/self.resolution))*self.resolution def _trace_var(self, *args): self.scale.set(float(self.variable.get())/self.resolution) @@ -775,8 +774,9 @@ class MyPysolScale: v = self._round(float(value)*self.resolution) self._set_text(v) self.variable.set(v) - if self.command: + if value != self.value and self.command: self.command(value) + self.value = value def pack(self, **kw): self.frame.pack(**kw) @@ -787,6 +787,15 @@ class MyPysolScale: self.scale.configure(**kw) config = configure + def state(self, v): + self.scale.state(statespec=(v,)) + self.label.state(statespec=(v,)) + + def get(self): + return self.variable.get() + def set(self, v): + self.variable.set(v) + class TkinterScale(Tkinter.Scale): def __init__(self, parent, **kw):