From 24c0b00c0d16e7d5d55ec33c658a2c899ed21f73 Mon Sep 17 00:00:00 2001
From: skomoroh <skomoroh@efabe8c0-fbe8-4139-b769-b5e6d273206e>
Date: Sun, 6 Mar 2011 06:33:38 +0000
Subject: [PATCH] * scalable cards (req: PIL >= 1.1.7)

git-svn-id: file:///home/shlomif/Backup/svn-dumps/PySolFC/svnsync-repos/pysolfc/PySolFC/trunk@263 efabe8c0-fbe8-4139-b769-b5e6d273206e
---
 AUTHORS                              |   7 +
 pysollib/app.py                      |  18 ++-
 pysollib/game.py                     | 227 +++++++++++++++++++++------
 pysollib/games/auldlangsyne.py       |   1 -
 pysollib/games/curdsandwhey.py       |   1 -
 pysollib/games/diplomat.py           |   1 -
 pysollib/games/fortythieves.py       |   1 -
 pysollib/games/grandduchess.py       |   1 -
 pysollib/games/gypsy.py              |   1 -
 pysollib/games/harp.py               |   1 -
 pysollib/games/larasgame.py          |   1 -
 pysollib/games/mahjongg/mahjongg.py  |   4 +-
 pysollib/games/mahjongg/mahjongg1.py |   2 +-
 pysollib/games/mahjongg/shisensho.py |  22 ++-
 pysollib/games/pasdedeux.py          |   1 -
 pysollib/games/pushpin.py            |   2 +-
 pysollib/games/royalcotillion.py     |   1 -
 pysollib/games/special/poker.py      |   9 +-
 pysollib/games/sthelena.py           |   1 -
 pysollib/games/sultan.py             |   2 +-
 pysollib/games/tournament.py         |   1 -
 pysollib/games/ultra/hanafuda.py     |  47 +++---
 pysollib/games/ultra/hexadeck.py     |  13 +-
 pysollib/games/zodiac.py             |   2 -
 pysollib/images.py                   | 196 +++++++++++++++--------
 pysollib/layout.py                   |  11 +-
 pysollib/mfxutil.py                  |   8 +-
 pysollib/options.py                  |  33 +++-
 pysollib/settings.py                 |   4 +-
 pysollib/stack.py                    | 225 ++++++++++++++++++--------
 pysollib/tile/card.py                |  11 ++
 pysollib/tile/menubar.py             |  96 +++++++++--
 pysollib/tile/selectcardset.py       | 102 +++++++++++-
 pysollib/tile/tkcanvas.py            |  28 +++-
 pysollib/tile/tkutil.py              |  37 +++--
 pysollib/tile/tkwidget.py            |  43 +++--
 36 files changed, 857 insertions(+), 304 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 233f27d3..a8310bd8 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -22,3 +22,10 @@ Matthew Hohlfeld <hohlfeld@cs.ucsd.edu>
 Nicola Larosa
 John Stoneham <obijohn99@aol.com>
 David Svoboda <svoboda@users.sourceforge.net>
+
+
+Translations
+============
+
+Holger Schäkel <Holger@Schaekel-row.de> (de)
+Jerzy Trzeciak <artusek@wp.pl> (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, '<Unmap>', self._unmapHandler)
-        bind(self.canvas, '<Configure>', self.configureHandler, add=True)
+        bind(self.canvas, '<Configure>', 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/<cs_type>)
+                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('<Configure>') # 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):