[skycastle-commits] SF.net SVN: skycastle: [488] trunk/skycastle/modules/ui/src

  • From: zzorn@xxxxxxxxxxxxxxxxxxxxx
  • To: skycastle-commits@xxxxxxxxxxxxx
  • Date: Sun, 27 Apr 2008 13:55:39 -0700

Revision: 488
          http://skycastle.svn.sourceforge.net/skycastle/?rev=488&view=rev
Author:   zzorn
Date:     2008-04-27 13:55:38 -0700 (Sun, 27 Apr 2008)

Log Message:
-----------
Cleaned up the event listening model, now events are handled in a separate 
thread, and all modifications to the model happen in the Swing thread.

Changed data model to use samples that only contain the values of variables 
that changed since the last sample.  It uses integer variable ID:s internally, 
and float values.  Should be much more compact.

Cleared up Tool and input handlers, the controller now creates them.

Did some work on the camera, but projection still needs to be done. 

Modified Paths:
--------------
    trunk/skycastle/modules/ui/src/main/java/org/skycastle/opengl/Canvas3D.java
    
trunk/skycastle/modules/ui/src/main/java/org/skycastle/opengl/navigationgestures/NavigationGesture.java
    
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SketchController.java
    
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SketchView.java
    
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/StrokeRenderer.java
    
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/group/Group.java
    
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/group/GroupImpl.java
    
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/stroke/Stroke.java
    
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/stroke/StrokeImpl.java
    
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/stroke/StrokeListener.java
    
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/old/JPenExample.java
    
trunk/skycastle/modules/ui/src/test/java/org/skycastle/sketch/model/SketchModelTest.java

Added Paths:
-----------
    
trunk/skycastle/modules/ui/src/main/java/org/skycastle/opengl/CanvasInitializer.java
    
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/InputHandler.java
    
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/PenInputHandler.java
    trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/Tools/
    
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/Tools/StrokeTool.java
    
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/Tools/Tool.java
    trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/
    
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/AbstractSampleProducer.java
    
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/DataSample.java
    
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/DataVariableRegistry.java
    
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/SampleListener.java
    
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/SampleProducer.java
    
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/SketchVariableRegistry.java

Removed Paths:
-------------
    
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/point/StrokePoint.java
    
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/point/StrokePointImpl.java

Modified: 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/opengl/Canvas3D.java
===================================================================
--- trunk/skycastle/modules/ui/src/main/java/org/skycastle/opengl/Canvas3D.java 
2008-04-27 00:52:45 UTC (rev 487)
+++ trunk/skycastle/modules/ui/src/main/java/org/skycastle/opengl/Canvas3D.java 
2008-04-27 20:55:38 UTC (rev 488)
@@ -8,7 +8,6 @@
 import com.jme.system.DisplaySystem;
 import com.jmex.awt.JMECanvas;
 import com.jmex.awt.SimpleCanvasImpl;
-import jpen.PenManager;
 import org.skycastle.opengl.navigationgestures.*;
 import org.skycastle.util.ParameterChecker;
 import org.skycastle.util.cursor.CursorChangerImpl;
@@ -22,7 +21,9 @@
 import java.awt.event.ComponentAdapter;
 import java.awt.event.ComponentEvent;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
+import java.util.Vector;
 
 /**
  * A 3D Canvas, showing a 3D object in an AWT Canvas component.
@@ -57,12 +58,19 @@
 
     };
 
+    // We use a vector to get syncronization support
+    @SuppressWarnings( { "UseOfObsoleteCollectionType" } )
+    private final List<CanvasInitializer> myInitializers = new 
Vector<CanvasInitializer>( 3 );
+
     private Spatial my3DNode = null;
     private Component myView3D = null;
     private Canvas myCanvas = null;
     private MyCanvasRenderer myCanvasRenderer = null;
     private float myViewDistance;
 
+
+    private boolean myInitialized = false;
+
     //======================================================================
     // Private Constants
 
@@ -116,6 +124,15 @@
     // Other Public Methods
 
     /**
+     * @return true if the 3D renderer for the canvas has been created.
+     */
+    public boolean isInitialized()
+    {
+        return myInitialized;
+    }
+
+
+    /**
      * TODO: CHECK: Are the units meters?
      *
      * @return distance in screen units to the far clipping plane - 3D 
geometry beyond this distance is not
@@ -261,7 +278,6 @@
         registerNavigationGestureListener( addedNavigationGesture );
     }
 
-    private PenManager myPenManager;
 
     /**
      * @param removedNavigationGesture gesture to remove.
@@ -306,6 +322,17 @@
                                color.getAlpha() / 255.0f );
     }
 
+
+    /**
+     * @param initializer an initializer to be run when the 3D canvas has been 
created
+     */
+    public void addInitializer( final CanvasInitializer initializer )
+    {
+        ParameterChecker.checkNotNull( initializer, "initializer" );
+
+        myInitializers.add( initializer );
+    }
+
     //======================================================================
     // Private Methods
 
@@ -314,7 +341,6 @@
         if ( myCanvas != null )
         {
             navigationGesture.init( myCanvas, myCursorChanger, 
myCameraAccessor );
-            myPenManager.pen.addListener( navigationGesture );
         }
     }
 
@@ -343,9 +369,6 @@
         myCanvasRenderer = new MyCanvasRenderer( width, height, my3DNode, 
myCanvas );
         jmeCanvas.setImplementor( myCanvasRenderer );
 
-        // Listen to tablet pen movement over the canvas
-        myPenManager = new PenManager( myCanvas );
-
         // Add navigation gesture listeners to the created 3D canvas
         for ( final NavigationGesture navigationGesture : myNavigationGestures 
)
         {
@@ -360,6 +383,19 @@
         return myCanvas;
     }
 
+
+    private void runInitializers( final Renderer renderer )
+    {
+        myInitialized = true;
+
+        for ( final CanvasInitializer initializer : myInitializers )
+        {
+            initializer.initialize( this, renderer );
+        }
+
+        myInitializers.clear();
+    }
+
     //======================================================================
     // Inner Classes
 
@@ -559,6 +595,8 @@
             {
                 SwingUtilities.invokeLater( myFrameListenerUpdater );
             }
+
+            runInitializers( getRenderer() );
         }
 
 

Added: 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/opengl/CanvasInitializer.java
===================================================================
--- 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/opengl/CanvasInitializer.java
                                (rev 0)
+++ 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/opengl/CanvasInitializer.java
        2008-04-27 20:55:38 UTC (rev 488)
@@ -0,0 +1,22 @@
+package org.skycastle.opengl;
+
+import com.jme.renderer.Renderer;
+
+/**
+ * Something that gets called when the {@link Canvas3D} is initialized.
+ *
+ * @author Hans Haggstrom
+ */
+public interface CanvasInitializer
+{
+
+    /**
+     * Called when the {@link Canvas3D}:s 3D parts have been initialized.
+     * <p/>
+     * Do not assume that this is called in the swing thread.
+     *
+     * @param canvas3D the {@link Canvas3D} that has been initialized.
+     * @param renderer the 3D {@link Renderer} of the canvas.
+     */
+    void initialize( final Canvas3D canvas3D, final Renderer renderer );
+}

Modified: 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/opengl/navigationgestures/NavigationGesture.java
===================================================================
--- 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/opengl/navigationgestures/NavigationGesture.java
     2008-04-27 00:52:45 UTC (rev 487)
+++ 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/opengl/navigationgestures/NavigationGesture.java
     2008-04-27 20:55:38 UTC (rev 488)
@@ -1,7 +1,6 @@
 package org.skycastle.opengl.navigationgestures;
 
 
