001/* [{
002Copyright 2010 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.internal.filter;
020
021import java.awt.geom.Point2D;
022import java.awt.MouseInfo;
023import java.awt.Point;
024import java.awt.PointerInfo;
025import java.security.AccessController;
026import java.security.PrivilegedAction;
027import java.util.Collection;
028import java.util.logging.Logger;
029import jpen.PenDevice;
030import jpen.PenProvider;
031import jpen.PenState;
032import jpen.PLevel;
033
034public final class RelativeLocationFilter{
035        private static final Logger L=Logger.getLogger(RelativeLocationFilter.class.getName());
036        //static { L.setLevel(Level.ALL); }
037
038
039        private PenDevice penDevice;
040        private State state=State.UNDEFINED;
041        public enum State{
042                /**
043                The filter tries to see if the values are absolute or relative and sets the state if possible.
044                */
045                UNDEFINED,
046                /**
047                The filter considers the sample values to be in absolute mode.
048                */
049                ABSOLUTE,
050                /**
051                The filter considers the sample values to be in relative mode and replace them with the reference (mouse pointer info).
052                */
053                RELATIVE,
054                /**
055                The filter does nothing. This state is used when there is not mouse pointer info available on the system.
056                */
057                OFF;
058        }
059        final Point2D.Float reference=new Point2D.Float();
060        final SamplePoint samplePoint=new SamplePoint();
061        static class SamplePoint
062                implements Cloneable{
063                PLevel levelX, levelY;
064                boolean isComplete;
065
066                boolean reset(Collection<PLevel> sample){
067                        levelX=levelY=null;
068                        int valuesCount=0;
069                out:
070                        for(PLevel level: sample){
071                                switch(level.getType()){
072                                case X:
073                                        valuesCount++;
074                                        levelX=level;
075                                        if(levelY!=null)
076                                                break out;
077                                        break;
078                                case Y:
079                                        valuesCount++;
080                                        levelY=level;
081                                        if(levelX!=null)
082                                                break out;
083                                        break;
084                                }
085                        }
086                        isComplete=valuesCount==2;
087                        return valuesCount>0;
088                }
089
090                private void set(float x, float y){
091                        set(x, y, null);
092                }
093
094                private void set(float x, float y, Collection<PLevel> sample){
095                        if(levelX!=null)
096                                levelX.value=x;
097                        else if(sample!=null)
098                                sample.add(new PLevel(PLevel.Type.X, x));
099                        if(levelY!=null)
100                                levelY.value=y;
101                        else if(sample!=null)
102                                sample.add(new PLevel(PLevel.Type.Y, y));
103                }
104
105                @Override
106                public SamplePoint clone(){
107                        try{
108                                SamplePoint clone=(SamplePoint)super.clone();
109                                clone.levelX=new PLevel(levelX);
110                                clone.levelY=new PLevel(levelY);
111                                return clone;
112                        }catch(CloneNotSupportedException ex){
113                                throw new AssertionError(ex);
114                        }
115                }
116        }
117
118        final Point2D.Float deviation=new Point2D.Float();
119        final Point2D.Float absDeviation=new Point2D.Float();
120        private final Rule[] rules=new Rule[]{
121                                //new LogToFileRule(),
122                                new AbsoluteLocationRule(),
123                                new AbsoluteOnARowRule(),
124                                new RelativeOnSlopesRule(),
125                        };
126        interface Rule{
127                void reset();
128                State evalFilterNextState(RelativeLocationFilter filter);
129        }
130
131        public void reset(){
132                penDevice=null;
133                resetRules();
134        }
135
136        private void resetRules(){
137                for(Rule rule: rules){
138                        rule.reset();
139                }
140        }
141
142        /**
143        @return {@code true} if the state changed to a definitive value.
144        */
145        public boolean filter(PenState penState, PenDevice penDevice, Collection<PLevel> sample, boolean levelsOnScreen){
146                if(!levelsOnScreen) // only levelsOnScreen is supported
147                        return false;
148                if(state.equals(State.OFF))
149                        return false;
150                if(this.penDevice!=penDevice){
151                        this.penDevice=penDevice;
152                        state=State.UNDEFINED;
153                        resetRules();
154                }
155                if(state.equals(State.ABSOLUTE))
156                        return false;
157                if(evalIsSystemMouseDevice(penDevice)) // the system device is trusted... no need to filter.
158                        return false;
159                if(!samplePoint.reset(sample))
160                        return false;
161                if(!setupReference())
162                        return true;
163
164                boolean stateChanged=false;
165                if(state.equals(State.UNDEFINED)){
166                        setupDeviation();
167                        stateChanged=evalStateFromRules();
168                }
169                switch(state){
170                case ABSOLUTE:
171                        break;
172                case RELATIVE:
173                        samplePoint.set(reference.x, reference.y, sample);
174                        break;
175                case UNDEFINED:
176                        samplePoint.set(penState.getLevelValue(PLevel.Type.X),
177                                                                                        penState.getLevelValue(PLevel.Type.Y)); // then it won't cause a level event because movement
178                        break;
179                default:
180                }
181                return stateChanged;
182        }
183
184        private static boolean evalIsSystemMouseDevice(PenDevice device){
185                return device.getProvider().getConstructor().getPenManager().isSystemMouseDevice(device);
186        }
187
188        private boolean setupReference(){
189                PointerInfo pointerInfo=AccessController.doPrivileged(getPointerInfoAction);
190                if(pointerInfo==null){
191                        L.warning("No mouse found. Can not correct devices on relative (mouse) mode.");
192                        state=State.OFF;
193                        return false;
194                }
195                reference.setLocation(pointerInfo.getLocation());
196                return true;
197        }
198
199        private final PrivilegedAction<PointerInfo> getPointerInfoAction=new PrivilegedAction<PointerInfo>(){
200                                //@Override
201                                public PointerInfo run(){
202                                        return MouseInfo.getPointerInfo();
203                                }
204                        };
205
206        private void setupDeviation(){
207                if(samplePoint.levelX!=null){
208                        deviation.x=samplePoint.levelX.value-reference.x;
209                        absDeviation.x=Math.abs(deviation.x);
210                }
211                if(samplePoint.levelY!=null){
212                        deviation.y=samplePoint.levelY.value-reference.y;
213                        absDeviation.y=Math.abs(deviation.y);
214                }
215        }
216
217        private boolean evalStateFromRules(){
218                for(Rule rule: rules){
219                        State nextState=rule.evalFilterNextState(this);
220                        if(nextState!=null){
221                                this.state=nextState;
222                                return true;
223                        }
224                }
225                return false;
226        }
227
228        public State getState(){
229                return state;
230        }
231}