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
00071 return;
00072 }
00073
00074 if (lastTempStroke != null) {
00075 strokes.remove(lastTempStroke);
00076 }
00077
00078
00079
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
00235
00236
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
00247
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
00255
00256
00257
00258
00259
00260 if (timeDiffBetweenPenUpAndPenDown > MAX_MILLIS_FOR_PEN_ERROR ) {
00261
00262
00263
00264 lastInkNotifier = null;
00265
00266
00267 distanceTraveled = 0;
00268 lastXForDistanceMeasurements = xDots;
00269 lastYForDistanceMeasurements = yDots;
00270
00271
00272 currentStrokeSamples = new ArrayList<PenSample>();
00273 currentStrokeSamples.add(new PenSample(xDots, yDots, 128, timestamp));
00274 } else {
00275
00276
00277
00278 lastInkNotifier.setDoNotNotify(true);
00279 lastInkNotifier = null;
00280
00281
00282 currentStrokeSamples.add(new PenSample(xDots, yDots, 128, timestamp));
00283 }
00284 } else if (event.isTypePenUp()) {
00285
00286
00287
00288 lastPenUpTime = System.currentTimeMillis();
00289
00290
00291
00292
00293
00294 lastInkNotifier = new InkNotifier(currentStrokeSamples,
00295 mostRecentlyAddedTemporaryStroke);
00296 new Thread(lastInkNotifier).start();
00297
00298
00299
00300 } else {
00301 currentStrokeSamples.add(new PenSample(xDots, yDots, 128, timestamp));
00302
00303
00304 if (notifyAfterEnoughDistance) {
00305
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 }