-import jpen.event.PenListener;
 import org.skycastle.util.cursor.CursorChanger;
 
 import javax.swing.event.MouseInputListener;
@@ -14,7 +13,7 @@
  * @author Hans H�ggstr�m
  */
 public interface NavigationGesture
-        extends MouseInputListener, MouseWheelListener, PenListener
+        extends MouseInputListener, MouseWheelListener
 {
 
     /**

Added: 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/InputHandler.java
===================================================================
--- 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/InputHandler.java 
                            (rev 0)
+++ 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/InputHandler.java 
    2008-04-27 20:55:38 UTC (rev 488)
@@ -0,0 +1,205 @@
+package org.skycastle.sketch;
+
+import jpen.PenManager;
+import org.skycastle.sketch.Tools.Tool;
+import org.skycastle.sketch.sample.DataSample;
+import org.skycastle.sketch.sample.SampleListener;
+import org.skycastle.util.ParameterChecker;
+
+import javax.swing.*;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * @author Hans Haggstrom
+ */
+public final class InputHandler
+{
+
+    //======================================================================
+    // Private Fields
+
+    private final SketchController mySketchController;
+    private final List<Tool> myTools = new ArrayList<Tool>();
+    private final PenInputHandler myPenInputHandler = new PenInputHandler();
+    private final BlockingQueue<DataSample> myQueuedInput = new 
ArrayBlockingQueue<DataSample>(
+            MAX_INPUT_EVENT_QUEUE );
+
+    /**
+     * Recieves input in the pen input thread, and just queues it in a 
syncronized storage for processing in
+     * the Swing thread.
+     */
+    private final SampleListener myInputReciever = new SampleListener()
+    {
+
+        public void onSample( final DataSample dataSample )
+        {
+            try
+            {
+                myQueuedInput.put( dataSample );
+            }
+            catch ( InterruptedException e )
+            {
+                // Interrupted, just return.  The sample is lost, but it 
doesn't matter that much.
+            }
+        }
+
+
+        public void onSamples( final List<DataSample> dataSamples )
+        {
+            // There's no putAll method, so just put the samples one at a time.
+            for ( final DataSample dataSample : dataSamples )
+            {
+                onSample( dataSample );
+            }
+        }
+
+    };
+
+    //======================================================================
+    // Private Constants
+
+    private static final Logger LOGGER = Logger.getLogger( 
InputHandler.class.getName() );
+    private static final int MAX_INPUT_EVENT_QUEUE = 10000;
+    private static final int MAX_SAMPLES_TO_HANDLE_AT_ONCE = 100;
+
+    //======================================================================
+    // Public Methods
+
+    //----------------------------------------------------------------------
+    // Constructors
+
+    /**
+     * Creates a new {@link org.skycastle.sketch.InputHandler}.
+     */
+    public InputHandler( final PenManager penManager, final SketchController 
sketchController )
+    {
+        mySketchController = sketchController;
+        ParameterChecker.checkNotNull( penManager, "penManager" );
+
+        // Convert pen events to DataSamples
+        penManager.pen.addListener( myPenInputHandler );
+
+        // Queue the DataSamples
+        myPenInputHandler.addListener( myInputReciever );
+
+        // Handle DataSamples in the queue with tools that run in the swing 
thread
+        final Thread sampleMover = new Thread( new Runnable()
+        {
+
+            public void run()
+            {
+                final List<DataSample> samples = new ArrayList<DataSample>();
+
+                //noinspection InfiniteLoopStatement
+                while ( true )
+                {
+                    // Get next samples
+                    waitForSamples( samples );
+
+                    // Handle the samples in the Swing thread.  Blocks until 
ready
+                    invokeToolsInSwingThread( samples );
+
+                    // Reuse collection.
+                    samples.clear();
+                }
+            }
+
+        } );
+
+        sampleMover.start();
+    }
+
+    //----------------------------------------------------------------------
+    // Other Public Methods
+
+    /**
+     * Adds the specified Tool.
+     *
+     * @param addedTool should not be null or already added.
+     */
+    public void addTool( final Tool addedTool )
+    {
+        ParameterChecker.checkNotNull( addedTool, "addedTool" );
+        ParameterChecker.checkNotAlreadyContained( addedTool, myTools, 
"myTools" );
+
+        myTools.add( addedTool );
+    }
+
+
+    /**
+     * Removes the specified Tool.
+     *
+     * @param removedTool should not be null, and should be present.
+     */
+    public void removeTool( final Tool removedTool )
+    {
+        ParameterChecker.checkNotNull( removedTool, "removedTool" );
+        ParameterChecker.checkContained( removedTool, myTools, "myTools" );
+
+        myTools.remove( removedTool );
+    }
+
+    //======================================================================
+    // Private Methods
+
+    private void invokeToolsInSwingThread( final List<DataSample> samples )
+    {
+        try
+        {
+            SwingUtilities.invokeAndWait( new Runnable()
+            {
+
+                public void run()
+                {
+                    for ( final DataSample dataSample : samples )
+                    {
+                        // Let each tool handle each sample
+                        for ( final Tool tool : myTools )
+                        {
+                            tool.onEvent( dataSample, mySketchController );
+                        }
+                    }
+                }
+
+            } );
+        }
+        catch ( InterruptedException e )
+        {
+            LOGGER.log( Level.INFO, "Interrupted while handling input events", 
e );
+        }
+        catch ( InvocationTargetException e )
+        {
+            LOGGER.log( Level.WARNING, "Problem when handling input events", e 
);
+        }
+    }
+
+
+    private void waitForSamples( final List<DataSample> samples )
+    {
+        // Wait for first sample
+        DataSample dataSample = null;
+        try
+        {
+            dataSample = myQueuedInput.take();
+        }
+        catch ( InterruptedException e )
+        {
+            // We were interrupted, didn't get anything.
+        }
+
+        if ( dataSample != null )
+        {
+            samples.add( dataSample );
+        }
+
+        // Get the rest of the available samples
+        myQueuedInput.drainTo( samples, MAX_SAMPLES_TO_HANDLE_AT_ONCE );
+    }
+
+}

Added: 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/PenInputHandler.java
===================================================================
--- 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/PenInputHandler.java
                          (rev 0)
+++ 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/PenInputHandler.java
  2008-04-27 20:55:38 UTC (rev 488)
@@ -0,0 +1,125 @@
+package org.skycastle.sketch;
+
+import jpen.*;
+import jpen.event.PenListener;
+import org.skycastle.sketch.sample.AbstractSampleProducer;
+import org.skycastle.sketch.sample.DataSample;
+
+/**
+ * Recieves pen events, and turns them into {@link DataSample} based events.
+ *
+ * @author Hans Haggstrom
+ */
+@SuppressWarnings( { "ParameterNameDiffersFromOverriddenParameter" } )
+public final class PenInputHandler
+        extends AbstractSampleProducer
+        implements PenListener
+{
+
+    //----------------------------------------------------------------------
+    // Constructors
+
+    /**
+     * Creates a new {@link PenInputHandler}.
+     */
+    public PenInputHandler()
+    {
+    }
+
+    //----------------------------------------------------------------------
+    // PenListener Implementation
+
+    public void penKindEvent( final PKindEvent ev )
+    {
+        // Ignored for now
+    }
+
+
+    public void penLevelEvent( final PLevelEvent event )
+    {
+        final DataSample dataSample = new DataSample();
+
+        dataSample.setVariable( "time", getTimeAsSeconds( event ) );
+
+        for ( final PLevel level : event.levels )
+        {
+            switch ( level.getType() )
+            {
+                case X:
+                    // TODO: Convert to canvas coordinates
+                    final float x = level.value / 10f;
+
+                    dataSample.setVariable( "x", x );
+                    break;
+                case Y:
+                    // TODO: Convert to canvas coordinates
+                    final float y = level.value / 10f;
+
+                    dataSample.setVariable( "y", y );
+                    break;
+                case PRESSURE:
+                    dataSample.setVariable( "pressure", level.value );
+                    break;
+                case TILT_X:
+                    dataSample.setVariable( "tiltX", level.value );
+                    break;
+                case TILT_Y:
+                    dataSample.setVariable( "tiltY", level.value );
+                    break;
+            }
+        }
+
+        sendSample( dataSample );
+    }
+
+
+    @Override
+    public void penButtonEvent( final PButtonEvent event )
+    {
+        final DataSample dataSample = new DataSample();
+
+        dataSample.setVariable( "time", getTimeAsSeconds( event ) );
+
+        final float value = event.button.value ? 1 : 0;
+
+        switch ( event.button.getType() )
+        {
+            case CENTER:
+                dataSample.setVariable( "centerButton", value );
+                break;
+
+            case LEFT:
+                dataSample.setVariable( "leftButton", value );
+                break;
+
+            case RIGHT:
+                dataSample.setVariable( "rightButton", value );
+                break;
+        }
+
+        sendSample( dataSample );
+    }
+
+
+    public void penScrollEvent( final PScrollEvent ev )
+    {
+        // Ignored for now
+    }
+
+
+    public void penTock( final long availableMillis )
+    {
+        // TODO: If this goes towards zero, it means our listeners are taking 
up too much time.
+        // Reduce amount of messages?
+    }
+
+    //======================================================================
+    // Private Methods
+
+    private float getTimeAsSeconds( final PenEvent event )
+    {
+        // Convert timestamp to seconds
+        return ( 1.0f * event.getTime() ) / 1000.0f;
+    }
+
+}

Modified: 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SketchController.java
===================================================================
--- 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SketchController.java
 2008-04-27 00:52:45 UTC (rev 487)
+++ 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SketchController.java
 2008-04-27 20:55:38 UTC (rev 488)
@@ -1,10 +1,12 @@
 package org.skycastle.sketch;
 
+import org.skycastle.sketch.Tools.StrokeTool;
 import org.skycastle.sketch.model.Sketch;
 import org.skycastle.sketch.model.SketchImpl;
-import org.skycastle.sketch.model.point.StrokePointImpl;
+import org.skycastle.sketch.model.group.Group;
 import org.skycastle.sketch.model.stroke.Stroke;
 import org.skycastle.sketch.model.stroke.StrokeImpl;
+import org.skycastle.sketch.sample.DataSample;
 import org.skycastle.util.command.*;
 
 import javax.swing.*;
@@ -56,7 +58,7 @@
 
     };
 
