All computer source code presented on this page, unless it includes attribution to another author, is provided by Ed Halley under the Artistic License. Use such code freely and without any expectation of support. I would like to know if you make anything cool with the code, or need questions answered.
python/
    bindings.py
    boards.py
    buzz.py
    caches.py
    cards.py
    constraints.py
    csql.py
    english.py
    getch.py
    getopts.py
    gizmos.py
    goals.py
    improv.py
    interpolations.py
    namespaces.py
    nihongo.py
    nodes.py
    octalplus.py
    patterns.py
    physics.py
    pids.py
    pieces.py
    quizzes.py
    recipes.py
    relays.py
    romaji.py
    ropen.py
    sheets.py
    stores.py
    strokes.py
    subscriptions.py
    svgbuild.py
    testing.py
    things.py
    timing.py
    ucsv.py
    useful.py
    uuid.py
    vectors.py
    weighted.py
java/
    CSVReader.java
    CSVWriter.java
    GlobFilenameFilter.java
    RegexFilenameFilter.java
    StringBufferOutputStream.java
    ThreadSet.java
    Throttle.java
    TracingThread.java
    Utf8ConsoleTest.java
    droid/
        ArrangeViewsTouchListener.java
        DownloadFileTask.java
perl/
    CVQM.pm
    Kana.pm
    Typo.pm
cxx/
    CCache.h
    equalish.cpp
Download things.py
# things - a generic "small simulation" application framework for pygame

'''

A generic "small simulation" application framework for pygame.

ABSTRACT

The "things" library is a very simple generic application framework,
organizing interactive objects for rendering.  Partners with the pygame
library for the actual rendering and device interaction capabilities.

The simulation is built on classes that extend a pair of basic root
classes, Item and Thing.  Items can represent anything, while Things
represent visible, tangible entities that are positioned or move around
in the simulation.

AUTHOR

    Ed Halley (ed@halley.cc) 6 November 2007

'''

import os
import glob
import math
import random
import pygame ; from pygame.locals import *
import vectors ; from vectors import *
import goals ; from goals import *

try:
    pass #import psyco ; psyco.full()
except:
    pass

class Context (object): pass

#----------------------------------------------------------------------------

class Item (object):

    '''The Item is the simplest abstract root class in the library.

    Items have style flags, goals, and define the basic four components
    of a simulation loop: event(), think(), visit(), and paint().

    * Style flags: every item has a set of keywords as style flags;
                   the actual keywords are mostly application defined,
                   though this library uses keywords like "pickable" to
                   enable or disable built-in interaction behaviors.

    * Goals: each item has its own independent "bunch-queue" of future
             goals that are being worked.  Goals can be "added" to let
             them run in parallel, or they can be "appended" to wait for
             earlier subgoals to complete fully before starting.

    * Simulation loop:

        + event(): called as required to respond to pygame or application
                   events as the simulation runs

        + think(): may be called once per simulation loop to allow items
                   to make progress on their goals or other tasks

        + visit(): may be called once per simulation loop to collect
                   or cull any rendering state information required

        + paint(): may be called once per rendering pass to perform the
                   actual drawing work required, such as to pygame or
                   opengl surfaces

    '''

    def __init__(self):
        self.style = set([])
        self.goals = [ ]

    def is_style(self, style):
        return style in self.style

    def set_style(self, style, value=True):
        if not value:
            if style in self.style:
                self.style.remove(style)
        else:
            self.style.add(style)

    def add_goal(self, goal):
        '''Adds a new goal to the goal queue, which can progress in
        parallel with other added goals.
        '''
        if not self.goals:
            return self.append_goal(goal)
        self.goals[-1].add(goal)
        return self

    def append_goal(self, goal):
        '''Appends a mark, followed by new goal to the goal queue,
        ensuring all prior goals have been completed before this goal
        begins.
        '''
        self.goals.append( ParallelGoal(goal) )
        return self

    def wait(self, when=None, dt=0., todo=None):
        '''Appends a mark to the goal queue, to ensure all prior goals
        have been completed before later goals begin.
        Optionally specify an absolute time to finish with a "when" value.
        Optionally specify a relative time to finish with a "dt" value.
        (If both are given, when+dt is the finish target.)
        '''
        if when is None and dt == 0.:
            dt = 0.00001
        self.goals.append( TimeGoal(when=when, dt=dt, todo=todo) )
        self.goals.append( ParallelGoal( ) )
        return self

    def stop(self):
        '''Removes all goals, complete or not, from the goal queue.'''
        self.goals = [ ]
        return self

    def is_idle(self):
        '''Returns True if the item has no active goals at all.'''
        return not self.goals

    def event(self, event):
        '''Receive a pygame or application event for processing.
        Any override of this method should also call this version.
        '''
        pass

    def think(self, context):
        '''Check the active goals on the goal queue for progress.
        Any override of this method should also call this version.
        '''
        while self.goals:
            if not self.goals[0].done():
                break
            self.goals.pop(0)

    def visit(self, context):
        '''Decide and digest any information required for processing
        or rendering in the other callbacks for each loop.
        Any override of this method should also call this version.
        '''
        pass

    def paint(self, context):
        '''Perform any rendering required for this item.
        Any override of this method should also call this version.
        '''
        #REVIEW: remove this method?
        pass

