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}