-/*
+    /*
     private final CommandAction myAddTestStrokeAction = new CommandAction( 
myCommandStack,
                                                                            
"Test Stroke",
                                                                            
"Add Test Stroke" )
@@ -98,6 +100,8 @@
 
     private Sketch mySketch;
 
+    private InputHandler myInputHandler;
+
     //======================================================================
     // Private Constants
 
@@ -125,6 +129,33 @@
     // Other Public Methods
 
     /**
+     * @return the {@link CommandStack} used for executing undoable commands.
+     */
+    public CommandStack getCommandStack()
+    {
+        return myCommandStack;
+    }
+
+
+    /**
+     * @return the currently edited {@link Sketch}
+     */
+    public Sketch getSketch()
+    {
+        return mySketch;
+    }
+
+
+    /**
+     * @return the currently edited {@link Group} in the {@link Sketch}
+     */
+    public Group getSketchGroup()
+    {
+        return mySketch.getRootGroup();
+    }
+
+
+    /**
      * Creates and opens the UI.
      */
     void start()
@@ -144,9 +175,11 @@
         mySketchView.addToolbarAction( myCommandStack.getUndoAction() );
         mySketchView.addToolbarAction( myCommandStack.getRedoAction() );
 
-        mySketchView.addNavigationGesture( new StrokeTool( 
mySketch.getRootGroup(), myCommandStack ) );
+        mySketchView.setSketch( mySketch );
 
-        mySketchView.setSketch( mySketch );
+        myInputHandler = new InputHandler( mySketchView.getPenManager(), this 
);
+
+        myInputHandler.addTool( new StrokeTool() );
     }
 
     //======================================================================
@@ -175,8 +208,10 @@
             y += yv;
             w += wv;
 
-            final StrokePointImpl strokePoint = new StrokePointImpl( x, y );
-            strokePoint.setProperty( "width", w );
+            final DataSample strokePoint = new DataSample();
+            strokePoint.setVariable( "x", x );
+            strokePoint.setVariable( "y", y );
+            strokePoint.setVariable( "width", w );
             stroke.addPoint( strokePoint );
         }
 

Modified: 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SketchView.java
===================================================================
--- 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SketchView.java   
    2008-04-27 00:52:45 UTC (rev 487)
+++ 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SketchView.java   
    2008-04-27 20:55:38 UTC (rev 488)
@@ -1,7 +1,13 @@
 package org.skycastle.sketch;
 
+import com.jme.math.FastMath;
+import com.jme.math.Vector3f;
+import com.jme.renderer.Camera;
+import com.jme.renderer.Renderer;
 import com.jme.scene.Node;
+import jpen.PenManager;
 import org.skycastle.opengl.Canvas3D;
+import org.skycastle.opengl.CanvasInitializer;
 import org.skycastle.opengl.navigationgestures.NavigationGesture;
 import org.skycastle.sketch.model.Sketch;
 import org.skycastle.sketch.model.group.Group;
@@ -16,6 +22,7 @@
 import javax.swing.*;
 import java.awt.BorderLayout;
 import java.awt.Color;
+import java.awt.Component;
 import java.awt.Frame;
 import java.util.HashMap;
 import java.util.Map;
@@ -35,13 +42,25 @@
     private final Map<String, JMenu> myMenus = new HashMap<String, JMenu>( 10 
);
     private final SimpleFrame myMainFrame;
     private final Node myRoot3DNode;
+    private final PenManager myPenManager;
 
     private Canvas3D myCanvas3D;
     private JToolBar myToolBar;
     private JMenuBar myMenuBar;
     private Sketch mySketch;
+    private float myCameraX = 0;
+    private float myCameraY = 0;
+    private float myViewSizeHorizontally = INITIAL_VIEW_SIZE;
+    private float myRotationRadians = 0;
 
     //======================================================================
+    // Private Constants
+
+    private static final float NEAR_CLIP_PLANE = -100f;
+    private static final float FAR_CLIP_PLANE = 1000f;
+    private static final int INITIAL_VIEW_SIZE = 1000;
+
+    //======================================================================
     // Public Methods
 
     //----------------------------------------------------------------------
@@ -63,6 +82,7 @@
         myCanvas3D.set3DNode( myRoot3DNode );
         myCanvas3D.setBackgroundColor( Color.WHITE );
 
+        moveCamera( myCameraX, myCameraY, myViewSizeHorizontally, 
myRotationRadians );
 
         final JPanel panel = new JPanel( new BorderLayout() );
         panel.add( myCanvas3D.get3DView(), BorderLayout.CENTER );
@@ -72,6 +92,8 @@
         myMainFrame.setJMenuBar( myMenuBar );
         myMainFrame.setTitle( applicationName );
 
+        myPenManager = new PenManager( myCanvas3D.get3DView() );
+
         // Make all popup menus and tooltips heavyweight so that they can 
