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}