#----------------------------------------------------------------------------

class Thing (Item):

    '''A Thing is an Item with a location.

    This class extends the basic Item class, providing position
    information, a metaphysics model that lets one thing "follow" another,
    and capabilities of hit-testing.

    * Positioning: every thing has a "place" and an "angle" which
                   represent their position and orientation information
                   within a given frame of reference; methods move() and
                   turn() offer an easy way to add motion-oriented goals
                   so things can travel with minimal programming effort

    * Following: the position of a thing is relative to a reference
                 frame, which can be assigned to almost any other thing;
                 this allows things to appear connected in parent/child
                 relationships so only the parent motion must be specified

    * Hit-testing: mouse events are routed to each visible thing, taking
                   their distance from the viewer into account; each thing
                   can then decide if the pointer is actually intersecting
                   with the thing, or if the thing is not even interactive
    
    '''

    def __init__(self):
        super(Thing, self).__init__()
        self.frame = None
        self.joins = set([])
        self.place = V(0,0,0)
        self.angle = V(0,0,0) # euler angles
        self.xform = M() # overwritten on each visit 

    def hits(self, context, hither, yon):
        '''Called to see if the ray between hither and yon hits this thing.
        By default, we return None.  Override with hit testing logic, and
        return self if hit.
        '''
        return None

    def followers(self):
        '''Returns a list of our direct followers.
        These are the things which directly use us as a frame of reference.
        '''
        return list(self.joins)

    def follows(self, frame):
        '''Checks if we follow (directly or indirectly) a given frame.'''
        if self.frame == frame: return True
        if self.frame is None: return False
        return self.frame.follows(frame)

    def follow(self, frame):
        '''Assigns another thing as the frame of reference for this thing.
        Cyclical relationships (following oneself) are not allowed.
        '''
        if not isinstance(frame, Thing):
            raise TypeError, 'reference frame must be another Thing'
        if frame.follows(self):
            raise TypeError, 'cannot make circular reference frame'
        if self.frame is frame:
            return
        if self.frame:
            self.unfollow()
        self.place -= frame.xform.translation()
        self.angle -= frame.xform.rotation()
        self.frame = frame
        frame.admit(self)
        return self

    def unfollow(self):
        '''Disconnect the following frame of reference.
        Note that after disconnection, this thing will no longer get any
        calls to visit() or paint().
        '''
        self.frame.eject(self)
        self.place += self.frame.xform.translation()
        self.angle += self.frame.xform.rotation()
        self.frame = None
        return self

    def is_leader(self):
        '''True if there are any followers.'''
        return not self.joins

    def admit(self, follower):
        '''Connects the given follower to our own set of known followers.
        Usually not called directly; tell the follower to follow() instead.
        '''
        self.joins.add(follower)

    def eject(self, follower):
        '''Disconnects the given follower to our own set of known followers.
        Usually not called directly; tell the follower to unfollow() instead.
        '''
        if follower in self.joins:
            self.joins.remove(follower)

    def space(self):
        '''Returns the ultimate frame of reference, that which we follow
        but does not follow anything.  Usually a Space.'''
        if not self.frame:
            return self
        return self.frame.space()

    def visit(self, context):
        '''Called on each simulation loop to determine global
        transformations for each thing.  If you wish to override this
        method to collect other context information, call this
        base version also.
        '''
        context.push()
        context.journal(self)
        self.xform = context.get()
        for thing in self.joins:
            thing.visit(context)
        context.pop()

    def think(self, context):
        '''Allows for independent object decision-making behavior.
        The base method currently does no thinking in particular,
        but any override should call this base version also.
        '''
        super(Thing, self).think(context)
        joins = self.joins.copy()
        for thing in joins:
            thing.think(context)

    def paint(self, context):
        '''Allows for painting to the screen surface.
        An override can use the context information to facilitate this.
        The base method currently does no painting in particular,
        but any override should call this base version also.
        '''
        pass

    def move(self, places, relative=False, when=None, dt=0.0):
        '''Apply a new place coordinate, interpolated on a curve over time.
        If one vector is given, trace linearly from current to new place.
        If a list of vectors is given, trace a bezier path to the new place.
        '''
        if not self.frame:
            raise RuntimeError, 'cannot move thing with no reference frame'
        self.add_goal(ChangeGoal(self.place, places, relative=relative,
                                 when=when, dt=dt))
        return self

    def turn(self, angles, relative=False, when=None, dt=0.0):
        '''Apply a new orientation angle, interpolated on a curve over time.
        If one vector is given, trace linearly from current to new place.
        If a list of vectors is given, trace a bezier path to the new place.
        '''
        if not self.frame:
            raise RuntimeError, 'cannot turn thing with no reference frame'
        self.add_goal(ChangeGoal(self.angle, angles,
                                 relative=relative, when=when, dt=dt))
        return self

    def ping(self, thing, event=None, when=None, dt=0.0):
        '''Add a goal to send an event or trigger to another item.'''
        if not isinstance(event, pygame.Event):
            event = pygame.event.Event(USEREVENT, code=event)
        todo = lambda x,them=thing,that=event: them.event(that)
        self.add_goal(TimeGoal(when=when, dt=dt, todo=todo))
        return self