overlap the 3D view
         JPopupMenu.setDefaultLightWeightPopupEnabled( false );
         ToolTipManager.sharedInstance().setLightWeightPopupEnabled( false );
@@ -80,8 +102,113 @@
     //----------------------------------------------------------------------
     // Other Public Methods
 
+    /**
+     * @return the pen input manager.
+     */
+    public PenManager getPenManager()
+    {
+        return myPenManager;
+    }
 
+
     /**
+     * Change the zoom of the camera.
+     *
+     * @param viewSizeHorizontally how many canvas coordinates should fit in 
the current view, in horizontal
+     *                             direction.
+     */
+    public void zoomCamera( final float viewSizeHorizontally )
+    {
+        moveCamera( myCameraX, myCameraY, viewSizeHorizontally );
+    }
+
+
+    /**
+     * Move the camera as specified.
+     *
+     * @param cameraX new center x position on the screen, in canvas 
coordinates.
+     * @param cameraY new center y position on the screen, in canvas 
coordinates.
+     */
+    public void moveCamera( final float cameraX,
+                            final float cameraY )
+    {
+        moveCamera( cameraX, cameraY, myViewSizeHorizontally );
+    }
+
+
+    /**
+     * Move the camera as specified.
+     *
+     * @param cameraX              new center x position on the screen, in 
canvas coordinates.
+     * @param cameraY              new center y position on the screen, in 
canvas coordinates.
+     * @param viewSizeHorizontally how many canvas coordinates should fit in 
the current view, in horizontal
+     *                             direction.
+     */
+    public void moveCamera( final float cameraX,
+                            final float cameraY,
+                            final float viewSizeHorizontally )
+    {
+        moveCamera( cameraX, cameraY, viewSizeHorizontally, myRotationRadians 
);
+    }
+
+
+    /**
+     * Move the camera as specified.
+     *
+     * @param cameraX              new center x position on the screen, in 
canvas coordinates.
+     * @param cameraY              new center y position on the screen, in 
canvas coordinates.
+     * @param viewSizeHorizontally how many canvas coordinates should fit in 
the current view, in horizontal
+     *                             direction.
+     * @param rotationRadians      amount of rotation of camera, in radians.  
0 = normal view.
+     */
+    public void moveCamera( final float cameraX,
+                            final float cameraY,
+                            final float viewSizeHorizontally,
+                            final float rotationRadians )
+    {
+        ParameterChecker.checkNormalNumber( cameraX, "cameraX" );
+        ParameterChecker.checkNormalNumber( cameraY, "cameraY" );
+        ParameterChecker.checkPositiveNonZeroNormalNumber( 
viewSizeHorizontally, "viewSizeHorizontally" );
+        ParameterChecker.checkNormalNumber( rotationRadians, "rotationRadians" 
);
+
+        myCameraX = cameraX;
+        myCameraY = cameraY;
+        myViewSizeHorizontally = viewSizeHorizontally;
+        myRotationRadians = rotationRadians;
+
+        if ( myCanvas3D.getCamera() != null &&
+             myCanvas3D.isInitialized() )
+        {
+            updateCamera( myCanvas3D, cameraX, cameraY, viewSizeHorizontally, 
rotationRadians );
+        }
+        else
+        {
+            // If not yet initialized, queue the change
+            myCanvas3D.addInitializer( new CanvasInitializer()
+            {
+
+                public void initialize( final Canvas3D canvas3D, final 
Renderer renderer )
+                {
+                    updateCamera( myCanvas3D, cameraX, cameraY, 
viewSizeHorizontally, rotationRadians );
+                }
+
+            } );
+        }
+    }
+
+
+    /**
+     * Rotates the camera.
+     *
+     * @param rotationRadians amount of rotation of camera, in radians.  0 = 
normal view.
+     */
+    public void rotateCamera( final float rotationRadians )
+    {
+        moveCamera( myCameraX, myCameraY, myViewSizeHorizontally, 
rotationRadians );
+    }
+
+
+    /**
      * @param closeAction an action that is called when the close button of 
the main window is pressed, or the
      *                    application is attempted to be closed in some other 
way.
      */
@@ -196,6 +323,46 @@
     //======================================================================
     // Private Methods
 
+    private void updateCamera( final Canvas3D canvas3D,
+                               final float cameraX,
+                               final float cameraY,
+                               final float viewSizeHorizontally,
+                               final float rotationRadians )
+    {
+        final Camera camera = canvas3D.getCamera();
+
+        if ( camera != null )
+        {
+            final float upX = FastMath.sin( rotationRadians );
+            final float upY = -FastMath.cos( rotationRadians );
+            final float leftX = -FastMath.cos( rotationRadians );
+            final float leftY = -FastMath.sin( rotationRadians );
+
+            camera.setUp( new Vector3f( upX, upY, 0 ) );
+            camera.setLeft( new Vector3f( leftX, leftY, 0 ) );
+            camera.setDirection( new Vector3f( 0, 0, 1 ) );
+
+
+            camera.setParallelProjection( true );
+
+            camera.setLocation( new Vector3f( cameraX, cameraY, 0 ) );
+
+            final Component view = canvas3D.get3DView();
+
+            final float halfViewSizeHorizontally = 0.5f * viewSizeHorizontally;
+
+            final float aspect = (float) view.getHeight() / (float) 
view.getWidth();
+            camera.setFrustum( NEAR_CLIP_PLANE,
+                               FAR_CLIP_PLANE,
+                               -halfViewSizeHorizontally,
+                               halfViewSizeHorizontally,
+                               -halfViewSizeHorizontally * aspect,
+                               halfViewSizeHorizontally * aspect );
+            camera.update();
+        }
+    }
+
+
     private void reRender( final Sketch sketch )
     {
         // Clean out old nodes (NOTE: detach all is usually a bit buggy / 
weird.. )
@@ -220,7 +387,6 @@
                 {
                     myRoot3DNode.attachChild( new StrokeRenderer( stroke ) );
                 }
