001/* [{
002Copyright 2007-2011 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.owner.awt;
020
021import java.awt.Component;
022import java.awt.Dialog;
023import java.awt.event.MouseEvent;
024import java.awt.event.MouseMotionListener;
025import java.awt.Window;
026import java.lang.ref.WeakReference;
027import java.util.Arrays;
028import java.util.Collection;
029import javax.swing.SwingUtilities;
030import jpen.internal.ActiveWindowProperty;
031import jpen.owner.AbstractPenOwner;
032import jpen.owner.PenClip;
033import jpen.PenProvider;
034import jpen.provider.osx.CocoaProvider;
035import jpen.provider.system.SystemProvider;
036import jpen.provider.wintab.WintabProvider;
037import jpen.provider.xinput.XinputProvider;
038
039public abstract class ComponentPenOwner
040        extends AbstractPenOwner{
041
042        private final ComponentPenClip componentPenClip=new ComponentPenClip(this);
043
044        public abstract Component getActiveComponent();
045
046        //@Override
047        public final Collection<PenProvider.Constructor> getPenProviderConstructors(){
048                return Arrays.asList(
049                                                 new PenProvider.Constructor[]{
050                                                         new SystemProvider.Constructor(),
051                                                         new XinputProvider.Constructor(),
052                                                         new WintabProvider.Constructor(),
053                                                         new CocoaProvider.Constructor(),
054                                                 }
055                                         );
056        }
057
058        //@Override
059        public final PenClip getPenClip(){
060                return componentPenClip;
061        }
062
063        protected final Unpauser unpauser=new Unpauser();
064
065        protected final class Unpauser
066                implements MouseMotionListener{
067
068                private volatile boolean enabled;
069                private WeakReference<Component> myActiveComponentRef;
070
071                public synchronized void enable(){
072                        if(enabled)
073                                return;
074                        Component myActiveComponent=getActiveComponent();
075                        myActiveComponentRef=new WeakReference<Component>(myActiveComponent);
076                        myActiveComponent.addMouseMotionListener(unpauser); // unpauses only when mouse motion is detected.
077                        enabled=true;
078                }
079
080                synchronized void disable(){
081                        if(!enabled)
082                                return;
083                        Component myActiveComponent=myActiveComponentRef.get();
084                        if(myActiveComponent!=null){
085                                myActiveComponent.removeMouseMotionListener(unpauser);
086                                myActiveComponent=null;
087                        }
088                        enabled=false;
089                }
090
091                //@Override
092                public void mouseMoved(MouseEvent ev){
093                        //L.fine("v");
094                        unpause();
095                }
096
097                //@Override
098                public void mouseDragged(MouseEvent ev){
099                }
100
101                void unpause(){
102                        synchronized(penManagerHandle.getPenSchedulerLock()){
103                                if(enabled){
104                                        activeWindowPL.setEnabled(true);
105                                        penManagerHandle.setPenManagerPaused(false);
106                                        disable();
107                                }
108                        }
109                }
110        }
111
112        private final ActiveWindowPL activeWindowPL=new ActiveWindowPL();
113
114        private class ActiveWindowPL
115                implements ActiveWindowProperty.Listener{
116
117                private boolean enabled;
118                private ActiveWindowProperty activeWindowP;
119
120                void setEnabled(boolean enabled){
121                        if(activeWindowP==null)
122                                activeWindowP=new ActiveWindowProperty(this);
123                        this.enabled=enabled;
124                }
125
126                //@Override
127                public void activeWindowChanged(Window activeWindow){
128                        if(!enabled)
129                                return;
130                        synchronized(getPenSchedulerLock(activeWindow)){
131                                if(activeWindow==null){
132                                        // if there is no active window on this application, on MS Windows the mouse stops sending events.
133                                        pauseAMoment();
134                                        return;
135                                }
136                                Window componentWindow=SwingUtilities.getWindowAncestor(getActiveComponent());
137                                if(componentWindow==null)
138                                        return;
139                                if(activeWindow!=componentWindow &&
140                                         activeWindow instanceof Dialog){
141                                        //      A modal dialog stops sending events from other windows when shown... then here we honor this behavior
142                                        Dialog activeDialog=(Dialog)activeWindow;
143                                        if(activeDialog.isModal())
144                                                pauseAMoment();
145                                }
146                        }
147                }
148
149                private void pauseAMoment(){
150                        pause();
151                        unpauser.enable();
152                }
153        }
154
155        protected final void pause(){
156                unpauser.disable();
157                activeWindowPL.setEnabled(false);
158                penManagerHandle.setPenManagerPaused(true);
159        }
160
161        @Override
162        protected void draggingOutDisengaged(){
163                pause();
164        }
165
166        /**
167        Checks if the given {@link Component} holds the {@link Component#getTreeLock()} before actually getting and returning the {@link jpen.owner.PenOwner.PenManagerHandle#getPenSchedulerLock()}. Prefer using this method instead of {@link jpen.owner.PenOwner.PenManagerHandle#getPenSchedulerLock()} to be shure you  aren't causing deadlocks because the {@link ComponentPenClip} methods hold the {@link Component#getTreeLock()}.
168        */
169        protected Object getPenSchedulerLock(Component component){
170                if(component!=null && Thread.currentThread().holdsLock(component.getTreeLock()))
171                        throw new AssertionError("tryed to hold penSchedulerLock while holding Component's treeLock");
172                return penManagerHandle.getPenSchedulerLock();
173        }
174}