#----------------------------------------------------------------------------

class Space (Thing):

    def __init__(self, context=None):
        '''An ultimate container for a set of things.'''
        super(Space, self).__init__()
        if not context:
            context = MatrixContext()
        self.context = context
        self.picked = set([])
        self.moused = None
        self.camera = V.Z * 1000
        self.hyper = None

    def segue(self):
        '''A callback checked on every event loop.
        Returning a Space-based type, or a Space instance, switches spaces.
        Default version always returns self, indicating we should continue.
        A return of None would indicate a request to shut down the app,
        or let the app pick some new space instance to create instead.
        '''
        return self

    def modal(self):
        return False

    def follow(self, frame):
        '''A thing can follow a space, but a space cannot follow anything.
        Throws an error if this is attempted.
        '''
        raise TypeError, 'a Space cannot follow any other thing or space'

    def background(self, surface):
        '''A simple base implementation used for painting the background.'''
        surface.fill( (0x00, 0x80, 0x00) )

    def visit(self, surface):
        '''Does the thinking for the space itself, and asks all of the
        things in the space to think for themselves in arbitrary order.
        '''
        self.context.reset()
        self.context.media = surface
        super(Space, self).visit(self.context)
        if self.hyper:
            self.hyper.visit(surface)

    def think(self):
        '''Does the thinking for the space itself, and asks all of the
        things in the space to think for themselves in arbitrary order.
        '''
        super(Space, self).think(self.context)
        for thing in self.joins:
            thing.think(self.context)
        if self.hyper:
            self.hyper.think()

    def paint(self, surface):
        '''Does the painting for the space itself, and asks all of the
        things in the space to paint themselves in the proper ordering.
        '''
        self.context.media = surface
        visited = self.context.visited(self.camera)
        for entry in visited:
            (thing, transform, where, z) = entry
            if self is thing:
                self.background(surface)
            else:
                self.context.load(transform)
                thing.paint(self.context)
        if self.hyper:
            self.hyper.paint(surface)

    def hits(self, context, hither, yon):
        '''Asks the various things in the space to perform their
        hit-testing in the proper ordering as required.
        '''
        visited = context.visited(self.camera)
        for entry in reversed(visited):
            (thing, transform, where, z) = entry
            if self is thing:
                return self
            else:
                hit = thing.hits(context, hither, yon)
                if hit: return hit
        return None

    def event(self, event):
        '''Routes any event that happens in this space to the right things.
        '''
        if self.hyper:
            if self.hyper.segue() is None:
                self.hyper = None
            else:
                self.hyper.event(event)
                if self.hyper.modal():
                    return
        if event.type in (KEYDOWN, KEYUP):
            for thing in self.picked:
                thing.event(event)
        if event.type == MOUSEBUTTONDOWN:
            hither = V(event.pos[0], event.pos[1], 1000)
            yon = V(event.pos[0], event.pos[1], 0)
            thing = self.hits(self.context, hither, yon)
            if not thing:
                self.unpick()
            else:
                if thing is not self:
                    thing.event(event)
                    self.moused = thing
                if thing.is_style('pickable'):
                    if thing in self.picked:
                        self.unpick(thing)
                    else:
                        self.pick(thing)
        if event.type == MOUSEMOTION:
            if self.moused:
                self.moused.event(event)
        if event.type == MOUSEBUTTONUP:
            if self.moused:
                self.moused.event(event)
                self.moused = None

    def pick(self, thing):
        '''Adds a thing to the "picked" group.'''
        self.picked.add(thing)
        thing.set_style('picked')

    def unpick(self, thing=None):
        '''Removes a thing (or all things) from the "picked" group.'''
        if thing is None:
            for thing in self.picked.copy():
                self.unpick(thing)
            return
        if thing in self.picked:
            self.picked.remove(thing)
            thing.set_style('picked', False)