-
             }
             else if ( groupElement instanceof Group )
             {

Modified: 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/StrokeRenderer.java
===================================================================
--- 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/StrokeRenderer.java
   2008-04-27 00:52:45 UTC (rev 487)
+++ 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/StrokeRenderer.java
   2008-04-27 20:55:38 UTC (rev 488)
@@ -16,8 +16,8 @@
 import com.jme.util.TextureManager;
 import com.jme.util.geom.BufferUtils;
 import com.jmex.awt.swingui.ImageGraphics;
-import org.skycastle.sketch.model.point.StrokePoint;
 import org.skycastle.sketch.model.stroke.Stroke;
+import org.skycastle.sketch.sample.DataSample;
 import org.skycastle.util.ImageUtils;
 import org.skycastle.util.ParameterChecker;
 
@@ -266,35 +266,41 @@
 
     private void initializeVetices()
     {
+        final DataSample pointState = new DataSample();
+
         int pointIndex = 0;
-        for ( final StrokePoint strokePoint : myStroke.getPoints() )
+        for ( final DataSample strokePoint : myStroke.getPoints() )
         {
-            initializePoint( strokePoint, pointIndex++ );
+            pointState.updateValuesWith( strokePoint );
+
+            initializePoint( pointState, pointIndex++ );
         }
     }
 
 
     @SuppressWarnings( { "PointlessArithmeticExpression" } )
-    private void initializePoint( final StrokePoint strokePoint, final int 
pointIndex )
+    private void initializePoint( final DataSample strokePoint, final int 
pointIndex )
     {
         final int index = pointIndex * 3;
 
-        final float xPos = strokePoint.getX();
-        final float yPos = strokePoint.getY();
+        final float xPos = strokePoint.getVariable( "x", 0 );
+        final float yPos = strokePoint.getVariable( "y", 0 );
         final float zPos = myZ;
-        final float width = strokePoint.getProperty( "width", 0.5f );
+        final float width = strokePoint.getVariable( "width", 0.5f );
 
+        final float colorR = strokePoint.getVariable( "colorR", 0 );
+        final float colorG = strokePoint.getVariable( "colorG", 0 );
+        final float colorB = strokePoint.getVariable( "colorB", 0 );
+        final float colorA = strokePoint.getVariable( "colorA", 0.7f );
+
         final Vector3f positionLeft = new Vector3f( xPos, yPos + width, zPos );
-        final ColorRGBA colorLeft = strokePoint.getProperty( "color",
-                                                             new ColorRGBA( 0, 
0, 0, 0 ) );
+        final ColorRGBA colorLeft = new ColorRGBA( colorR, colorG, colorB, 0 );
 
         final Vector3f positionMid = new Vector3f( xPos, yPos, zPos );
-        final ColorRGBA colorMid = strokePoint.getProperty( "color",
-                                                            new ColorRGBA( 0, 
0, 0, 0.7f ) );
+        final ColorRGBA colorMid = new ColorRGBA( colorR, colorG, colorB, 
colorA );
 
         final Vector3f positionRight = new Vector3f( xPos, yPos - width, zPos 
);
-        final ColorRGBA colorRight = strokePoint.getProperty( "color",
-                                                              new ColorRGBA( 
0, 0, 0, 0 ) );
+        final ColorRGBA colorRight = new ColorRGBA( colorR, colorG, colorB, 0 
);
 
         setPointData( index + 0, positionLeft, colorLeft );
         setPointData( index + 1, positionMid, colorMid );

Copied: 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/Tools/StrokeTool.java
 (from rev 487, 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/StrokeTool.java)
===================================================================
--- 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/Tools/StrokeTool.java
                         (rev 0)
+++ 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/Tools/StrokeTool.java
 2008-04-27 20:55:38 UTC (rev 488)
@@ -0,0 +1,106 @@
+package org.skycastle.sketch.Tools;
+
+import org.skycastle.sketch.SketchController;
+import org.skycastle.sketch.model.group.Group;
+import org.skycastle.sketch.model.stroke.Stroke;
+import org.skycastle.sketch.model.stroke.StrokeImpl;
+import org.skycastle.sketch.sample.DataSample;
+import org.skycastle.util.command.AbstractCommand;
+
+/**
+ * @author Hans Haggstrom
+ */
+public final class StrokeTool
+        implements Tool
+{
+
+    //======================================================================
+    // Private Fields
+
+    private final DataSample myCurrentStatus = new DataSample();
+
+    private Stroke myStroke = null;
+
+    //======================================================================
+    // Public Methods
+
+    //----------------------------------------------------------------------
+    // Tool Implementation
+
+    public void onEvent( final DataSample event, final SketchController 
sketchController )
+    {
+        myCurrentStatus.updateValuesWith( event );
+
+        if ( event.hasVariable( "leftButton" ) )
+        {
+            final boolean pressed = event.getVariable( "leftButton", 0 ) > 
0.5f;
+
+            if ( pressed != isStrokeActive() )
+            {
+                if ( pressed )
+                {
+                    startStroke( sketchController );
+                }
+                else
+                {
+                    endStroke( sketchController );
+                }
+            }
+        }
+
+        if ( isStrokeActive() )
+        {
+            myStroke.addPoint( event );
+        }
+    }
+
+    //======================================================================
+    // Private Methods
+
+    private void startStroke( final SketchController sketchController )
+    {
+        myStroke = new StrokeImpl();
+        myStroke.addPoint( new DataSample( myCurrentStatus ) );
+
+        // Add stroke, so that we get a preview of it
+        final Group group = sketchController.getSketchGroup();
+        group.add( myStroke );
+    }
+
+
+    private void endStroke( final SketchController sketchController )
+    {
+        final Stroke stroke = myStroke;
+        final Group group = sketchController.getSketchGroup();
+
+        sketchController.getCommandStack().invoke( new AbstractCommand( 
"Stroke", true )
+        {
+
+            public void doCommand()
+            {
+                // Allows us to add the stroke in the startStroke method 
already, and still undo and redo if needed.
+                if ( !group.contains( stroke ) )
+                {
+                    group.add( stroke );
+                }
+            }
+
+
+            @Override
+            public void undoCommand()
+            {
+                group.remove( stroke );
+            }
+
+        } );
+
+        myStroke = null;
+    }
+
+
+    private boolean isStrokeActive()
+    {
+        return myStroke != null;
+    }
+
+}

Added: 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/Tools/Tool.java
===================================================================
--- 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/Tools/Tool.java   
                            (rev 0)
+++ 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/Tools/Tool.java   
    2008-04-27 20:55:38 UTC (rev 488)
@@ -0,0 +1,25 @@
+package org.skycastle.sketch.Tools;
+
+import org.skycastle.sketch.SketchController;
+import org.skycastle.sketch.sample.DataSample;
+
+/**
+ * Something that recieves events from the input devices.
+ *
+ * @author Hans Haggstrom
+ */
+public interface Tool
+{
+
+    /**
+     * Called when there is input.
+     *
+     * @param event            an event from the input devices.  Can contain 
one or more variables that
+     *                         describe its state.
+     * @param sketchController the {@link SketchController}.  Can be used to 
access the view and other parts
+     *                         of the program.
+     */
+    void onEvent( DataSample event,
+                  SketchController sketchController );
+
+}

Modified: 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/group/Group.java
===================================================================
--- 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/group/Group.java
        2008-04-27 00:52:45 UTC (rev 487)
+++ 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/group/Group.java
        2008-04-27 20:55:38 UTC (rev 488)
@@ -44,4 +44,8 @@
      */
     List<GroupElement> getElements();
 
+    /**
+     * @return true if the specified element is contained in this {@link 
Group}.
+     */
+    boolean contains( final GroupElement element );
 }

Modified: 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/group/GroupImpl.java
===================================================================
--- 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/group/GroupImpl.java
    2008-04-27 00:52:45 UTC (rev 487)
+++ 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/group/GroupImpl.java
    2008-04-27 20:55:38 UTC (rev 488)
@@ -50,4 +50,9 @@
         return myElements.getReadOnlyList();
     }
 
+    public boolean contains( final GroupElement element )
+    {
+        return myElements.contains( element );
+    }
+
 }

Deleted: 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/point/StrokePoint.java
===================================================================
--- 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/point/StrokePoint.java
  2008-04-27 00:52:45 UTC (rev 487)
+++ 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/point/StrokePoint.java
  2008-04-27 20:55:38 UTC (rev 488)
@@ -1,50 +0,0 @@
-package org.skycastle.sketch.model.point;
-
-import org.skycastle.sketch.model.stroke.Stroke;
-
-/**
- * A point on a {@link Stroke}
- * <p/>
- * Can be modified by filters when the stroke is created, but usually not 
modified in the final {@link
- * Stroke}.
- *
- * @author Hans Häggström
- */
-public interface StrokePoint
-{
-
-    /**
-     * @return x coordinate of the point.
-     */
-    float getX();
-
-    /**
-     * @param x coordinate of the point.
-     */
-    void setX( float x );
-
-    /**
-     * @return y coordinate of the point.
-     */
-    float getY();
-
-    /**
-     * @param y coordinate of the point.
-     */
-    void setY( float y );
-
-    /**
-     * @param property     the property to get
-     * @param defaultValue value returned if the specified property is not 
available.
-     *
-     * @return the specified property of the stroke.
-     */
-    <T> T getProperty( String property, T defaultValue );
-
-    /**
-     * @param property the property to set
-     * @param value    the value to set the property to.
-     */
-    void setProperty( String property, Object value );
-
-}

