View Javadoc
1   /*
2    * License : The MIT License
3    * Copyright(c) 2019 Olyutorskii
4    */
5   
6   package io.github.olyutorskii.quetexj;
7   
8   import java.awt.EventQueue;
9   import javax.swing.BoundedRangeModel;
10  import javax.swing.ButtonModel;
11  import javax.swing.DefaultButtonModel;
12  
13  /**
14   * Automatic tracker that always tracks last position of BoundedRangeModel.
15   * It's usefull for JScrollBar or JSlider view.
16   *
17   * <p>Tracking mode switching is supported by ButtonModel(optional).
18   * If ButtonModel is selected, it is a tracking mode.
19   *
20   * <p>Tracking mode switch is also supported
21   * by special BoundedRangeModel operations.
22   * (Just sliding knob to max manually)
23   */
24  public class MaxTracker {
25  
26      /** Invalid knob position. */
27      private static final int VAL_INVALID = -1;
28  
29  
30      private final BoundedRangeModel rangeModel;
31      private final ButtonModel trackModeModel;
32  
33      /** Knob operation position when tracking-start . */
34      private int trackStartPos;
35  
36  
37      /**
38       * Constructor.
39       *
40       * @param rangeModel BoundedRangeModel.
41       */
42      public MaxTracker(BoundedRangeModel rangeModel) {
43          this(rangeModel, new DefaultButtonModel());
44          return;
45      }
46  
47      /**
48       * Constructor.
49       *
50       * @param rangeModel BoundedRangeModel.
51       * @param trackModeModel ButtonModel for tracking mode.
52       */
53      public MaxTracker(
54              BoundedRangeModel rangeModel,
55              ButtonModel trackModeModel) {
56          super();
57  
58          this.rangeModel = rangeModel;
59          this.trackModeModel = trackModeModel;
60  
61          this.rangeModel.addChangeListener(ev -> {
62              eventBoundedRangeChanged();
63          });
64  
65          this.trackModeModel.addItemListener(ev -> {
66              eventTrackModeChanged();
67          });
68  
69          resetTrackStartPos();
70  
71          return;
72      }
73  
74  
75      /**
76       * Get associated BoundedRangeModel.
77       *
78       * @return BoundedRangeModel
79       */
80      public BoundedRangeModel getBoundedRangeModel() {
81          return this.rangeModel;
82      }
83  
84      /**
85       * Get associated ButtonModel.
86       *
87       * @return ButtonModel
88       */
89      public ButtonModel getButtonModel() {
90          return this.trackModeModel;
91      }
92  
93      /**
94       * Return tracking mode by ButtonModel.
95       *
96       * <p>If ButtonModel is selected, it is a tracking mode.
97       *
98       * @return Return true if tracking mode.
99       */
100     public boolean isTrackingMode() {
101         boolean result = this.trackModeModel.isSelected();
102         return result;
103     }
104 
105     /**
106      * Set tracking mode to ButtonModel.
107      *
108      * <p>It will fire ItemEvent from ButtonModel.
109      *
110      * <p>If ButtonModel is selected, it is a tracking mode.
111      *
112      * <p>If tracking mode is not changed, do nothing.
113      *
114      * @param tracking tracking mode
115      */
116     public void setTrackingMode(boolean tracking) {
117         if (EventQueue.isDispatchThread()) {
118             setTrackingModeImpl(tracking);
119         } else {
120             EventQueue.invokeLater(() -> {
121                 setTrackingModeImpl(tracking);
122             });
123         }
124         return;
125     }
126 
127     /**
128      * Set tracking mode to ButtonModel.
129      *
130      * <p>(EDT only.)
131      *
132      * @param tracking tracking mode
133      */
134     private void setTrackingModeImpl(boolean tracking) {
135         boolean oldCond = isTrackingMode();
136         if (tracking == oldCond) return;
137 
138         this.trackModeModel.setSelected(tracking);
139 
140         return;
141     }
142 
143     /**
144      * Return wheteher BoundedRangeModel is currently adjusted with mouse.
145      *
146      * @return Return true if adjusting.
147      */
148     private boolean isHandAdjusting() {
149         boolean result = this.rangeModel.getValueIsAdjusting();
150         return result;
151     }
152 
153     /**
154      * Return whether knob is touching max in BoundedRangeModel.
155      *
156      * @return Return true if touching.
157      */
158     private boolean isKnobTouchMax() {
159         int val    = this.rangeModel.getValue();
160         int max    = this.rangeModel.getMaximum();
161         int extent = this.rangeModel.getExtent();
162 
163         boolean touchMax = val + extent >= max;
164         return touchMax;
165     }
166 
167     /**
168      * Set track-start operation position.
169      */
170     private void setTrackStartPos() {
171         this.trackStartPos = this.rangeModel.getValue();
172         return;
173     }
174 
175     /**
176      * Reset track-start operation position.
177      */
178     private void resetTrackStartPos() {
179         this.trackStartPos = VAL_INVALID;
180         return;
181     }
182 
183     /**
184      * Return wheteher knob position at track-start operation is keeped.
185      *
186      * @return Return true if keeping.
187      */
188     private boolean keepingTrackStartPos() {
189         int modelVal = this.rangeModel.getValue();
190         boolean result = this.trackStartPos == modelVal;
191         return result;
192     }
193 
194     /**
195      * Force knob to be touching max in BoundedRangeModel.
196      *
197      * <ul>
198      * <li>If knob already touches max, do nothing.
199      * <li>If adjusting knob (by hand), do nothing.
200      * </ul>
201      */
202     private void forceKnobTouchMax() {
203         if (isKnobTouchMax()) return;
204         if (isHandAdjusting()) return;
205 
206         int max    = this.rangeModel.getMaximum();
207         int extent = this.rangeModel.getExtent();
208         int newVal = max - extent;
209         this.rangeModel.setValue(newVal);
210 
211         return;
212     }
213 
214     /**
215      * Determine if tracking mode has been activated
216      * by BoundedRangeModel operation.
217      *
218      * <p>If knob touches max in BoundedRangeModel,
219      * tracking mode is activated.
220      *
221      * <p>Tracking mode is deactivated if knob detaches max again.
222      *
223      * <p>While holding knob by mouse after touching max,
224      * tracking mode is not changed.
225      */
226     private void checkTrackingByKnob() {
227         boolean knobTouchMax = isKnobTouchMax();
228         if (knobTouchMax) {
229             setTrackingMode(true);
230             setTrackStartPos();
231         } else if (!keepingTrackStartPos()) {
232             setTrackingMode(false);
233         }
234 
235         return;
236     }
237 
238     /**
239      * Receive ChangeListener event from BoundedRangeModel.
240      */
241     private void eventBoundedRangeChanged() {
242         if (isHandAdjusting()) {
243             checkTrackingByKnob();
244             return;
245         } else {
246             resetTrackStartPos();
247         }
248 
249         if (isTrackingMode()) {
250             forceKnobTouchMax();
251         }
252 
253         return;
254     }
255 
256     /**
257      * Receive ItemListener event from ButtonModel.
258      *
259      * <p>It means changing track mode event with checkbutton-view.
260      */
261     private void eventTrackModeChanged() {
262         if (isTrackingMode()) {
263             forceKnobTouchMax();
264         }
265         return;
266     }
267 
268 }