#----------------------------------------------------------------------------

class Twister (object):

    def __init__(self, steps=45):
        self.twist = { }
        self.steps = int(steps)

    def twist_image(original):
        '''Takes an image and returns a rotated version.'''
        if original in self.twist:
            return
        spun = { }
        for angle in range(0, 360, self.steps):
            center = original.get_rect().center
            image = pygame.transform.rotate(original, angle)
            rect = image.get_rect(center=center)
            spun[angle] = (image, rect)
        self.twist[original] = spun

    def get_twisted_image(original, angle):
        '''Returns the best twisted image for an arbitrary given angle.'''
        if not original in self.twist:
            self.twist_image(original)
        angle = (int(((angle+self.steps/2.)/self.steps))*self.steps) % 360
        return self.twist[original][angle]

#----------------------------------------------------------------------------

class Context (object):

    '''This abstract internal class is for collecting a view onto state
    information for rendering or other purposes.  A specific kind of
    context is created and used by the application to visit all known
    items, and prepare for painting, physics, and event-processing.
    '''

    def __init__(self, media=None):
        '''Sets up a new context for simulation logic.
        Optionally provide a pygame surface as the painting media.
        '''
        self.media = media
        self.reset()

    def reset(self):
        '''Prepare the context for another simulation loop.
        The transformation stack reverts to the identity matrix.
        The journal of visit() call results is cleared.
        '''
        self.visit = [ ]
        self.specs = { }

    def get(self):
        '''Retrieves the current transform state.'''
        return None

    def push(self):
        '''Saves this transform state on an internal stack.'''
        pass

    def pop(self):
        '''Returns to prior pushed transform state, or the identity state.'''
        pass

    def do(self, transform):
        '''Accumulates a change to the current transform state.'''
        pass

    def load(self, transform):
        '''Replaces the current transform state with a specific matrix.'''
        pass

    def point(self, p):
        '''Transforms a local-space point to its global-space location.'''
        pass

    def journal(self, thing):
        '''Keep track of the status of a thing, such as its world position.'''
        entry = [ thing ]
        self.visit.append( entry )
        self.specs[thing] = entry

    def visited(self, hither=None):
        '''Return a list of things, optionally sorted by a view distance.'''
        return self.visit

    def explain(self, thing):
        '''Retrieve the context information about a given thing.'''
        return self.specs.get(thing, None)