Deleted: 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/point/StrokePointImpl.java
===================================================================
--- 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/point/StrokePointImpl.java
      2008-04-27 00:52:45 UTC (rev 487)
+++ 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/point/StrokePointImpl.java
      2008-04-27 20:55:38 UTC (rev 488)
@@ -1,95 +0,0 @@
-package org.skycastle.sketch.model.point;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * @author Hans Häggström
- */
-public final class StrokePointImpl
-        implements StrokePoint
-{
-
-    //======================================================================
-    // Private Fields
-
-    private float myX = 0;
-    private float myY = 0;
-
-    private Map<String, Object> myProperties = null;
-
-    //======================================================================
-    // Public Methods
-
-    //----------------------------------------------------------------------
-    // Constructors
-
-    /**
-     * Creates a new {@link StrokePointImpl}.
-     */
-    public StrokePointImpl()
-    {
-        this( 0, 0 );
-    }
-
-    /**
-     * Creates a new {@link StrokePointImpl}.
-     *
-     * @param x x coordinate of point, in canvas coordinates.
-     * @param y y coordinate of point, in canvas coordinates.
-     */
-    public StrokePointImpl( float x, float y )
-    {
-        setX( x );
-        setY( y );
-    }
-
-    //----------------------------------------------------------------------
-    // StrokePoint Implementation
-
-    public float getX()
-    {
-        return myX;
-    }
-
-    public void setX( final float x )
-    {
-        myX = x;
-    }
-
-    public float getY()
-    {
-        return myY;
-    }
-
-    public void setY( final float y )
-    {
-        myY = y;
-    }
-
-
-    public <T> T getProperty( final String property, final T defaultValue )
-    {
-        if ( myProperties != null &&
-             myProperties.containsKey( property ) )
-        {
-            //noinspection unchecked
-            return (T) myProperties.get( property );
-        }
-        else
-        {
-            return defaultValue;
-        }
-    }
-
-    public void setProperty( final String property, final Object value )
-    {
-        if ( myProperties == null )
-        {
-            myProperties = new HashMap<String, Object>( 5 );
-        }
-
-        myProperties.put( property, value );
-    }
-
-}

Modified: 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/stroke/Stroke.java
===================================================================
--- 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/stroke/Stroke.java
      2008-04-27 00:52:45 UTC (rev 487)
+++ 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/stroke/Stroke.java
      2008-04-27 20:55:38 UTC (rev 488)
@@ -2,7 +2,7 @@
 
 import org.skycastle.sketch.model.group.Group;
 import org.skycastle.sketch.model.group.GroupElement;
-import org.skycastle.sketch.model.point.StrokePoint;
+import org.skycastle.sketch.sample.DataSample;
 
 import java.util.List;
 
@@ -16,23 +16,23 @@
 {
 
     /**
-     * Adds the specified {@link StrokePoint} to this {@link Stroke}.
+     * Adds the specified {@link DataSample} to this {@link Stroke}.
      *
      * @param addedPoint should not be null or already added.
      */
-    void addPoint( StrokePoint addedPoint );
+    void addPoint( DataSample addedPoint );
 
     /**
-     * Removes the specified {@link StrokePoint} from this {@link Stroke}.
+     * Removes the specified {@link DataSample} from this {@link Stroke}.
      *
      * @param removedPoint should not be null, and should be present.
      */
-    void removePoint( StrokePoint removedPoint );
+    void removePoint( DataSample removedPoint );
 
     /**
-     * @return a read-only list with the {@link StrokePoint}s in this {@link 
Stroke}.
+     * @return a read-only list with the {@link DataSample}s in this {@link 
Stroke}.
      */
-    List<StrokePoint> getPoints();
+    List<DataSample> getPoints();
 
     /**
      * @return the {@link StrokeListener} that is currently listening to 
changes to this {@link Stroke}.

Modified: 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/stroke/StrokeImpl.java
===================================================================
--- 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/stroke/StrokeImpl.java
  2008-04-27 00:52:45 UTC (rev 487)
+++ 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/stroke/StrokeImpl.java
  2008-04-27 20:55:38 UTC (rev 488)
@@ -1,7 +1,7 @@
 package org.skycastle.sketch.model.stroke;
 
 import org.skycastle.sketch.model.group.AbstractGroupElement;
-import org.skycastle.sketch.model.point.StrokePoint;
+import org.skycastle.sketch.sample.DataSample;
 import org.skycastle.util.ParameterChecker;
 import org.skycastle.util.listenable.collection.list.ListenableArrayList;
 import org.skycastle.util.listenable.collection.list.ListenableMutableList;
@@ -19,7 +19,7 @@
     //======================================================================
     // Private Fields
 
-    private final ListenableMutableList<StrokePoint> myPoints = new 
ListenableArrayList<StrokePoint>(
+    private final ListenableMutableList<DataSample> myPoints = new 
ListenableArrayList<DataSample>(
             INITIAL_POINT_CAPACITY );
 
     private StrokeListener myStrokeListener;
@@ -35,7 +35,7 @@
     //----------------------------------------------------------------------
     // Stroke Implementation
 
-    public void addPoint( final StrokePoint addedPoint )
+    public void addPoint( final DataSample addedPoint )
     {
         ParameterChecker.checkNotNull( addedPoint, "addedPoint" );
         ParameterChecker.checkNotAlreadyContained( addedPoint, myPoints, 
"myPoints" );
@@ -49,7 +49,7 @@
     }
 
 
-    public void removePoint( final StrokePoint removedPoint )
+    public void removePoint( final DataSample removedPoint )
     {
         ParameterChecker.checkNotNull( removedPoint, "removedPoint" );
         ParameterChecker.checkContained( removedPoint, myPoints, "myPoints" );
@@ -63,7 +63,7 @@
     }
 
 
-    public List<StrokePoint> getPoints()
+    public List<DataSample> getPoints()
     {
         return myPoints.getReadOnlyList();
     }

Modified: 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/stroke/StrokeListener.java
===================================================================
--- 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/stroke/StrokeListener.java
      2008-04-27 00:52:45 UTC (rev 487)
+++ 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/stroke/StrokeListener.java
      2008-04-27 20:55:38 UTC (rev 488)
@@ -1,6 +1,6 @@
 package org.skycastle.sketch.model.stroke;
 
-import org.skycastle.sketch.model.point.StrokePoint;
+import org.skycastle.sketch.sample.DataSample;
 
 /**
  * Listens to changes to {@link Stroke}s.
@@ -16,7 +16,7 @@
      * @param stroke the {@link Stroke} in question.
      * @param point  the point in question.
      */
-    void onPointAdded( Stroke stroke, StrokePoint point );
+    void onPointAdded( Stroke stroke, DataSample point );
 
     /**
      * Called when a point is removed from a {@link Stroke}.
@@ -24,6 +24,6 @@
      * @param stroke the {@link Stroke} in question.
      * @param point  the point in question.
      */
-    void onPointRemoved( Stroke stroke, StrokePoint point );
+    void onPointRemoved( Stroke stroke, DataSample point );
 
 }

Modified: 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/old/JPenExample.java
===================================================================
--- 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/old/JPenExample.java
  2008-04-27 00:52:45 UTC (rev 487)
