001/* [{ 002Copyright 2007, 2008 Nicolas Carranza <nicarran at gmail.com> 003 004This file is part of jpen. 005 006jpen is free software: you can redistribute it and/or modify 007it under the terms of the GNU Lesser General Public License as published by 008the Free Software Foundation, either version 3 of the License, 009or (at your option) any later version. 010 011jpen is distributed in the hope that it will be useful, 012but WITHOUT ANY WARRANTY; without even the implied warranty of 013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014GNU Lesser General Public License for more details. 015 016You should have received a copy of the GNU Lesser General Public License 017along with jpen. If not, see <http://www.gnu.org/licenses/>. 018}] */ 019package jpen; 020 021import java.lang.reflect.InvocationTargetException; 022import java.security.AccessController; 023import java.security.PrivilegedAction; 024import java.util.ArrayList; 025import java.util.Collection; 026import java.util.EnumMap; 027import java.util.List; 028import java.util.logging.Level; 029import java.util.logging.Logger; 030import java.util.Queue; 031import javax.swing.SwingUtilities; 032import jpen.event.PenListener; 033import jpen.internal.ThreadUtils; 034import jpen.internal.ThrowableUtils; 035 036public class Pen extends PenState { 037 private static final Logger L=Logger.getLogger(Pen.class.getName()); 038 //static{L.setLevel(Level.ALL);} 039 040 public static final int DEFAULT_FREQUENCY=60; // TODO: 50 is a better default or less?? 041 042 public final PenManager penManager; 043 private int frequency; 044 private volatile MyThread thread; 045 046 /** Tail of event queue. */ 047 private PenEvent lastDispatchedEvent=new PenEvent.Dummy(); 048 final PenScheduler scheduler; 049 public final PenState lastScheduledState; 050 private final List<PenListener> listeners=new ArrayList<PenListener>(); 051 private PenListener[] listenersArray; 052 private boolean firePenTockOnSwing; 053 public final PLevelEmulator levelEmulator; 054 055 private final class MyThread 056 extends Thread { 057 final int periodMillis; 058 long beforeTime; 059 long waitTime; 060 long availablePeriod; 061 PenEvent event; 062 boolean waitedNewEvents; 063 Exception exception; 064 private final Waiter waiter=new Waiter(); 065 volatile boolean stopRunning; 066 Thread oldThread; 067 068 final class Waiter 069 extends Object{ 070 071 boolean waitForNewEvent() throws InterruptedException{ 072 if(lastDispatchedEvent.next!=null) 073 return false; 074 synchronized(this){ 075 if(lastDispatchedEvent.next!=null) 076 return false; 077 if(!stopRunning) 078 wait(0); 079 return true; 080 } 081 } 082 synchronized void notifyNewEvent(){ 083 notify(); 084 } 085 } 086 087 MyThread(Thread oldThread){ 088 periodMillis=1000/Pen.this.frequency; 089 this.oldThread=oldThread; 090 setName("jpen-Pen-["+periodMillis+"ms]"); 091 AccessController.doPrivileged(new PrivilegedAction<Object>(){ 092 //@Override 093 public Object run(){ 094 setDaemon(true); 095 return null; 096 } 097 }); 098 } 099 private final Runnable penTockFirer=new Runnable(){ 100 //@Override 101 public void run(){ 102 //System.out.println("firing tocks "+System.currentTimeMillis()); 103 for(PenListener l:getListenersArray()){ 104 //System.out.println("firing pentock, procTime="+evalCurrentProcTime()+", l="+l); 105 l.penTock(availablePeriodLeft()); 106 } 107 } 108 }; 109 110 public void run() { 111 try { 112 L.finest("v"); 113 if(oldThread!=null) 114 oldThread.join(); 115 oldThread=null; 116 while(!stopRunning) { 117 waitedNewEvents=waiter.waitForNewEvent(); 118 beforeTime=System.currentTimeMillis(); 119 if(waitedNewEvents) 120 waitTime=0; 121 boolean eventDispatched=false; 122 while((event=lastDispatchedEvent.next)!=null && event.getTime()<=beforeTime) { 123 event.copyTo(Pen.this); 124 event.dispatch(); 125 lastDispatchedEvent.next=null; 126 lastDispatchedEvent=event; 127 eventDispatched=true; 128 } 129 //System.out.println("after event dispatching, procTime="+evalCurrentProcTime()); 130 availablePeriod=periodMillis+waitTime; // waitTime here is always <=0, if it is <0 then the whole processing of the previous round took longer than the time available. 131 //System.out.println("going to fire tock "+System.currentTimeMillis()); 132 if(eventDispatched) 133 firePenTock(); 134 //System.out.println("after penTock, procTime="+evalCurrentProcTime()); 135 waitTime=availablePeriodLeft();// the same: (periodMillis-evalCurrentProcTime())+waitTime; 136 if(waitTime>0) { 137 //System.out.println("going to wait: "+waitTime); 138 ThreadUtils.sleepUninterrupted(waitTime); 139 //waiter.doWait(waitTime); // in some cases, this (instead of sleepUninterrupted ^ ) gives better overall performance (wintab-pulling, jpen demo). We can put this as an alternate behavior if needed. 140 waitTime=0; 141 } 142 } 143 } catch(Exception ex) { 144 L.severe("jpen-Pen thread threw an exception: "+ThrowableUtils.evalStackTraceString(ex)); 145 exception=ex; 146 } 147 L.finest("^"); 148 } 149 150 private long evalCurrentProcTime(){ 151 return System.currentTimeMillis()-beforeTime; 152 } 153 154 private long availablePeriodLeft(){ 155 return availablePeriod-evalCurrentProcTime(); 156 } 157 158 private void firePenTock() throws InterruptedException, InvocationTargetException{ 159 if(getListenersArray().length==0) 160 return; 161 if(firePenTockOnSwing) 162 SwingUtilities.invokeAndWait(penTockFirer); 163 else 164 penTockFirer.run(); 165 } 166 167 void stop(boolean join){ 168 stopRunning=true; 169 processNewEvents(); // because it may be waiting for new events. 170 if(join) 171 try{ 172 join(); 173 } 174 catch(InterruptedException ex) { 175 throw new Error(ex); 176 } 177 } 178 } 179 180 Pen(PenManager penManager) { 181 this.penManager=penManager; 182 this.scheduler=new PenScheduler(this); 183 this.lastScheduledState=scheduler.lastScheduledState; 184 this.levelEmulator=new PLevelEmulator(this); 185 setFrequencyLater(DEFAULT_FREQUENCY); 186 } 187 188 void processNewEvents(){ 189 thread.waiter.notifyNewEvent(); 190 } 191 192 PenEvent getLastDispatchedEvent(){ 193 return lastDispatchedEvent; 194 } 195 196 public boolean getFirePenTockOnSwing() { 197 return firePenTockOnSwing; 198 } 199 200 /** 201 @param firePenTockOnSwing If {@code true} then {@link PenListener#penTock(long)} is called from the event dispatch thread. {@code false} by default. 202 */ 203 public void setFirePenTockOnSwing(boolean firePenTockOnSwing){ 204 this.firePenTockOnSwing = firePenTockOnSwing; 205 } 206 207 /** 208 Changes the event firing frequency. The pen collects device (tablet) data points and stores them in a buffer. The data points are taken from this buffer and fired as {@link PenEvent}s at this frequency.<p> 209 210 This method returns immediately, the change of frequency will happen after all the pending events are processed. 211 212 @see #addListener(PenListener) 213 @see #removeListener(PenListener) 214 */ 215 public void setFrequencyLater(int frequency){ 216 setFrequency(frequency, false); 217 } 218 219 private synchronized void setFrequency(int frequency, boolean wait) { 220 if(frequency<=0) 221 throw new IllegalArgumentException(); 222 if(frequency==this.frequency) 223 return; 224 if(wait && SwingUtilities.isEventDispatchThread()) 225 throw new Error("Cannot call setFrequency(int, <true>) from the event dispatcher thread"); 226 L.finest("v"); 227 MyThread oldThread=this.thread; 228 if(oldThread!=null){ 229 oldThread.stop(wait); 230 } 231 this.frequency=frequency; 232 thread=new MyThread(oldThread); 233 thread.start(); 234 L.finest("^"); 235 } 236 237 public int getFrequency() { 238 return frequency; 239 } 240 241 public int getPeriodMillis(){ 242 return thread.periodMillis; 243 } 244 245 public synchronized Exception getThreadException(){ 246 return thread.exception; 247 } 248 249 /** 250 Adds a {@link PenListener} for {@link PenEvent}s fired by this pen. 251 */ 252 public void addListener(PenListener l) { 253 synchronized(listeners) { 254 listeners.add(l); 255 listenersArray=null; 256 } 257 } 258 259 /** 260 Removes a {@link PenListener} previously added using {@link #addListener(PenListener)}. 261 */ 262 public void removeListener(PenListener l) { 263 synchronized(listeners) { 264 listeners.remove(l); 265 listenersArray=null; 266 } 267 } 268 269 PenListener[] getListenersArray() { 270 synchronized(listeners){ 271 if(listenersArray==null) 272 listenersArray=listeners.toArray(new PenListener[listeners.size()]); 273 return listenersArray; 274 } 275 } 276}