#----------------------------------------------------------------------------

class VectorContext (Context):

    '''This context supports only a 3D vector transform stack for every
    thing.  The things are controlled only by their place member; the angle
    members are completely ignored.  While this means that things have no
    rotation capabilities, and only an offset relative to their frame of
    reference, the computational power is much more streamlined for many
    objects on even low-powered devices.
    '''

    def __init__(self, media=None):
        '''Sets up a new context for simulation logic.
        Optionally provide a pygame surface as the painting media.
        '''
        super(VectorContext, self).__init__(media)
        self.hither = None

    def reset(self):
        '''Prepare the context for another simulation loop.
        The transformation stack reverts to the identity matrix.
        The journal of visit() call results is cleared.
        '''
        self.stack = [ V(0,0,0) ]
        super(VectorContext, self).reset()

    def get(self):
        '''Retrieves the current transform state.'''
        return M.translate(self.stack[-1])

    def push(self):
        '''Saves this transform state on an internal stack.'''
        self.stack.append(V(self.stack[-1]))

    def pop(self):
        '''Returns to prior pushed transform state, or the identity state.'''
        self.stack.pop()
        if not self.stack:
            self.stack = [ V() ]

    def do(self, transform):
        '''Accumulates a change to the current transform state.'''
        if isinstance(transform, M):
            transform = transform.translation()
        self.stack[-1] += transform

    def load(self, transform):
        '''Replaces the current transform state with a specific matrix.'''
        if isinstance(transform, M):
            transform = transform.translation()
        self.stack[-1] = transform

    def point(self, p):
        '''Transforms a local-space point to its global-space location.'''
        return p + self.stack[-1]

    def journal(self, thing):
        '''Keep track of the status of a thing, such as its world position.'''
        if thing.place:
            self.stack[-1] += thing.place
        transform = V(self.stack[-1])
        where = transform
        entry = [ thing, transform, where, where[2] ]
        # redundant transform/where above, but matches MatrixContext
        self.visit.append( entry )
        self.specs[thing] = entry

    def visited(self, hither=None):
        '''Return a list of things, optionally sorted by a view distance.'''
        self.visit.sort(cmp=lambda a,b: cmp(a[3],b[3]))
        if hither is not self.hither:
            self.hither = hither
        return self.visit

#----------------------------------------------------------------------------

class MatrixContext (Context):

    '''This context supports a full 4x4 matrix transform stack for every
    thing.  The things are controlled by their place and angle members.
    This means that each thing has a complete six degrees of freedom
    relative to their frame of reference.  Can be computationally slow on
    some low-powered devices.
    '''

    def __init__(self, media=None):
        '''Sets up a new context for simulation logic.
        Optionally provide a pygame surface as the painting media.
        '''
        super(MatrixContext, self).__init__(media)
        self.hither = None

    def reset(self):
        '''Prepare the context for another simulation loop.
        The transformation stack reverts to the identity matrix.
        The journal of visit() call results is cleared.
        '''
        self.stack = [ M() ]
        super(MatrixContext, self).reset()

    def get(self):
        '''Retrieves the current transform state.'''
        return M(self.stack[-1])

    def push(self):
        '''Saves this transform state on an internal stack.'''
        self.stack.append(M(self.stack[-1]))

    def pop(self):
        '''Returns to prior pushed transform state, or the identity state.'''
        self.stack.pop()
        if not self.stack:
            self.stack = [ M() ]

    def do(self, transform):
        '''Accumulates a change to the current transform state.'''
        self.stack[-1] *= transform

    def load(self, transform):
        '''Replaces the current transform state with a specific matrix.'''
        self.stack[-1] = transform

    def point(self, p):
        '''Transforms a local-space point to its global-space location.'''
        return p * self.stack[-1]

    def journal(self, thing):
        '''Keep track of the status of a thing, such as its world position.'''
        if thing.place:
            self.do(M.translate(thing.place))
        if thing.angle[2] != 0.0:
            self.do(M.rotate('Z', thing.angle[2]))
        transform = M(self.stack[-1])
        where = transform.translation() # V.O * transform
        entry = [ thing, transform, where, where[2] ]
        self.visit.append( entry )
        self.specs[thing] = entry

    def visited(self, hither=None):
        '''Return a list of things, optionally sorted by a view distance.'''
        self.visit.sort(cmp=lambda a,b: cmp(a[3],b[3]))
        if hither is not self.hither:
            self.hither = hither
        return self.visit

