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.awt.Component; 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.HashMap; 026import java.util.HashSet; 027import java.util.List; 028import java.util.logging.Logger; 029import java.util.Map; 030import java.util.Set; 031 032import jpen.event.PenManagerListener; 033import jpen.internal.BuildInfo; 034import jpen.internal.ObjectUtils; 035import jpen.owner.awt.AwtPenOwner; 036import jpen.owner.PenOwner; 037import jpen.provider.system.MouseDevice; 038 039/** 040Create a {@code PenManager} to start using JPen, {@link jpen.owner.multiAwt.AwtPenToolkit} contains one ready to be used. 041*/ 042public final class PenManager { 043 044 private static final Logger L=Logger.getLogger(PenManager.class.getName()); 045 046 public static String getJPenFullVersion() { 047 return BuildInfo.getFullVersion(); 048 } 049 050 private static int instanceCount; 051 private static boolean singletonMode; 052 053 private synchronized static void incrementInstanceCount() { 054 if(singletonMode && instanceCount!=0) 055 throw new IllegalStateException("can't create more than one PenManager when in singleton mode"); 056 instanceCount++; 057 } 058 059 /** 060 See {@link PenOwner#enforceSinglePenManager()}. 061 */ 062 private synchronized static void setSingletonMode(boolean singletonMode) { 063 if(singletonMode && instanceCount>1) 064 throw new IllegalStateException("can't change singleton mode to true when many PenManagers has already being created"); 065 PenManager.singletonMode=singletonMode; 066 } 067 068 public final Pen pen=new Pen(this); 069 public final PenOwner penOwner; 070 private final Set<PenProvider.Constructor> providerConstructors=Collections.synchronizedSet(new HashSet<PenProvider.Constructor>()); 071 private final Set<PenProvider.Constructor> providerConstructorsA=Collections.unmodifiableSet(providerConstructors); 072 private final Map<Byte, PenDevice> deviceIdToDevice=new HashMap<Byte, PenDevice>(Byte.MAX_VALUE, 1f); 073 private final Collection<PenDevice> devicesA=Collections.unmodifiableCollection(deviceIdToDevice.values()); 074 private byte nextDeviceId; 075 private volatile boolean paused=true; 076 private final List<PenManagerListener> listeners=new ArrayList<PenManagerListener>(); 077 private PenManagerListener[] listenersArray; 078 final PenDevice emulationDevice; 079 private PenDevice systemMouseDevice; // may be null 080 081 /** 082 Creates an {@code AwtPenOwner} and calls the {@link #PenManager(PenOwner)} constructor. <b>Warning:</b> see {@link jpen.owner.awt.AwtPenOwner#AwtPenOwner(Component)}. 083 */ 084 public PenManager(Component component) { 085 this(new AwtPenOwner(component)); 086 } 087 088 public PenManager(PenOwner penOwner) { 089 if(penOwner.enforceSinglePenManager()) 090 setSingletonMode(true); 091 incrementInstanceCount(); 092 this.penOwner=penOwner; 093 synchronized(pen.scheduler) { 094 PenProvider.Constructor emulationProviderConstructor=new EmulationProvider.Constructor(); 095 addProvider(emulationProviderConstructor); 096 @SuppressWarnings("unchecked") 097 EmulationProvider emulationProvider=(EmulationProvider)emulationProviderConstructor.getConstructed(); 098 emulationDevice=emulationProvider.device; 099 100 penOwner.setPenManagerHandle(new PenOwner.PenManagerHandle() { 101 //@Override 102 public PenManager getPenManager() { 103 return PenManager.this; 104 } 105 //@Override 106 public Object getPenSchedulerLock() { 107 return pen.scheduler; 108 } 109 //@Override 110 public void setPenManagerPaused(boolean paused) { 111 PenManager.this.setPaused(paused); 112 } 113 //@Override 114 public Object retrievePenEventTag(PenEvent ev) { 115 return ev.getPenOwnerTag(); 116 } 117 } 118 ); 119 } 120 addPenOwnerProviders(); 121 } 122 123 /** 124 In some cases, constructing providers takes considerable time (wintab), so we add them in a new thread. 125 */ 126 private void addPenOwnerProviders() { 127 Thread thread=new Thread("jpen-PenManager-addPenOwnerProviders") { 128 @Override 129 public void run() { 130 synchronized(pen.scheduler) { 131 for(PenProvider.Constructor penProviderConstructor: PenManager.this.penOwner.getPenProviderConstructors()) 132 addProvider(penProviderConstructor); 133 } 134 synchronized(PenManager.this) { 135 providerConstructorsInitialized=true; 136 PenManager.this.notifyAll(); 137 } 138 } 139 }; 140 thread.setPriority(Thread.MIN_PRIORITY); 141 thread.start(); 142 } 143 private volatile boolean providerConstructorsInitialized=false; 144 145 private synchronized void waitForProviderConstructorsInitialization() { 146 while(!providerConstructorsInitialized) 147 ObjectUtils.waitUninterrupted(this); 148 } 149 150 /** 151 @return the mouse PenProvider or {@code null} if no mouse provider has been added. 152 @see #addProvider(PenProvider.Constructor) 153 */ 154 public PenProvider getSystemMouseProvider() { 155 return systemMouseDevice==null? null: systemMouseDevice.getProvider(); 156 } 157 158 public boolean isSystemMouseDevice(PenDevice device) { 159 return device!=null && device==systemMouseDevice; 160 } 161 162 /** 163 Constructs and adds provider if {@link PenProvider.Constructor#constructable(PenManager)} is true. 164 @return The {@link PenProvider} added or null if it couldn't be constructed. 165 */ 166 private PenProvider addProvider(PenProvider.Constructor providerConstructor) { 167 if(providerConstructor.constructable(this)) { 168 if(!this.providerConstructors.add(providerConstructor)) 169 throw new IllegalArgumentException("constructor already added"); 170 if(providerConstructor.construct(this)) { 171 PenProvider provider=providerConstructor.getConstructed(); 172 if(!getPaused()) 173 provider.penManagerPaused(false);// the provider is paused after construction, so it is not necessary to call when true 174 for(PenDevice device:provider.getDevices()) 175 firePenDeviceAdded(providerConstructor, device); 176 return provider; 177 } 178 } 179 return null; 180 } 181 182 public void addListener(PenManagerListener l) { 183 synchronized(listeners) { 184 listeners.add(l); 185 listenersArray=null; 186 } 187 } 188 189 public void removeListener(PenManagerListener l) { 190 synchronized(listeners) { 191 listeners.remove(l); 192 listenersArray=null; 193 } 194 } 195 196 PenManagerListener[] getListenersArray() { 197 synchronized(listeners) { 198 if(listenersArray==null) 199 listenersArray=listeners.toArray(new PenManagerListener[listeners.size()]); 200 return listenersArray; 201 } 202 } 203 204 public void firePenDeviceAdded(PenProvider.Constructor constructor, PenDevice device) { 205 byte nextDeviceId=getNextDeviceId(); 206 device.penManagerSetId(nextDeviceId); 207 if(deviceIdToDevice.put(nextDeviceId, device)!=null) 208 throw new AssertionError(); 209 if(systemMouseDevice==null && device instanceof MouseDevice) 210 this.systemMouseDevice=device; 211 for(PenManagerListener l: getListenersArray()) { 212 l.penDeviceAdded(constructor, device); 213 } 214 } 215 216 private byte getNextDeviceId() { 217 Set<Byte> deviceIds=deviceIdToDevice.keySet(); 218 while(deviceIds.contains(Byte.valueOf(nextDeviceId))) 219 nextDeviceId++; 220 if(nextDeviceId<0) 221 throw new IllegalStateException(); 222 return nextDeviceId; 223 } 224 225 public void firePenDeviceRemoved(PenProvider.Constructor constructor, PenDevice device) { 226 if(deviceIdToDevice.remove(device.getId())==null) 227 throw new IllegalArgumentException("device not found"); 228 for(PenManagerListener l: getListenersArray()) 229 l.penDeviceRemoved(constructor, device); 230 if(systemMouseDevice==device) 231 this.systemMouseDevice=null; 232 } 233 234 public PenDevice getDevice(byte deviceId) { 235 return deviceIdToDevice.get(Byte.valueOf(deviceId)); 236 } 237 238 public Collection<PenDevice> getDevices() { 239 return devicesA; 240 } 241 242 /** 243 @deprecated replacement: #getProviderConstructors(). 244 */ 245 @Deprecated 246 public Set<PenProvider.Constructor> getConstructors() { 247 return getProviderConstructors(); 248 } 249 250 public Set<PenProvider.Constructor> getProviderConstructors() { 251 waitForProviderConstructorsInitialization(); 252 return providerConstructorsA; 253 } 254 255 void setPaused(boolean paused) { 256 synchronized(pen.scheduler) { 257 if(this.paused==paused) 258 return; 259 pen.scheduler.setPaused(paused); 260 this.paused=paused; 261 PenProvider.Constructor[] providerConstructorsArray=providerConstructors.toArray(new PenProvider.Constructor[0]); // I don't want to wait for the providerConstructors initialization so I do a copy. 262 for(PenProvider.Constructor providerConstructor: providerConstructorsArray) { 263 PenProvider penProvider=providerConstructor.getConstructed(); 264 if(penProvider!=null) 265 penProvider.penManagerPaused(paused); 266 } 267 } 268 } 269 270 public boolean getPaused() { 271 synchronized(pen.scheduler) { 272 return paused; 273 } 274 } 275 276 /** 277 Schedules button events. You must construct a new {@code PButton} each time you call this method (do not reuse). 278 */ 279 public void scheduleButtonEvent(PenDevice device, long deviceTime, PButton button) { 280 if(paused) 281 return; 282 pen.scheduler.scheduleButtonEvent(device, deviceTime, button); 283 } 284 285 /** 286 Schedules scroll events. You must construct a new {@code PScroll} each time you call this method (do not reuse). 287 */ 288 public void scheduleScrollEvent(PenDevice device, long deviceTime, PScroll scroll) { 289 if(paused) 290 return; 291 pen.scheduler.scheduleScrollEvent(device, deviceTime, scroll); 292 } 293 294 public boolean scheduleLevelEvent(PenDevice device, long deviceTime, Collection<PLevel> levels) { 295 return scheduleLevelEvent(device, deviceTime, levels, false); 296 } 297 298 /** 299 Schedules level events. You can reuse the levels {@code Collection} but you must construct new {@code PLevel}s each time you call this method. 300 */ 301 public boolean scheduleLevelEvent(PenDevice device, long deviceTime, Collection<PLevel> levels, boolean levelsOnScreen) { 302 if(paused) 303 return false; 304 return pen.scheduler.scheduleLevelEvent(device, deviceTime, levels, levelsOnScreen); 305 } 306 307 /** 308 Uses reflection to get the first provider of the given class. 309 */ 310 public <T extends PenProvider> T getProvider(Class<T> providerClass) { 311 for(PenProvider.Constructor constructor: getProviderConstructors()) { 312 PenProvider penProvider=constructor.getConstructed(); 313 if(providerClass.isInstance(penProvider)) 314 return providerClass.cast(penProvider); 315 } 316 return null; 317 } 318}