View Javadoc

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__ */