#----------------------------------------------------------------------------

class App (Item):

    __resources = { }

    def __init__(self, name, size=(800,480)):
        '''A basic application loop ready to employ Item and Thing classes
        using pygame features to draw and manage interaction.
        '''
        super(App, self).__init__()
        self.fps = 30
        self.quit = False
        self.keys = { K_LCTRL: False,  K_RCTRL: False,
                      K_LSHIFT: False, K_RSHIFT: False,
                      K_LMETA: False,  K_RMETA: False,
                      K_LALT: False,   K_RALT: False }
        pygame.init()
        self.resize( size )
        pygame.display.set_caption(name)
        self.space = None
        self.graph = { }
        self.clock = pygame.time.Clock()

    def reshape(self):
        #TODO:
        if self.mode & FULLSCREEN:
            self.mode &= ~FULLSCREEN
            screen = pygame.display.set_mode(self.size, self.mode)
        else:
            self.mode |= FULLSCREEN
            screen = pygame.display.set_mode(self.size, self.mode)
        self.screen = screen
        #self.context = VectorContext(screen)

    def resize(self, size=None):
        '''Sets up the basic window size. Also called if the user resizes.'''
        self.mode = HWSURFACE # | RESIZABLE
        self.size = size
        self.screen = pygame.display.set_mode(self.size, self.mode)
        #self.context = VectorContext(screen)

    @classmethod
    def image(cls, size, flags=0):
        if (flags & SRCALPHA):
            image = pygame.Surface(size, flags, 32).convert_alpha()
            image.fill( (0,0,0,0) )
        else:
            image = pygame.Surface( size, flags )
        return image

    @classmethod
    def find_resource(cls, name):
        for path in [ '.', 'res', 'resource', 'resources' ]:
            attempt = os.path.join(path, name)
            if os.path.exists(attempt):
                return attempt
        return None

    @classmethod
    def resource(cls, name, *args):
        if name in App.__resources:
            return App.__resources[name]
        resource = None
        found = cls.find_resource(name)
        if not found:
            raise ValueError, 'could not find resource %s' % name
        (file, ext) = os.path.splitext(found)
        if ext == '.ttf':
            name = name + repr(*args)
            if name in App.__resources:
                return App.__resources[name]
            resource = pygame.font.Font(found, *args)
        elif ext in [ '.jpg', '.png', '.gif' ]:
            image = pygame.image.load(found)
            convert = True
            if len(args): convert = args.pop(0)
            if convert:
                if ext == '.jpg':
                    image = image.convert()
                else:
                    image = image.convert_alpha()
            resource = image
        elif ext in [ '.txt' ]:
            f = open(found)
            if not f:
                raise ValueError, 'could not open %s for reading' % name
            resource = f.read()
            f.close()
        if not resource:
            raise ValueError, 'unknown resource type %s' % ext
        App.__resources[name] = resource
        return resource

    @classmethod
    def resources(cls, wildcard, *args):
        found = { }
        for path in [ '.', 'res', 'resource', 'resources' ]:
            attempt = os.path.join(path, wildcard)
            files = glob.glob(attempt)
            for file in files:
                name = os.path.basename(file)
                found[name] = cls.resource(file, *args)
        if not found:
            raise ValueError, 'could not find any resources %s' % wildcard
        return found

    def think(self):
        '''One space may be active at a time. The active space can think.'''
        if self.space:
            self.space.think()

    def visit(self):
        '''The active space is visited to collect rendering state data.'''
        #self.context.reset()
        if self.space:
            self.space.visit(self.screen)

    def paint(self):
        '''The active space is painted into the context.'''
        if self.space:
            self.space.paint(self.screen)

    def event_standard(self, event):
        '''Handles common application events such as requests to quit.'''
        if event.type == QUIT:
            self.quit = True
        if event.type == VIDEORESIZE:
            self.resize(event.size)

    def event_common(self, event):
        '''Handles common application events such as tracking shift-keys.'''
        if event.type == KEYDOWN:
            if event.key in self.keys:
                self.keys[event.key] = True
        if event.type == KEYUP:
            if event.key in self.keys:
                self.keys[event.key] = False

    def event_vista(self, event):
        '''Handles common Windows-platform events.'''
        if event.type == KEYDOWN:
            if event.key == K_F4:
                self.quit = True
            if event.key == K_KP_ENTER or event.key == K_RETURN:
                if self.keys[K_LMETA] or self.keys[K_RMETA]:
                    pass # self.reshape()

    def event_apple(self, event):
        '''Handles common Apple-platform events.'''
        # Command keys are K_LMETA and K_RMETA
        # KEYDOWN K_*META + Q for quit
        if event.type == KEYDOWN:
            if event.key == K_q:
                if self.keys[K_LMETA] or self.keys[K_RMETA]:
                    self.quit = True

    def event_maemo(self, event):
        '''Handles common Maemo-platform (e.g., Nokia N810) events.'''
        # KEYDOWN K_F6 is FULLSCREEN/window toggle
        # KEYDOWN K_F7 is zoomin/biggertext
        # KEYDOWN K_F8 is zoomout/smallertext
        # KEYDOWN K_F5 is appswitch
        # KEYDOWN K_F4 is menu
        # KEYDOWN K_RETURN is center of dpad
        # KEYDOWN K_ALTGR is Fn (a sticky mod)
        # KEYDOWN K_COMPOSE is Chr (a sticky mod)
        # (note that both shift keys give K_LSHIFT on N810)
        if event.type == KEYDOWN:
            if event.key == K_F6:
                pass # self.reshape()

    def route(self):
        '''Standard event routing and processing loop.'''
        for event in pygame.event.get():
            self.event_standard(event)
            self.event_common(event)
            #self.event_vista(event)
            self.event_apple(event)
            self.event_maemo(event)
            self.event(event)

    def event(self, event):
        if self.space:
            self.space.event(event)

    def segue(self):
        '''Watch for any transition from one space to another.'''
        if not self.space:
            return
        segue = self.space.segue()
        if segue is None:
            name = self.space.__class__.__name__
            if name in self.graph:
                segue = self.graph[name]
        if segue is self.space:
            return
        if segue is None:
            self.quit = True
            return
        if isinstance(segue, (list,tuple)):
            segue = random.choice(segue)
        if isinstance(segue, str):
            if segue in globals():
                segue = globals()[segue]
        if callable(segue):
            segue = segue()
        if isinstance(segue, Space):
            self.space = segue

    def thaw(self):
        '''This method is called before the main loop starts.'''
        pass

    def freeze(self):
        '''This method is called after the main loop quits.'''
        pass

    def run(self):
        '''The basic event loop of the application.
        Opens the window and continues until the user quits.
        May only be called once.
        '''
        self.thaw()
        while not self.quit:
            self.segue()
            self.route()
            self.think()
            self.visit()
            self.paint()
            pygame.display.flip()
            self.clock.tick(self.fps)
        self.freeze()
        pygame.quit()