+++ 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/old/JPenExample.java
  2008-04-27 20:55:38 UTC (rev 488)
@@ -25,7 +25,6 @@
 
     JPenExample()
     {
-/*
         JLabel l = new JLabel( "Move the pen or mouse over me!" );
         PenManager pm = new PenManager( l );
         pm.pen.addListener( this );
@@ -34,7 +33,7 @@
         f.getContentPane().add( l );
         f.setSize( 300, 300 );
         f.setVisible( true );
-*/
+/*
 
         JLabel l = new JLabel( "Move the pen or mouse over me!" );
         PenManager pm = new PenManager( l );
@@ -46,6 +45,7 @@
         f.getContentPane().add( l );
         f.setSize( 300, 300 );
         f.setVisible( true );
+*/
     }
 
     //----------------------------------------------------------------------

Added: 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/AbstractSampleProducer.java
===================================================================
--- 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/AbstractSampleProducer.java
                            (rev 0)
+++ 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/AbstractSampleProducer.java
    2008-04-27 20:55:38 UTC (rev 488)
@@ -0,0 +1,81 @@
+package org.skycastle.sketch.sample;
+
+import org.skycastle.util.ParameterChecker;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Provides basic functionality for a {@link SampleProducer}.
+ *
+ * @author Hans Haggstrom
+ */
+public abstract class AbstractSampleProducer
+        implements SampleProducer
+{
+
+    //======================================================================
+    // Private Fields
+
+    private final Set<SampleListener> myListeners = new 
HashSet<SampleListener>();
+
+    //======================================================================
+    // Public Methods
+
+    //----------------------------------------------------------------------
+    // SampleProducer Implementation
+
+    public final void addListener( final SampleListener sampleListener )
+    {
+        ParameterChecker.checkNotNull( sampleListener, "addedListener" );
+        ParameterChecker.checkNotAlreadyContained( sampleListener, 
myListeners, "myListeners" );
+
+        myListeners.add( sampleListener );
+    }
+
+
+    public final void removeListener( final SampleListener sampleListener )
+    {
+        ParameterChecker.checkNotNull( sampleListener, "removedListener" );
+        ParameterChecker.checkContained( sampleListener, myListeners, 
"myListeners" );
+
+        myListeners.remove( sampleListener );
+    }
+
+    //======================================================================
+    // Protected Methods
+
+    /**
+     * @return true if there are any registered listeners.
+     */
+    protected final boolean hasListeners()
+    {
+        return myListeners.size() > 0;
+    }
+
+
+    /**
+     * Sends the specified {@link DataSample} to registered listeners.
+     */
+    protected final void sendSample( final DataSample sample )
+    {
+        for ( final SampleListener listener : myListeners )
+        {
+            listener.onSample( sample );
+        }
+    }
+
+
+    /**
+     * Sends the specified {@link DataSample}s to registered listeners.
+     */
+    protected final void sendSamples( final List<DataSample> samples )
+    {
+        for ( final SampleListener listener : myListeners )
+        {
+            listener.onSamples( samples );
+        }
+    }
+
+}

Added: 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/DataSample.java
===================================================================
--- 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/DataSample.java
                                (rev 0)
+++ 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/DataSample.java
        2008-04-27 20:55:38 UTC (rev 488)
@@ -0,0 +1,207 @@
+package org.skycastle.sketch.sample;
+
+import gnu.trove.TIntFloatHashMap;
+import gnu.trove.TIntFloatProcedure;
+
+import java.io.Serializable;
+
+/**
+ * Contains information about some variables.
+ * <p/>
+ * Designed for use in sequence - e.g. strokes, or event handling.
+ * <p/>
+ * Individual {@link DataSample}s only tell the values of the variables that 
changed.
+ * <p/>
+ * Uses {@link SketchVariableRegistry} to keep track of a mapping between 
variable names and variable id:s.
+ * This allows it to minimize requires storage space.
+ *
+ * @author Hans Haggstrom
+ */
+public final class DataSample
+        implements Serializable
+{
+
+    //======================================================================
+    // Private Fields
+
+    private TIntFloatHashMap myVariables = new TIntFloatHashMap( 2 );
+    private transient TIntFloatProcedure myCopyValuesProcedure = null;
+
+    //======================================================================
+    // Private Constants
+
+    private static final long serialVersionUID = 1L;
+
+    //======================================================================
+    // Public Methods
+
+    //----------------------------------------------------------------------
+    // Constructors
+
+    /**
+     * Creates a new {@link DataSample}.
+     */
+    public DataSample()
+    {
+    }
+
+    /**
+     * Creates a new {@link DataSample} that has a copy of the variables from 
the specified source sample.
+     */
+    public DataSample( final DataSample source )
+    {
+        updateValuesWith( source );
+    }
+
+    //----------------------------------------------------------------------
+    // Other Public Methods
+
+    /**
+     * @return true if the {@link DataSample} has any variable values.
+     */
+    public boolean hasValues()
+    {
+        return !myVariables.isEmpty();
+    }
+
+
+    /**
+     * @param variable the name of the variable to set.
+     * @param value    the value to set the variable to.
+     */
+    public void setVariable( final String variable, final float value )
+    {
+        setVariable( getVariableId( variable ), value );
+    }
+
+
+    /**
+     * @param variableId the id of the variable to set.
+     * @param value      the value to set the variable to.
+     */
+    public void setVariable( final int variableId, final float value )
+    {
+        myVariables.put( variableId, value );
+    }
+
+
+    /**
+     * @param variable the name of the variable to remove, if found.
+     */
+    public void removeVariable( final String variable )
+    {
+        removeVariable( getVariableId( variable ) );
+    }
+
+
+    /**
+     * @return true if the specified variable has a defined value in this 
{@link DataSample}.
+     */
+    public boolean hasVariable( final String variable )
+    {
+        return hasVariable( getVariableId( variable ) );
+    }
+
+
+    /**
+     * @return true if the variable with the speicfied id has a defined value 
in this {@link DataSample}.
+     */
+    public boolean hasVariable( final int variableId )
+    {
+        return myVariables.containsKey( variableId );
+    }
+
+
+    /**
+     * @return the value of the specified variable if found, otherwise the 
specified default value.
+     */
+    public float getVariable( final String variable, final float defaultValue )
+    {
+        return getVariable( getVariableId( variable ), defaultValue );
+    }
+
+
+    /**
+     * @return the value of the variable with the speicfied id if found, 
otherwise the specified default
+     *         value.
+     */
+    public float getVariable( final int variableId, final float defaultValue )
+    {
+        if ( myVariables.containsKey( variableId ) )
+        {
+            return myVariables.get( variableId );
+        }
+        else
+        {
+            return defaultValue;
+        }
+    }
+
+
+    /**
+     * @param procedure a procedure to run for each entry in the variable name 
to variable value map.
+     */
+    public void forEachEntry( final TIntFloatProcedure procedure )
+    {
+        myVariables.forEachEntry( procedure );
+    }
+
+
+    /**
+     * Adds the variable values specified by the input {@link DataSample} to 
this {@link DataSample},
+     * overwriting previous values if they exists.
+     * <p/>
+     * Can be used to build up a current state of the variable values when 
reading a stream of {@link
+     * DataSample}s that only report changes in variable values.
+     *
+     * @param dataSample the {@link DataSample} whose variable values we 
should apply to this {@link
+     *                   DataSample}.
+     */
+    public void updateValuesWith( final DataSample dataSample )
+    {
+        // Lazy init of the copier.  Typically we only need this for the data 
samples that are used to
+        // aggregate the current state of some stream of data samples.
+        if ( myCopyValuesProcedure == null )
+        {
+            myCopyValuesProcedure = createCopyValuesProcedure();
+        }
+
+        //noinspection AccessingNonPublicFieldOfAnotherObject
+        dataSample.forEachEntry( myCopyValuesProcedure );
+    }
+
+    //======================================================================
+    // Private Methods
+
+    /**
+     * @param variableId the id of the the variable to remove, if found.
+     */
+    private void removeVariable( final int variableId )
+    {
+        myVariables.remove( variableId );
+    }
+
+
+    private int getVariableId( final String variable )
+    {
+        return SketchVariableRegistry.getRegistry().getId( variable );
+    }
+
+
+    private TIntFloatProcedure createCopyValuesProcedure()
+    {
+        return new TIntFloatProcedure()
+        {
+
+            @SuppressWarnings( { "ParameterNameDiffersFromOverriddenParameter" 
} )
+            public boolean execute( final int variableId, final float value )
+            {
+                setVariable( variableId, value );
+
+                return true;
+            }
+
+        };
+    }
+
+}

