pykiss

Python Kisekae set viewer
git clone https://code.literati.org/pykiss.git
Log | Files | Refs | LICENSE

kiss.py (23866B)


      1 #!/usr/bin/python
      2 # Copyright (C) 2003 Sean R. Lynch <seanl@chaosring.org>
      3 #
      4 # This program is free software; you can redistribute it and/or modify
      5 # it under the terms of the GNU General Public License as published by
      6 # the Free Software Foundation; either version 2 of the License, or
      7 # (at your option) any later version.
      8 #
      9 # This program is distributed in the hope that it will be useful,
     10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 # GNU General Public License for more details.
     13 #
     14 # You should have received a copy of the GNU General Public License
     15 # along with this program; if not, write to the Free Software
     16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
     17 
     18 import re, sys, os, time, bisect, random, struct, string
     19 from kissparser import KissParser
     20 from kisscompiler import EventCompiler
     21 import Numeric
     22 
     23 class ParseError(Exception): pass
     24 
     25 class Variable:
     26     """A variable."""
     27     def __init__(self, compiler, name, value=0):
     28         self.compiler = compiler
     29         self.name = name
     30         self.value = value
     31 
     32     def __str__(self):
     33         return self.compiler.varname('var_', self.name, 0)
     34     
     35     
     36 class MovementHandler:
     37     def __init__(self):
     38         self.handler0 = None # was no, is no
     39         self.handler1 = None # was no, is yes
     40         self.handler2 = None # was yes, is no
     41         self.handler3 = None # was yes, is yes
     42 
     43     def test(self):
     44         """Run the actual test. Should be overridden."""
     45         pass
     46     
     47     def setup(self):
     48         self.prev_result = self.test()
     49 
     50     def check(self):
     51         """Check the condition."""
     52         result = self.test()
     53         prev_result = self.prev_result
     54         self.prev_result = result
     55         if result:
     56             if prev_result:
     57                 if self.handler3 is not None: self.handler3()
     58             elif self.handler1 is not None:
     59                 self.handler1()
     60         else:
     61             if prev_result:
     62                 if self.handler2 is not None: self.handler2()
     63             elif self.handler0 is not None: self.handler0()
     64 
     65 
     66 
     67 class Collider(MovementHandler):
     68     """Keep track of collisions between cels"""
     69     def __init__(self, cel1, cel2):
     70         MovementHandler.__init__(self)
     71         self.cel1 = cel1
     72         self.cel2 = cel2
     73 
     74     def __repr__(self):
     75         return 'Collider("%s", "%s")' % (self.cel1.name, self.cel2.name)
     76 
     77     def test(self):
     78         #print "Checking for collision between %s and %s" % (self.cel1.name, self.cel2.name)
     79 
     80         # Only check if both cels are visible
     81         if not (self.cel1.is_visible() and self.cel2.is_visible()): return 0
     82 
     83         return self.cel1.collide(self.cel2)
     84 
     85 class Inout(MovementHandler):
     86     """Keep track of in/out movement"""
     87     def __init__(self, obj1, obj2):
     88         MovementHandler.__init__(self)
     89         self.obj1 = obj1
     90         self.obj2 = obj2
     91 
     92     def test(self):
     93         result = self.obj1.rectcollide(self.obj2)
     94         #print self.obj1.num, self.obj2.num, result
     95         return result
     96     
     97         
     98 class Object:
     99     def __init__(self, kiss, num, fixval):
    100         self.kiss = kiss
    101 	self.num = num
    102 	self.pos = [(0, 0)] * 10
    103         self.cnfpos = [(0, 0)] * 10 # Configured position
    104 	self.cels = []
    105         self.fixval = fixval
    106         self.colliders = []
    107 
    108         # Handlers
    109         self.unfix_handler = None
    110         self.catch_handler = None
    111         self.drop_handler = None
    112         self.fixcatch_handler = None
    113         self.fixdrop_handler = None
    114         self.press_handler = None
    115         self.release_handler = None
    116 
    117     def __repr__(self):
    118         return '#%d' % self.num
    119 
    120     def add_cel(self, cel):
    121 	self.cels.append(cel)
    122     
    123     def map(self):
    124         for cel in self.cels:
    125             cel.mapped = 1
    126 
    127         self.check_collisions()
    128 
    129     def unmap(self):
    130         for cel in self.cels:
    131             cel.mapped = 0
    132 
    133         self.check_collisions()
    134 
    135     def altmap(self):
    136         for cel in self.cels:
    137             cel.mapped = not cel.mapped
    138 
    139         self.check_collisions()
    140 
    141     def setfixval(self, fixval):
    142         self.fixval = fixval
    143 
    144     def is_fixed(self):
    145         return self.fixval > 0
    146 
    147     def is_moved(self):
    148         return self.pos[self.kiss.set] == self.cnfpos[self.kiss.set]
    149 
    150     def move(self, pos):
    151         """Move the object to an absolute position and check collisions"""
    152         self.set_pos(pos)
    153         self.check_collisions()
    154 
    155     def set_pos(self, (x, y)):
    156         w = self.kiss.width
    157         h = self.kiss.height
    158         l = w
    159         t = h
    160         r = 0
    161         b = 0
    162         for cel in self.cels:
    163             x1 = x + cel.xoffset
    164             y1 = y + cel.yoffset
    165             if x1 < l: l = x1
    166             if y1 < t: t = y1
    167             if x1 + cel.width > r: r = x1 + cel.width
    168             if y1 + cel.height > b: b = y1 + cel.height
    169 
    170         if l < 0: x -= l
    171         elif r > w: x += (w-r) 
    172         if t < 0: y -= t
    173         elif b > h: y += (h-b)
    174         
    175         self.pos[self.kiss.set] = (x, y)
    176 
    177     def get_pos(self):
    178         return self.pos[self.kiss.set]
    179 
    180     def rectcollide(self, other):
    181         for cel1 in self.cels:
    182             if not cel1.is_visible(): continue
    183             for cel2 in other.cels:
    184                 #print cel1, cel2
    185                 if cel2.is_visible():
    186                     if cel1.quick_collide(cel2): return 1
    187                     
    188         return 0
    189 
    190     def set_transparent(self, reloffset):
    191         for cel in self.cels: cel.set_transparent(reloffset)
    192 
    193     def setup_collisions(self):
    194         for collision in self.colliders:
    195             collision.setup()
    196 
    197     def check_collisions(self):
    198         for collision in self.colliders:
    199             collision.check()
    200 
    201 	
    202 class Cel:
    203     def __init__(self, name, obj, palfile, sets):
    204 	self.name = name
    205 	self.obj = obj
    206 	self.palfile = palfile
    207 	self.transparent = 0
    208         self.mapped = 1
    209 
    210         # Handlers
    211         self.unfix_handler = None
    212         self.catch_handler = None
    213         self.drop_handler = None
    214         self.fixcatch_handler = None
    215         self.fixdrop_handler = None
    216         self.press_handler = None
    217         self.release_handler = None
    218 
    219         if sets is None: self.visible = [1] * 10
    220         else:
    221             self.visible = [0] * 10
    222             for set in sets:
    223                 self.visible[set] = 1
    224         
    225 	self.load()
    226 
    227     #def __repr__(self):
    228     #    return '<%s "%s" in #%d>' % (self.__class__.__name__, self.name, self.obj.num)
    229 
    230     def set_transparent(self, reloffset):
    231         # This may need to be overridden
    232         self.transparent += reloffset
    233         if self.transparent > 255: self.transparent = 255
    234         if self.transparent < 0: self.transparent = 0
    235 
    236     def collide(self, other):
    237         # First, check if the rectangles overlap
    238         rect = self.quick_collide(other)
    239         if rect:
    240             l1, t1, r1, b1 = self.get_rect()
    241             l2, t2, r2, b2 = other.get_rect()
    242             l, t, r, b = rect
    243 
    244             # Use the GUI's pixel collision detection
    245             return self.pixcollide(other, (l-l1, t-t1), (l-l2, t-t2), (r-l, b-t))
    246 
    247         return 0
    248 
    249     def quick_collide(self, other):
    250         l1, t1, r1, b1 = self.get_rect()
    251         l2, t2, r2, b2 = other.get_rect()
    252         # Pygame would be nice for this, but we don't depend on pygame
    253         l = max(l1, l2)
    254         t = max(t1, t2)
    255         r = min(r1, r2)
    256         b = min(b1, b2)
    257         if l >= r or t >= b: return None
    258         return l, t, r, b
    259 
    260     def load(self):
    261         mode, data, (self.width, self.height), (self.xoffset, self.yoffset) \
    262               = load_cel(self.name)
    263         print len(data), self.width, self.height, mode
    264         self.convert(mode, data)
    265 
    266     def get_rect(self):
    267         x, y = self.obj.get_pos()
    268         x += self.xoffset
    269         y += self.yoffset
    270         return x, y, x+self.width, y+self.height
    271 
    272     def set_palette(self, palette): pass
    273         
    274     def convert(self):
    275         """Meant to be overridden."""
    276         pass
    277 
    278     def get_pos(self):
    279         pos = self.obj.get_pos()
    280         #print pos
    281         if pos is not None:
    282             return pos[0] + self.xoffset, pos[1] + self.yoffset
    283 
    284     def map(self):
    285         self.mapped = 1
    286         self.obj.check_collisions()
    287 
    288     def unmap(self):
    289         self.mapped = 0
    290         self.obj.check_collisions()
    291 
    292     def altmap(self):
    293         self.mapped = not self.mapped
    294         self.obj.check_collisions()
    295 
    296     def is_visible(self):
    297         return self.mapped and self.visible[self.obj.kiss.set]
    298 
    299     def is_fixed(self):
    300         return self.obj.is_fixed()
    301 
    302     def is_moved(self):
    303         return self.obj.is_moved()
    304 
    305 
    306 def load_cel(filename):
    307     f = open(filename)
    308 	
    309     header = f.read(32)
    310     if header[:4] == 'KiSS':
    311         bpp, reserved, width, height, xoffset, yoffset = struct.unpack('<BHHHHH', header[5:16])
    312         data = f.read()
    313     else:
    314         bpp = 4
    315         xoffset = yoffset = 0
    316         width, height = struct.unpack('<HH', header[:4])
    317         data = header[4:] + f.read()
    318 	    
    319     f.close()
    320 
    321     if bpp == 4:
    322         mode = 'P'
    323         data = Numeric.fromstring(data, Numeric.UInt8)
    324         outdata = Numeric.zeros((width*height,), Numeric.UInt8)
    325         step = (width+1)/2
    326         step1 = width/2
    327         for y in range(height):
    328             outdata[y*width:y*width+width:2] = (data[y*step:y*step+step] >> 4).astype(Numeric.UInt8)
    329             outdata[y*width+1:y*width+width:2] = (data[y*step:y*step+step1] & 15).astype(Numeric.UInt8)
    330             
    331         outdata = outdata.tostring()
    332     elif bpp == 8:
    333         mode = 'P'
    334         outdata = data
    335     elif bpp == 32:
    336         mode = 'RGBA'
    337         outdata = Numeric.fromstring(data, Numeric.UInt8)
    338         r = outdata[::4].copy()
    339         outdata[::4] = outdata[2::4].copy()
    340         outdata[2::4] = r
    341         outdata = outdata.tostring()
    342     else: raise ValueError, "Unimplemented bpp %d" % bpp
    343 
    344     return mode, outdata, (width, height), (xoffset, yoffset)
    345 
    346 
    347 def load_palette(filename):
    348     f = open(filename)
    349     header = f.read(32)
    350     if header[:5] != 'KiSS\x10':
    351         bits = 12
    352         ncols = 16
    353         ngroups = 10
    354         data = header + f.read()
    355     else:
    356         bits, blah, ncols, ngroups = struct.unpack('<BHHH', header[5:12])
    357         data = f.read()
    358 
    359     f.close()
    360     pal = []
    361     i = 0
    362     if bits == 24:
    363         for group in range(ngroups):
    364             grp = [None] * ncols
    365             for col in range(ncols):
    366                 r, g, b = struct.unpack('<BBB', data[i:i+3])
    367                 grp[col] = (r, g, b)
    368                 i += 3
    369                 
    370             pal.append(grp)
    371 
    372     elif bits == 12:
    373         for group in range(ngroups):
    374             grp = [None] * ncols
    375             for col in range(ncols):
    376                 r, g = struct.unpack('<BB', data[i:i+2])
    377                 b = r & 15
    378                 r >>= 4
    379                 grp[col] = (r<<4, g<<4, b<<4)
    380                 i += 2
    381                 
    382             pal.append(grp)
    383 
    384     # Copy all remaining groups from group 0
    385     while len(pal) < 10: pal.append(pal[0])
    386     return pal
    387 
    388 
    389 class KiSS:
    390     def __init__(self, cnf, canvas=None, cel_class=Cel):
    391 	self.objs = {}
    392         # Cel names are per set
    393 	self.cels = {}
    394         self.cellist = [[], [], [], [], [], [], [], [], [], []]
    395 	self.cel_class = cel_class
    396 	self.canvas = canvas
    397         self.width = 448
    398         self.height = 320
    399         self.bordercol = 0
    400         self.handlers = {}
    401         self.alarms = {}
    402         self.timers = []
    403         self.palgroup = [0] * 10 # The palette group for each set
    404         self.palettes = []      # My palettes
    405         self.compiler = EventCompiler(self)        
    406 	parser = KissParser(self)
    407         parser.parse(os.path.basename(cnf))
    408 
    409         # Reverse the cel lists to make sure bottom cels are drawn first
    410         for l in self.cellist: l.reverse()
    411 
    412     # methods called by the parser
    413     def is_action(self, name):
    414         return self.compiler.cmds.has_key(name)
    415     
    416     def get_obj(self, num):
    417         try: return self.objs[num]
    418         except KeyError: return None
    419 
    420     def get_var(self, name):
    421         try: var = self.vars[name]
    422         except KeyError:
    423             var = Variable(name)
    424             self.vars[name] = var
    425             
    426         return var
    427 
    428     def set_palette(self, set, palnum):
    429         self.palgroup[set] = palnum
    430 
    431     def add_event(self, name, args, actions):
    432         handler = self.compiler.compile('%s%r' % (name, args), actions)
    433 
    434         if handler is None:
    435             self.warn("Empty event: %s%r: %r" % (name, args, actions))
    436             return
    437 
    438         if name == "label":
    439             self.compiler.add_label(args[0], handler)
    440         elif name == "alarm":
    441             self.alarms[args[0]] = handler
    442         elif name in ("unfix", "map", "unmap", "catch", "fixcatch", "drop", "fixdrop", "press", "release"):
    443             what = args[0]
    444             # There should be a list called blah_handlers on objects
    445             attr = '%s_handler' % name
    446             if isinstance(what, Object):
    447                 if getattr(what, attr) is not None:
    448                     self.warn("Multiple handlers for %s%r" % (name, args))
    449                 else: setattr(what, attr, handler)
    450             else:
    451                 try: what = self.cels[what]
    452                 except KeyError: self.warn("Unknown cel: %s" % what)
    453                 else:
    454                     if getattr(what.obj, attr) is not None:
    455                         self.warn("Multiple handlers for %s%r" % (name, args))
    456                     else: setattr(what, attr, handler)
    457         elif name in ("initialize", "set", "col", "begin", "end", "version"):
    458             self.handlers[name, args] = handler
    459         elif name in ("apart", "collide"):
    460             # Handle a collision event
    461             name1, name2 = args
    462             name1 = name1.lower()
    463             name2 = name2.lower()
    464 
    465             try:
    466                 cel1 = self.cels[name1]
    467                 cel2 = self.cels[name2]
    468             except KeyError: return
    469             obj1 = cel1.obj
    470             obj2 = cel2.obj
    471             
    472             # See if there is an existing handler
    473             for collider in obj1.colliders:
    474                 if not isinstance(collider, Collider): continue
    475                 if collider.cel1 == cel1 and collider.cel2 == cel2 or \
    476                    collider.cel2 == cel1 and collider.cel1 == cel2: break
    477             else:
    478                 collider = Collider(cel1, cel2)
    479                 obj1.colliders.append(collider)
    480                 obj2.colliders.append(collider)
    481                     
    482             if name == "apart":
    483                 if collider.handler2 is not None:
    484                     raise ParseError, "Two apart handlers for %s and %s" % \
    485                           (cel1.name, cel2.name)
    486                 collider.handler2 = handler
    487             else:
    488                 if collider.handler1 is not None:
    489                     raise ParseError, "Two collide handlers for %s and %s" % \
    490                           (cel1.name, cel2.name)
    491                 collider.handler1 = handler
    492 
    493 	    print cel1.name, cel2.name
    494 
    495         elif name in ("in", "out", "stillin", "stillout"):
    496             obj1, obj2 = args
    497 
    498             for inout in obj1.colliders:
    499                 if not isinstance(inout, Inout): continue
    500                 if (inout.obj1 == obj1 and inout.obj2 == obj2) or \
    501                    (inout.obj2 == obj1 and inout.obj1 == obj2): break
    502             else:
    503                 inout = Inout(obj1, obj2)
    504                 obj1.colliders.append(inout)
    505                 obj2.colliders.append(inout)
    506 
    507             if name == "in":
    508                 if inout.handler1 is not None:
    509                     raise ParseError, "Two in handlers for #%d and #%d" % \
    510                           (obj1.num, obj2.num)
    511                 inout.handler1 = handler
    512             elif name == "out":
    513                 if inout.handler2 is not None:
    514                     raise ParseError, "Two out handlers for #%d and #%d" % \
    515                           obj1.num, obj2.num
    516                 inout.handler2 = handler
    517             elif name == "stillin":
    518                 if inout.handler3 is not None:
    519                     raise ParseError, "Two stillin handlers for #%d and #%d" % \
    520                           obj1.num, obj2.num
    521                 inout.handler3 = handler
    522             else:
    523                 if inout.handler0 is not None:
    524                     raise ParseError, "Two stillout handlers for #%d and #%d" % \
    525                           obj1.num, obj2.num
    526                 inout.handler0 = handler
    527 
    528         elif self.handlers.has_key((name, args)):
    529             self.warn("Duplicate handler for %s%r" % (name, args))
    530         else: raise ValueError, "Unknown event: %s%r" % (name, args)
    531 
    532     def set_size(self, (width, height)):
    533 	self.width = width
    534         self.height = height
    535 	
    536     def add_palette(self, palfile):
    537         self.palettes.extend(load_palette(palfile))
    538 	
    539     def set_border(self, bordercol):
    540 	self.bordercol = bordercol
    541 	
    542     def add_cel(self, name, objnum, fixval, palfile, groups, trans):
    543         if groups is None: groups = range(10)
    544 
    545         try: obj = self.objs[objnum]
    546         except KeyError:
    547             obj = Object(self, objnum, fixval)
    548             self.objs[objnum] = obj
    549 
    550         try:
    551             cel = self.cel_class(name, obj, palfile, groups)
    552         except IOError:
    553             self.warn("No such cel: %s" % name)
    554             return
    555         
    556         if trans != 0: cel.set_transparent(trans)
    557         #print 'adding cel', cel.name
    558         if self.cels.has_key(name):
    559             print >> sys.stderr, "Ambiguous cel name: %s" % name
    560         else:
    561 	    self.cels[name] = cel
    562         obj.add_cel(cel)
    563 
    564         for group in groups:
    565             self.cellist[group].append(cel)
    566 	
    567     def set_objpos(self, objnum, set, pos):
    568         try: obj = self.objs[objnum]
    569         except KeyError: return
    570         obj.pos[set] = pos
    571         obj.cnfpos[set] = pos
    572 
    573     # Methods called by the GUI
    574     def pulse(self):
    575         """Call this at least every 100 ms"""
    576         now = time.time()
    577         flag = 0
    578         while self.timers:
    579             t, num = self.timers[0]
    580             if t > now: break
    581             del self.timers[0]
    582             try: alarm = self.alarms[num]
    583             except KeyError: pass
    584             else:
    585                 alarm()
    586                 flag = 1
    587 
    588         if flag:
    589             self.draw()
    590             
    591     def start(self):
    592         self.set = 0
    593         self.dragging = None
    594         self.pressed = None
    595         self.change_palette(self.palgroup[self.set])
    596         for obj in self.objs.values():
    597             obj.setup_collisions()
    598         self.handle('initialize')
    599         self.handle('version', 1)
    600         self.handle('version', 2)
    601         self.handle('version', 3)
    602         self.handle('begin')
    603         self.handle('set', 0)
    604         self.draw()
    605 
    606     def end(self):
    607         self.handle('end')
    608         
    609     def get_cel_at(self, (x, y)):
    610         cellist = self.cellist[self.set]
    611         for i in range(len(cellist)-1, -1, -1):
    612             cel = cellist[i]
    613             if not cel.mapped: continue
    614             x1, y1 = cel.get_pos()
    615             w = cel.width
    616             h = cel.height
    617             #print cel.name, x, y, x1, y1, w, h
    618             if x >= x1 and y >= y1 and x < x1+w and y < y1+h:
    619                 if cel.is_pixel_set((x-x1, y-y1)): return cel
    620         else: return None
    621 
    622     def unfix(self, pos):
    623         cel = self.get_cel_at(pos)
    624         cel.obj.fixval = 1
    625         self.press(pos)
    626     
    627     def make_transparent(self, pos):
    628         cel = self.get_cel_at(pos)
    629         if cel.transparent > 0: cel.set_transparent(-255)
    630         else: cel.set_transparent(255)
    631         self.draw()
    632         
    633     def press(self, (x, y)):
    634         """Mouse button pressed"""
    635 
    636         cel = self.get_cel_at((x, y))
    637         if cel is None: return
    638         self.pressed = cel
    639         obj = cel.obj
    640         print "#%d.%d %s" % (obj.num, obj.fixval, cel.name)
    641         if obj.fixval == 0:
    642             x1, y1 = obj.get_pos()
    643             func = cel.catch_handler
    644             if func is not None: func()
    645             func = obj.catch_handler
    646             if func is not None: func()
    647             # Store the object and the position offset
    648             self.dragging = x1-x, y1-y
    649             self.canvas.start_drag()
    650         else:
    651             func = cel.catch_handler
    652             if func is not None: func()
    653             func = obj.catch_handler
    654             if func is not None: func()
    655             obj.fixval -= 1
    656             if obj.fixval == 0:
    657                 func = obj.unfix_handler
    658                 if func is not None: func()
    659                 for cel in obj.cels:
    660                     func = cel.unfix_handler
    661                     if func is not None: func()
    662 
    663         func = cel.press_handler
    664         if func is not None: func()
    665         func = obj.press_handler
    666         if func is not None: func()
    667 
    668 	self.draw()
    669 
    670     def release(self, (x, y)):
    671         """Mouse button released"""
    672 	cel = self.pressed
    673 	if cel is None: return
    674 	obj = cel.obj
    675         # Generate events that don't care about fixval
    676         func = cel.release_handler
    677         if func is not None: func()
    678         func = obj.release_handler
    679         if func is not None: func()
    680         self.pressed = None
    681         if self.dragging: 
    682             self.canvas.end_drag()
    683             xoff, yoff = self.dragging
    684             self.dragging = None
    685             obj.move((x+xoff, y+yoff))
    686             func = cel.drop_handler
    687             if func is not None: 
    688 	        func()
    689             func = obj.drop_handler
    690             if func is not None: 
    691 	        func()
    692         elif obj.fixval:
    693             # Object is fixed
    694             func = cel.fixdrop_handler
    695             if func is not None: func()
    696             func = obj.fixdrop_handler
    697             if func is not None: func()
    698 
    699 	self.draw()
    700         
    701     def drag(self, (x, y)):
    702         """Mouse drag"""
    703         xoff, yoff = self.dragging
    704 	cel = self.pressed
    705         cel.obj.set_pos((x+xoff, y+yoff))
    706         self.draw()
    707 
    708     # Internal
    709     def handle(self, name, *args):
    710         #print "handling", name, args
    711         try: handler = self.handlers[name, args]
    712         except KeyError: return
    713 
    714         handler()
    715             
    716         self.draw()
    717 
    718     def changeset(self, set):
    719         self.set = set
    720         self.change_palette(self.palgroup[set])
    721         self.draw()
    722         
    723         # Reset all colliders
    724         for obj in self.objs.values():
    725             obj.setup_collisions()
    726                 
    727         self.handle("set", set)
    728 
    729     def change_palette(self, palnum):
    730         if not self.palettes: return
    731         if palnum >= len(self.palettes):
    732             print >> sys.stderr, "No such palette: %d" % palnum
    733             return
    734         self.palgroup[self.set] = palnum
    735         for cel in self.cellist[self.set]:
    736             cel.set_palette(self.palettes[cel.palfile*10+self.palgroup[palnum]])
    737 
    738     def get_bordercol(self):
    739         if self.palettes:
    740             return self.palettes[self.palgroup[self.set]][self.bordercol]
    741         return (0, 0, 0)
    742 
    743     def draw(self):
    744         self.canvas.draw(self.cellist[self.set])
    745     
    746     def timer(self, number, duration):
    747         #print "timer(%d, %d)" % (number, duration)
    748         now = time.time()
    749         timers = self.timers
    750         for i in range(len(self.timers)):
    751             if timers[i][1] == number:
    752                 del timers[i]
    753                 break
    754             
    755         if duration: bisect.insort(timers, (now + int(duration)/1000.0, number))
    756 
    757     def setfix(self, obj, fixval):
    758         obj.fixval = fixval
    759         if fixval == 0:
    760             self.handle("unfix", obj)
    761             for cel in obj.cels:
    762                 if cel.is_visible():
    763                     self.handle("unfix", cel.name)
    764 
    765     def changecol(self, palette):
    766         self.change_palette(palette)
    767         self.handle("col", palette)
    768 
    769     def warn(self, message):
    770         print >> sys.stderr, "Warning:", message
    771 
    772     def notify(self, message):
    773         print >> sys.stderr, "Notify:", message
    774 
    775     def debug(self, message):
    776         print >> sys.stderr, "Debug:", message