#----------------------------------------------------------------------------

import interpolations

class _TestPad (Thing):

    def __init__(self, size=None, color=None):
        super(_TestPad, self).__init__()
        if not size:
            size = (200, 200)
        self.size = size
        self.nib = App.resource('spot-12x12.png')
        fat = self.nib.get_rect()
        self.fat = (fat.width/2,fat.height/2)
        self.pad = App.image(self.size, SRCALPHA)
        self.pad.fill( (0,0,0,0) )
        if color:
            self.pad.fill( color )
        self.stylus = V(0, 0)
        self.pressure = V(0.)

    def draw(self, where, pressure):
        d = distance(self.stylus, where)
        for t in range(0, int(d), self.fat[0]):
            stylus = interpolations.linear(0, d, t, self.stylus, where)
            self.pad.blit(self.nib, (stylus[0]-self.fat[0],
                                     stylus[1]-self.fat[1]))
        self.stylus = where

    def hits(self, context, hither, yon):
        h = super(_TestPad, self).hits(context, hither, yon)
        if h: return h
        if hither[0] < self.place[0]-self.size[0]/2: return None
        if hither[1] < self.place[1]-self.size[1]/2: return None
        if hither[0] >= self.place[0]+self.size[0]/2: return None
        if hither[1] >= self.place[1]+self.size[1]/2: return None
        return self

    def event(self, event):
        super(_TestPad, self).event(event)
        if event.type == MOUSEBUTTONDOWN:
            where = V(event.pos[0]-self.place[0]+self.size[0]/2,
                      event.pos[1]-self.place[1]+self.size[1]/2)
            self.stylus = where
            self.pressure = V(1.)
            self.draw(where, 1.)
        if event.type == MOUSEMOTION:
            where = V(event.pos[0]-self.place[0]+self.size[0]/2,
                      event.pos[1]-self.place[1]+self.size[1]/2)
            pressure = self.pressure[0]
            if pressure > 0.:
                self.draw(where, pressure)
        if event.type == MOUSEBUTTONUP:
            where = V(event.pos[0]-self.place[0]+self.size[0]/2,
                      event.pos[1]-self.place[1]+self.size[1]/2)
            pressure = self.pressure[0]
            if pressure > 0.:
                self.draw(where, pressure)
            self.pressure = V(0.)

    def paint(self, context):
        context.media.blit(self.pad,
                           (self.place[0] - self.size[0]/2,
                           self.place[1] - self.size[1]/2))