Added: 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/DataVariableRegistry.java
===================================================================
--- 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/DataVariableRegistry.java
                              (rev 0)
+++ 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/DataVariableRegistry.java
      2008-04-27 20:55:38 UTC (rev 488)
@@ -0,0 +1,87 @@
+package org.skycastle.sketch.sample;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Keeps track of the names of the variables that are stored in {@link 
DataSample}s.
+ *
+ * @author Hans Haggstrom
+ */
+public final class DataVariableRegistry
+{
+
+    //======================================================================
+    // Private Fields
+
+    private final List<String> myIdToName = new ArrayList<String>();
+    private final Map<String, Integer> myNameToId = new HashMap<String, 
Integer>();
+    private final Object myLock = new Object();
+
+    //======================================================================
+    // Public Methods
+
+    //----------------------------------------------------------------------
+    // Constructors
+
+    /**
+     * Creates a new {@link DataVariableRegistry}.
+     */
+    public DataVariableRegistry()
+    {
+    }
+
+    //----------------------------------------------------------------------
+    // Other Public Methods
+
+    /**
+     * @param name the name to get or create an id for.
+     *
+     * @return the id for the specified variable name.  If none is registered 
before, a new one is registered
+     *         and returned.
+     */
+    int getId( final String name )
+    {
+        synchronized ( myLock )
+        {
+            if ( myNameToId.containsKey( name ) )
+            {
+                return myNameToId.get( name );
+            }
+            else
+            {
+                final int id = myIdToName.size();
+
+                myIdToName.add( name );
+                myNameToId.put( name, id );
+
+                return id;
+            }
+        }
+    }
+
+
+    /**
+     * @param id the id to get a name for.
+     *
+     * @return the name of the variable with the specified id, or null if none 
registered.
+     */
+    String getName( final int id )
+    {
+        synchronized ( myLock )
+        {
+            if ( id < 0 ||
+                 id >= myIdToName.size() )
+            {
+                return null;
+            }
+            else
+            {
+                return myIdToName.get( id );
+            }
+        }
+    }
+
+}

Added: 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/SampleListener.java
===================================================================
--- 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/SampleListener.java
                            (rev 0)
+++ 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/SampleListener.java
    2008-04-27 20:55:38 UTC (rev 488)
@@ -0,0 +1,23 @@
+package org.skycastle.sketch.sample;
+
+import java.util.List;
+
+/**
+ * @author Hans Haggstrom
+ */
+public interface SampleListener
+{
+    /**
+     * Called when there is a new sample.
+     *
+     * @param dataSample a new {@link DataSample}.
+     */
+    void onSample( DataSample dataSample );
+
+    /**
+     * Called when there is more than one sample
+     *
+     * @param dataSamples zero or more {@link DataSample}s ordered in 
cronological order.
+     */
+    void onSamples( List<DataSample> dataSamples );
+}

Added: 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/SampleProducer.java
===================================================================
--- 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/SampleProducer.java
                            (rev 0)
+++ 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/SampleProducer.java
    2008-04-27 20:55:38 UTC (rev 488)
@@ -0,0 +1,20 @@
+package org.skycastle.sketch.sample;
+
+/**
+ * Something that produces {@link DataSample}s.
+ *
+ * @author Hans Haggstrom
+ */
+public interface SampleProducer
+{
+    /**
+     * @param sampleListener A listener that is notified when there are more 
{@link DataSample}s.
+     */
+    void addListener( SampleListener sampleListener );
+
+    /**
+     * @param sampleListener listener to remove.
+     */
+    void removeListener( SampleListener sampleListener );
+
+}

Added: 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/SketchVariableRegistry.java
===================================================================
--- 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/SketchVariableRegistry.java
                            (rev 0)
+++ 
trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/SketchVariableRegistry.java
    2008-04-27 20:55:38 UTC (rev 488)
@@ -0,0 +1,38 @@
+package org.skycastle.sketch.sample;
+
+/**
+ * A singleton class for getting a {@link DataVariableRegistry} for the Sketch 
Room application.
+ *
+ * @author Hans Haggstrom
+ */
+public final class SketchVariableRegistry
+{
+
+    //======================================================================
+    // Private Constants
+
+    private static final DataVariableRegistry DATA_VARIABLE_REGISTRY = new 
DataVariableRegistry();
+
+    //======================================================================
+    // Public Methods
+
+    //----------------------------------------------------------------------
+    // Static Methods
+
+    /**
+     * @return the registry used to keep track of the names of the variables 
used in the Sketch Room
+     *         application.
+     */
+    public static DataVariableRegistry getRegistry()
+    {
+        return DATA_VARIABLE_REGISTRY;
+    }
+
+    //======================================================================
+    // Private Methods
+
+    private SketchVariableRegistry()
+    {
+    }
+
+}

Modified: 
trunk/skycastle/modules/ui/src/test/java/org/skycastle/sketch/model/SketchModelTest.java
===================================================================
--- 
trunk/skycastle/modules/ui/src/test/java/org/skycastle/sketch/model/SketchModelTest.java
    2008-04-27 00:52:45 UTC (rev 487)
+++ 
trunk/skycastle/modules/ui/src/test/java/org/skycastle/sketch/model/SketchModelTest.java
    2008-04-27 20:55:38 UTC (rev 488)
@@ -1,12 +1,10 @@
 package org.skycastle.sketch.model;
 
 import junit.framework.TestCase;
-import org.skycastle.sketch.model.point.StrokePointImpl;
 import org.skycastle.sketch.model.stroke.Stroke;
 import org.skycastle.sketch.model.stroke.StrokeImpl;
+import org.skycastle.sketch.sample.DataSample;
 
-import java.awt.Color;
-
 /**
  * @author Hans Häggström
  */
@@ -26,11 +24,11 @@
         final StrokeImpl stroke = new StrokeImpl();
         sketch.getRootGroup().add( stroke );
 
-        final StrokePointImpl strokePoint = new StrokePointImpl( 10.0f, 20.0f 
);
+        final DataSample strokePoint = new DataSample();
         stroke.addPoint( strokePoint );
 
-        strokePoint.setProperty( "foo", 23L );
-        strokePoint.setProperty( "bar", Color.RED );
+        strokePoint.setVariable( "foo", 23 );
+        strokePoint.setVariable( "bar", -0.002f );
 
         final Stroke element = (Stroke) 
sketch.getRootGroup().getElements().get( 0 );
         assertEquals( strokePoint, element.getPoints().get( 0 ) );


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

Other related posts:

  • » [skycastle-commits] SF.net SVN: skycastle: [488] trunk/skycastle/modules/ui/src