1 /*
2 * Copyright (c) 2003-2008 by Cosylab d. d.
3 *
4 * This file is part of CosyBeans-Common.
5 *
6 * CosyBeans-Common is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * CosyBeans-Common is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with CosyBeans-Common. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 package com.cosylab.gui.components.range2;
21
22 import java.util.ArrayList;
23
24
25 /**
26 * <p>
27 * RangedValue is used as pasive data representation, where each change to the
28 * RangedValue is reported via RangedValueListener. This ensures centralized
29 * handling of all changes and modifications.<br>
30 * This is also the central class to be used when working with ranged value.
31 * Typical use can be shown as:<br>
32 * <code> class Example implements RangedValueListener {<br>
33 * ...<br>
34 * RangedValue value = new RangedValue();<br>
35 * value.addRangeValueListener(this);<br>
36 * value.addChangePolicy(new RescalingValuePolicy()); value.setRange(new
37 * LogarithmicRange()); ... void valueChanged(RangedValueEvent event) { if
38 * (event.minOrMaxChanged()) { // repaint scale } if (event.valueChanged()) {
39 * // repaint value }}</code>
40 * </p>
41 *
42 * <p>
43 * RangedValue will ensure that value vill always be consistant within min, max in range and
44 * policies.
45 *
46 * @author <a href="mailto:ales.pucelj@cosylab.com">Ales Pucelj</a>
47 * @version $id$
48 */
49 public class RangedValueController
50 {
51 private Range range = null;
52 private double rawValue;
53 private final ArrayList<RangedValueListener> listeners = new ArrayList<RangedValueListener>();
54 private double scalingFactor=1.0;
55
56 private RangedValue rangedValue= new RangedValue();
57 private RangedValuePolicy policy;
58
59 private TickCalculator tickCalculator = null;
60 private Tick[] lastTicksUsed = null;
61 private boolean snapToTicks = false;
62
63 /**
64 * Creates default range implementation. Override this method to provide
65 * different default implementation. By default, <code>LinearRange</code>
66 * class is created.
67 *
68 * @return Range range Range interface implementation.
69 */
70 protected Range createDefaultRange()
71 {
72 return new LinearRange();
73 }
74 /**
75 * Returns current range definition.
76 *
77 * @see com.cosylab.gui.components.range2.RangedValueController#getRange()
78 */
79 public Range getRange()
80 {
81 if (range == null) {
82 range = createDefaultRange();
83 }
84 return range;
85 }
86
87 /**
88 * Sets the new definition of <code>Range</code>.
89 *
90 * @see com.cosylab.gui.components.range2.RangedValueController#setRange(Range)
91 */
92 public void setRange(Range newRange)
93 {
94 // Save current range in case parameters cannot be adjusted to new
95 // range
96 range = newRange;
97
98 if (range != null) {
99 // Try tranferring the state
100 setRangedValue(new RangedValue(getMinimum(),getMaximum(),getRawValue()));
101 }
102 }
103
104 /**
105 * Sets the RangedValue to this controller.
106 *
107 * @param v new ranged value
108 */
109 public void setRangedValue(RangedValue v) {
110 if (snapToTicks && lastTicksUsed != null) {
111 final Tick[] ticks = lastTicksUsed;
112 double value = v.getValue();
113 for (int i = 1; i < ticks.length; i++) {
114 if (ticks[i-1].absolute == value) break;
115 if (ticks[i-1].absolute < value && ticks[i].absolute > value) {
116 if (ticks[i].absolute-value < value - ticks[i-1].absolute) {
117 value = ticks[i].absolute;
118 } else {
119 value = ticks[i-1].absolute;
120 }
121 break;
122 }
123 }
124 v = new RangedValue(v.getMinimum(), v.getMaximum(), value);
125 }
126 final double raw=v.getValue();
127
128 // Make sure new values are valid in range
129 final Range r = getRange();
130
131 v = new RangedValue(r.validate(v.getMinimum()),r.validate(v.getMaximum()), r.validate(v.getValue()));
132
133 // Apply policies
134 v = validatePolicies(v);
135
136 // Validate again if policieas has not moved ouside range
137 v = new RangedValue(r.validate(v.getMinimum()),r.validate(v.getMaximum()), r.validate(v.getValue()));
138
139 // find out what has changed
140 boolean chMin = (getMinimum() != v.getMinimum());
141 boolean chMax = (getMaximum() != v.getMaximum());
142 boolean chVal = (getValue() != v.getValue());
143
144
145 if (chMin || chMax || chVal) {
146 rangedValue=new RangedValue(v.getMinimum(),v.getMaximum(),v.getValue());
147 }
148
149 boolean chRaw= raw!=rawValue;
150 if (chRaw) {
151 rawValue=raw;
152 }
153
154
155 fireValueChange(chMin, chMax, chVal, chRaw);
156
157 }
158
159 /**
160 * Returns the RangedValue employed by this controller.
161 *
162 * @return
163 */
164 public RangedValue getRangedValue() {
165 if (rangedValue==null) {
166 rangedValue= new RangedValue();
167 }
168 return rangedValue;
169 }
170
171 /**
172 * Returns current minimum the value can take. Depending on effective
173 * policies, value could still be set below minimum.
174 *
175 * @return double current minimum.
176 *
177 * @see com.cosylab.gui.components.range2.RangedValueController#getMinimum()
178 */
179 public double getMinimum()
180 {
181 return getRangedValue().getMinimum();
182 }
183
184 /**
185 * Returns current maximum the value can take. Depending on effective
186 * policies, value could still be set above maximum.
187 *
188 * @return double current maximum.
189 *
190 * @see com.cosylab.gui.components.range2.RangedValueController#getMaximum()
191 */
192 public double getMaximum()
193 {
194 return getRangedValue().getMaximum();
195 }
196
197 /**
198 * Returns current value.
199 *
200 * @return double current value.
201 *
202 * @see com.cosylab.gui.components.range2.RangedValueController#getValue()
203 */
204 public double getValue()
205 {
206 return getRangedValue().getValue();
207 }
208
209 /**
210 * Sets new value. This will attempt to set new value. If new value is
211 * outside of range or policies are set, new value might not be the same
212 * as the one set. Notification about this change will be reported via
213 * RangedValueListener, where event can be queried about the actual changes.<br>
214 * setValue(double, double, double) should be called before using this
215 * method for the first time to ensure the values. This is not neccessary
216 * if no policies are in effect or values are known to conform to with
217 * them.
218 *
219 * @see com.cosylab.gui.components.range2.RangedValueController#setValue()
220 */
221 public void setValue(double value)
222 {
223 setValue(getMinimum(), getMaximum(), value);
224 }
225
226 /**
227 * Sets new minimum. Setting the minimum may have side effects based on
228 * range definition, policies and current value or maximum. Actual change
229 * will be reported via RangedValueListener.<br>
230 * setValue(double, double, double) should be called before using this
231 * method for the first time to ensure the values. This is not neccessary
232 * if no policies are in effect or values are known to conform to with
233 * them.
234 *
235 * @param value double
236 */
237 public void setMinimum(double value)
238 {
239 setValue(value, getMaximum(), getValue());
240 }
241
242 /**
243 * Sets new maximum. Setting the maximum may have side effects based on
244 * range definition, policies and current value or minimum. Actual change
245 * will be reported via RangedValueListener.<br>
246 * setValue(double, double, double) should be called before using this
247 * method for the first time to ensure the values. This is not neccessary
248 * if no policies are in effect or values are known to conform to with
249 * them.
250 *
251 * @param value double
252 */
253 public void setMaximum(double value)
254 {
255 setValue(getMinimum(), value, getValue());
256 }
257
258 /**
259 * Checks all policies to give them oportunity to modify the values.
260 *
261 * @param values holder of values.
262 */
263 public RangedValue validatePolicies(RangedValue values)
264 {
265
266 if (policy!=null) {
267 values = policy.validate(values);
268 }
269
270 // this is mandatory policy, keeps value visthin min/max.
271 return TrimValuePolicy.trim(values);
272
273 }
274
275 /**
276 * Initializes all values to new values. Minimum, value and maximum should
277 * be in this order when compared to each other.
278 *
279 * @param minimum double
280 * @param maximum double
281 * @param value double
282 *
283 * @throws InvalidBoundException In case the parameters do not conform with
284 * policies defined by this range.
285 */
286 public synchronized void setValue(double minimum, double maximum,
287 double value)
288 {
289 setRangedValue(new RangedValue(minimum,maximum,value));
290 }
291
292 /**
293 * Notify listeners of the change.
294 *
295 * @param event
296 */
297 protected void fireValueChange(RangedValueEvent event)
298 {
299 if (event == null) {
300 return;
301 }
302
303 synchronized (this) {
304 final int n = listeners.size();
305
306 for (int i = 0; i < n; i++) {
307 (listeners.get(i)).valueChanged(event);
308 }
309 }
310 }
311
312 protected void fireValueChange(boolean min, boolean max, boolean val, boolean raw)
313 {
314 if (min || max || val || raw) {
315 RangedValueEvent event = new RangedValueEvent(this, min, max, val, raw);
316 fireValueChange(event);
317 }
318 }
319
320 protected void firePolicyAdded()
321 {
322 fireValueChange(new RangedValueEvent(this, true));
323 }
324
325 protected void firePolicyRemoved()
326 {
327 fireValueChange(new RangedValueEvent(this, false));
328 }
329
330 /**
331 * Adds new <code>RangedValuePolicy</code>. Polices will be checked in the
332 * same order they are added.
333 *
334 * @see com.cosylab.gui.components.range2.RangedValueController#addChangePolicy(RangedValuePolicy)
335 */
336 public synchronized void setPolicy(RangedValuePolicy policy)
337 {
338 this.policy= policy;
339
340 firePolicyAdded();
341 }
342
343 /**
344 * Adds a RangedValueListener to the the controller. Listener is notified
345 * of changes in the ranged value.
346 * @param listener
347 */
348 public void addRangedValueListener(RangedValueListener listener)
349 {
350 if (listener == null) {
351 return;
352 }
353
354 if (listeners.contains(listener)) {
355 return;
356 }
357
358 synchronized (listeners) {
359 listeners.add(listener);
360 }
361 }
362
363 /**
364 * Removes RangedValueListener from this controller.
365 *
366 * @param listener
367 */
368 public void removeRangedValueListener(RangedValueListener listener)
369 {
370 if (listener == null) {
371 return;
372 }
373
374 synchronized (listeners) {
375 listeners.remove(listener);
376 }
377 }
378
379 /**
380 * Returns the relative value between min and max.
381 *
382 * @return
383 */
384 public synchronized double getRelativeValue()
385 {
386 if (scalingFactor==1.0) {
387 return getRange().toRelative(getValue(),getRangedValue());
388 }
389 return getRange().toRelative(getValue(),getRangedValue())*scalingFactor;
390 }
391
392 /**
393 * Sets relative value according to min and max.
394 *
395 * @param value new value
396 */
397 public void setRelativeValue(double value)
398 {
399 if (scalingFactor==1.0) {
400 setValue(getRange().toAbsolute(value,getRangedValue()));
401 } else {
402 setValue(getRange().toAbsolute(value/scalingFactor,getRangedValue()));
403 }
404 }
405
406 /**
407 * Returns the actual value.
408 *
409 * @return
410 */
411 public double getRawValue() {
412 return rawValue;
413 }
414
415 /**
416 * Validates the value according to the bounds and policies.
417 *
418 * @param value value to be validated
419 *
420 * @return validated value
421 */
422 public double validate(double value) {
423 double v= getRange().validate(value);
424 RangedValue val= new RangedValue(getMinimum(),getMaximum(),v);
425 val = validatePolicies(val);
426 return val.getValue();
427 }
428
429
430 /**
431 * Returns the scalingFactor.
432 * @return Returns the scalingFactor.
433 */
434 public double getScalingFactor() {
435 return scalingFactor;
436 }
437
438
439 /**
440 * Sets new scalingFactor. Scaling factor scales relative value from 0 to 1.0 or whatever scaling factor is.
441 * @param scalingFactor The scalingFactor to set.
442 */
443 public void setScalingFactor(double scalingFactor) {
444 this.scalingFactor = scalingFactor;
445 }
446
447 /**
448 * Returns the currently employed policy.
449 *
450 * @return
451 */
452 public RangedValuePolicy getPolicy() {
453 return policy;
454 }
455
456 /**
457 * Adds a policy to this controller.
458 *
459 * @param p
460 */
461 public synchronized void addPolicy(RangedValuePolicy p) {
462 if (p==null) return;
463
464 if (policy==null) {
465 policy=p;
466 } else {
467 policy.addPeerPolicy(p);
468 }
469
470 setRangedValue(new RangedValue(getMinimum(),getMaximum(),getRawValue()));
471
472 firePolicyAdded();
473 }
474
475 /**
476 * Removes the policy from the controller.
477 *
478 * @param p
479 */
480 public void removePolicy(RangedValuePolicy p) {
481 if (p==null) {
482 return;
483 }
484 if (policy!=null) {
485 RangedValuePolicy pp= policy;
486 policy= policy.removePeerPolicy(p);
487 if (pp!=policy) {
488 setRangedValue(new RangedValue(getMinimum(),getMaximum(),getRawValue()));
489 firePolicyAdded();
490 }
491 }
492 }
493
494 /**
495 * Removes all policies of the given type.
496 *
497 * @param c
498 */
499 public void removePolicyByType(Class<? extends RangedValuePolicy> c) {
500 if (c==null) {
501 return;
502 }
503 if (policy!=null) {
504 RangedValuePolicy pp= policy;
505 policy= policy.removePeerPolicyByType(c);
506 if (pp!=policy) {
507 setRangedValue(new RangedValue(getMinimum(),getMaximum(),getRawValue()));
508 firePolicyAdded();
509 }
510 }
511 }
512
513 /**
514 * Converts the given value to the relative value according to the bounds.
515 *
516 * @param d value to be converted
517 *
518 * @return converted value
519 */
520 public double toRelative(double d) {
521 if (scalingFactor==1.0) {
522 return getRange().toRelative(d,getRangedValue());
523 }
524 return getRange().toRelative(d, getRangedValue())*scalingFactor;
525 }
526
527 /**
528 * Calculates ticks using the given measurer and width.
529 *
530 * @param w
531 * @param measurer
532 * @return
533 */
534 public Tick[] calculateTicks(int w, TickParameters measurer) {
535 if (tickCalculator != null) {
536 lastTicksUsed = tickCalculator.calculateTicks(w, measurer, getRange(), getRangedValue());
537 } else {
538 lastTicksUsed = getRange().getDefaultTickCalculator().calculateTicks(w, measurer, getRange(), getRangedValue());
539 }
540 return lastTicksUsed;
541 }
542
543 /**
544 * Converts proportional value to the absolute.
545 *
546 * @param proportional value to be converted
547 *
548 * @return converted value
549 */
550 public double toAbsolute(double proportional) {
551 if (scalingFactor==1.0) {
552 return getRange().toAbsolute(proportional,getRangedValue());
553 }
554 return getRange().toAbsolute(proportional/scalingFactor, getRangedValue());
555 }
556
557 /**
558 * Calculates ticks using default the given width and default measurer.
559 *
560 * @param width
561 * @return
562 */
563 public Tick[] calculateTicks(int width) {
564 if (tickCalculator != null) {
565 lastTicksUsed = tickCalculator.calculateTicks(width, getRange(), getRangedValue());
566 } else {
567 lastTicksUsed = getRange().getDefaultTickCalculator().calculateTicks(width, getRange(), getRangedValue());
568 }
569 return lastTicksUsed;
570 }
571
572 /**
573 * Sets the TickCalculator which overrides any default settings for this
574 * controller. If this provider is non-null the controller should always
575 * take this controller to calculate ticks.
576 *
577 * @param provider
578 */
579 public void setTickCalculator(TickCalculator calculator) {
580 this.tickCalculator = calculator;
581 }
582
583 /**
584 * Returns the ManualTickProvider which is used to calculated ticks for this
585 * controller. If the provider is null the default settings are used, which
586 * means that ticks calculation is delegated further to the Range.
587 *
588 * @return
589 */
590 public TickCalculator getTickCalculator() {
591 return this.tickCalculator;
592 }
593
594 /**
595 * Sets the snap to ticks property. If true the slider thumb can be moved on discrete
596 * values defined by the slider ticks.
597 *
598 * @param snapToTicks true if thumb should snap to ticks
599 */
600 public void setSnapToTicks(boolean snapToTicks) {
601 if (this.snapToTicks == snapToTicks) return;
602 this.snapToTicks = snapToTicks;
603 setValue(getValue());
604 }
605
606 /**
607 * Returns true if snap to ticks is turned on.
608 *
609 * @see #setSnapToTicks(boolean)
610 * @return true if snap is turned on
611 */
612 public boolean isSnapToTicks() {
613 return snapToTicks;
614 }
615
616 }
617
618 /* __oOo__ */