class _TestSpace (Space):

    def __init__(self, *args):
        super(_TestSpace, self).__init__(*args)
        self.text = [ "Press keys to see their event codes.",
                      "Press Esc to quit." ]
        self.font = App.resource("FreeSans.ttf", 25)
        self.pad = _TestPad( (300,200), (0xFF,0xFF,0xFe) )
        self.pad.follow(self)
        self.pad.move( V(400,250,0) )
        self.quit = False

    def segue(self):
        if self.quit: return None
        return super(_TestSpace, self).segue()

    def event(self, event):
        if event.type == KEYDOWN:
            text = "KEYDOWN %s=%s" % ( str(event.key),
                                       pygame.key.name(event.key) )
            self.text.append(text)
            if event.key == K_ESCAPE:
                self.quit = True
        while len(self.text) > 10:
            self.text.pop(0)
        super(_TestSpace, self).event(event)

    def paint(self, surface):
        super(_TestSpace, self).paint(surface)
        y = 25
        for line in self.text:
            text = self.font.render( line, 1, (0xFF,0xCC,0x66) )
            self.context.media.blit( text, (15, y) )
            y += text.get_rect().height
 
if __name__ == '__main__':
    app = App('Demo - press Esc to quit')
    app.space = _TestSpace()
    app.run()


Contact Ed Halley by email at ed@halley.cc.
Text, code, layout and artwork are Copyright © 1996-2013 Ed Halley.
Copying in whole or in part, with author attribution, is expressly allowed.
Any references to trademarks are illustrative and are controlled by their respective owners.
Make donations with PayPal - it's fast, free and secure!