InkCollector.java

00001 package edu.stanford.hci.r3.events.handlers;
00002 
00003 import java.io.File;
00004 import java.util.ArrayList;
00005 import java.util.Collections;
00006 import java.util.Date;
00007 import java.util.List;
00008 
00009 import edu.stanford.hci.r3.events.EventHandler;
00010 import edu.stanford.hci.r3.events.PenEvent;
00011 import edu.stanford.hci.r3.pen.PenSample;
00012 import edu.stanford.hci.r3.pen.ink.Ink;
00013 import edu.stanford.hci.r3.pen.ink.InkStroke;
00014 import edu.stanford.hci.r3.units.PatternDots;
00015 import edu.stanford.hci.r3.units.Units;
00016 import edu.stanford.hci.r3.units.coordinates.PercentageCoordinates;
00017 import edu.stanford.hci.r3.util.MathUtils;
00018 
00040 public abstract class InkCollector extends EventHandler {
00041 
00047         private class InkNotifier implements Runnable {
00048 
00049                 private boolean doNotNotify;
00050 
00051                 private InkStroke lastTempStroke;
00052 
00053                 private List<PenSample> strokeSamples;
00054 
00055                 public InkNotifier(List<PenSample> currentStrokeSamples, InkStroke tempStroke) {
00056                         strokeSamples = currentStrokeSamples;
00057                         lastTempStroke = tempStroke;
00058                 }
00059 
00063                 public void run() {
00064                         try {
00065                                 Thread.sleep(MILLIS_TO_DELAY);
00066                         } catch (InterruptedException e) {
00067                                 e.printStackTrace();
00068                         }
00069                         if (doNotNotify) {
00070                                 // someone told us to cancel
00071                                 return;
00072                         }
00073 
00074                         if (lastTempStroke != null) {
00075                                 strokes.remove(lastTempStroke);
00076                         }
00077 
00078                         // System.out.println(currentStrokeSamples.size() + " samples in
00079                         // this stroke.");
00080                         addStrokeAndNotifyListeners(strokeSamples);
00081                 }
00082 
00086                 public void setDoNotNotify(boolean b) {
00087                         doNotNotify = b;
00088                 }
00089         }
00090 
00094         private static final PatternDots DOTS = new PatternDots();
00095 
00099         private static final int MAX_MILLIS_FOR_PEN_ERROR = 20;
00100 
00105         private static final int MILLIS_TO_DELAY = 21;
00106 
00110         private List<PenSample> currentStrokeSamples = new ArrayList<PenSample>();
00111 
00112         private long currPenDownTime;
00113 
00114         private double distanceThreshold = 0;
00115 
00119         private double distanceTraveled = 0;
00120 
00121         private InkNotifier lastInkNotifier;
00122 
00126         private long lastPenUpTime;
00127 
00128         private double lastXForDistanceMeasurements;
00129 
00130         private double lastYForDistanceMeasurements;
00131 
00132         private InkStroke mostRecentlyAddedStroke;
00133 
00134         private InkStroke mostRecentlyAddedTemporaryStroke;
00135 
00139         private int newInkMarker = 0;
00140 
00145         private boolean notifyAfterEnoughDistance = false;
00146 
00150         private List<InkStroke> strokes = Collections.synchronizedList(new ArrayList<InkStroke>());
00151 
00152         private long timeDiffBetweenPenUpAndPenDown;
00153 
00154         public InkCollector() {
00155 
00156         };
00157 
00161         private synchronized void addStrokeAndNotifyListeners(List<PenSample> strokeSamples) {
00162                 mostRecentlyAddedStroke = new InkStroke(strokeSamples, DOTS);
00163                 strokes.add(mostRecentlyAddedStroke);
00164                 contentArrived();
00165         }
00166 
00170         private synchronized void addStrokeTemporarilyAndNotifyListeners(List<PenSample> strokeSamples) {
00171                 if (mostRecentlyAddedTemporaryStroke != null) {
00172                         strokes.remove(mostRecentlyAddedTemporaryStroke);
00173                 }
00174                 mostRecentlyAddedTemporaryStroke = new InkStroke(strokeSamples, DOTS);
00175                 strokes.add(mostRecentlyAddedTemporaryStroke);
00176                 contentArrived();
00177         }
00178 
00182         public void clear() {
00183                 strokes.clear();
00184                 currentStrokeSamples.clear();
00185                 newInkMarker = 0;
00186         }
00187 
00188         public abstract void contentArrived();
00189 
00193         public Ink getInk() {
00194                 return new Ink(new ArrayList<InkStroke>(strokes));
00195         }
00196 
00200         public Ink getNewInkOnly() {
00201                 Ink newInk = new Ink(
00202                                 new ArrayList<InkStroke>(strokes.subList(newInkMarker, strokes.size())));
00203                 newInkMarker = strokes.size();
00204                 return newInk;
00205         }
00206 
00210         public int getNumStrokesCollected() {
00211                 return strokes.size();
00212         }
00213 
00218         public long getTimestampOfMostRecentInkStroke() {
00219                 if (strokes != null && strokes.size() >= 1) {
00220                         return strokes.get(strokes.size() - 1).getLastTimestamp();
00221                 } else {
00222                         return -1;
00223                 }
00224         }
00225 
00229         public Date getTimestampOfMostRecentPenUp() {
00230                 return new Date(lastPenUpTime);
00231         }
00232 
00233         /*
00234          * (non-Javadoc)
00235          * 
00236          * @see edu.stanford.hci.r3.events.EventHandler#handleEvent(edu.stanford.hci.r3.events.PenEvent)
00237          */
00238         @Override
00239         public void handleEvent(PenEvent event) {
00240 
00241                 final PercentageCoordinates percentageLocation = event.getPercentageLocation();
00242                 final Units xPct = percentageLocation.getX();
00243                 final Units yPct = percentageLocation.getY();
00244                 final long timestamp = event.getTimestamp();
00245 
00246                 // collect the ink strokes in default units? (i.e., PatternDots?)
00247                 // the thing that renders the ink should decide how to scale it
00248                 final double xDots = xPct.getValueInPatternDots();
00249                 final double yDots = yPct.getValueInPatternDots();
00250 
00251                 if (event.isTypePenDown()) {
00252                         currPenDownTime = System.currentTimeMillis();
00253                         timeDiffBetweenPenUpAndPenDown = currPenDownTime - lastPenUpTime;
00254                         // DebugUtils.println("The pen was up for " +
00255                         // timeDiffBetweenPenUpAndPenDown + "
00256                         // milliseconds");
00257 
00258                         // 20 milliseconds (1/50 of a second) is probably faster than a
00259                         // human can go up and down
00260                         if (timeDiffBetweenPenUpAndPenDown > MAX_MILLIS_FOR_PEN_ERROR /* millis */) {
00261                                 // not a pen error!
00262 
00263                                 // let the last ink notifier run
00264                                 lastInkNotifier = null;
00265 
00266                                 // reset the distance traveled
00267                                 distanceTraveled = 0;
00268                                 lastXForDistanceMeasurements = xDots;
00269                                 lastYForDistanceMeasurements = yDots;
00270 
00271                                 // We should start a new stroke!
00272                                 currentStrokeSamples = new ArrayList<PenSample>();
00273                                 currentStrokeSamples.add(new PenSample(xDots, yDots, 128, timestamp));
00274                         } else {
00275                                 // we'll assume this is a pen manufacturing error (jitter)!
00276 
00277                                 // "kill" the last notifier if possible (best effort)
00278                                 lastInkNotifier.setDoNotNotify(true);
00279                                 lastInkNotifier = null;
00280 
00281                                 // add this sample back to the current stroke
00282                                 currentStrokeSamples.add(new PenSample(xDots, yDots, 128, timestamp));
00283                         }
00284                 } else if (event.isTypePenUp()) {
00285                         // the pen is lifted from the page
00286 
00287                         // record the time of the pen up
00288                         lastPenUpTime = System.currentTimeMillis();
00289 
00290                         // we need to notify our listeners
00291                         // notify after a short delay, because we may actually update the
00292                         // current stroke
00293                         // if there is a pen error
00294                         lastInkNotifier = new InkNotifier(currentStrokeSamples,
00295                                         mostRecentlyAddedTemporaryStroke);
00296                         new Thread(lastInkNotifier).start();
00297 
00298                         // System.out.println("Collected " + strokes.size() + " strokes so
00299                         // far.");
00300                 } else { // regular sample
00301                         currentStrokeSamples.add(new PenSample(xDots, yDots, 128, timestamp));
00302 
00303                         // are we supposed to notify after enough distance?
00304                         if (notifyAfterEnoughDistance) {
00305                                 // assume zero distance for now...
00306                                 distanceTraveled += MathUtils.distance(xDots, yDots, //
00307                                                 lastXForDistanceMeasurements, lastYForDistanceMeasurements);
00308                                 lastXForDistanceMeasurements = xDots;
00309                                 lastYForDistanceMeasurements = yDots;
00310 
00311                                 if (distanceTraveled > distanceThreshold) {
00312                                         addStrokeTemporarilyAndNotifyListeners(currentStrokeSamples);
00313                                 }
00314                         }
00315                 }
00316         }
00317 
00321         public void saveInkToXMLFile(File xmlFile) {
00322                 new Ink(strokes).saveToXMLFile(xmlFile);
00323         }
00324 
00329         public void setNotifyDistance(Units notifyAfterThisMuchPenMovement) {
00330                 if (notifyAfterThisMuchPenMovement == null) {
00331                         distanceThreshold = 0;
00332                         notifyAfterEnoughDistance = false;
00333                 } else {
00334                         distanceThreshold = notifyAfterThisMuchPenMovement.getValueInPatternDots();
00335                         notifyAfterEnoughDistance = true;
00336                 }
00337         }
00338 
00342         @Override
00343         public String toString() {
00344                 return "Ink Collector [" + strokes.size() + " strokes]";
00345         }
00346 }

Generated on Sat Apr 14 18:21:35 2007 for R3 Paper Toolkit by  doxygen 1.4.7