001: /*
002: * Copyright (c) 2002-2007 JGoodies Karsten Lentzsch. All Rights Reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * o Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * o Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * o Neither the name of JGoodies Karsten Lentzsch nor the names of
015: * its contributors may be used to endorse or promote products derived
016: * from this software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package com.jgoodies.binding.adapter;
032:
033: import java.beans.PropertyChangeEvent;
034: import java.beans.PropertyChangeListener;
035: import java.io.Serializable;
036:
037: import javax.swing.BoundedRangeModel;
038: import javax.swing.event.ChangeEvent;
039: import javax.swing.event.ChangeListener;
040: import javax.swing.event.EventListenerList;
041:
042: import com.jgoodies.binding.value.ValueModel;
043:
044: /**
045: * Converts a ValueModel to the <code>BoundedRangeModel</code> interface.
046: * Honors an upper and lower bound and returns the adapter's minimum
047: * in case the subject value is <code>null</code>.<p>
048: *
049: * <strong>Example:</strong><pre>
050: * int minSaturation = 0;
051: * int maxSaturation = 255;
052: * PresentationModel pm = new PresentationModel(settings);
053: * ValueModel saturationModel = pm.getModel("saturation");
054: * JSlider saturationSlider = new JSlider(
055: * new BoundedRangeAdapter(saturationModel,
056: * 0,
057: * minSaturation,
058: * maxSaturation));
059: * </pre>
060: *
061: * @author Karsten Lentzsch
062: * @version $Revision: 1.6 $
063: *
064: * @see javax.swing.JSlider
065: */
066: public final class BoundedRangeAdapter implements BoundedRangeModel,
067: Serializable {
068:
069: /**
070: * Only one ChangeEvent is needed per model instance since the
071: * event's only (read-only) state is the source property. The source
072: * of events generated here is always "this".
073: */
074: private transient ChangeEvent changeEvent = null;
075:
076: /**
077: * The listeners observing model changes.
078: */
079: private final EventListenerList listenerList = new EventListenerList();
080:
081: private final ValueModel subject;
082:
083: private int theExtent = 0;
084: private int min = 0;
085: private int max = 100;
086: private boolean isAdjusting = false;
087:
088: // Instance Creation ***************************************************
089:
090: /**
091: * Constructs a BoundedRangeAdapter on the given subject
092: * using the specified extend, minimum and maximum values.
093:
094: * @param subject the underlying ValueModel that provides the value
095: * @param extent the extent to be set
096: * @param min the minimum to be set
097: * @param max the maximum to be set
098: * @throws IllegalArgumentException if the following constraints aren't
099: * satisfied: <code>min <= initial subject value <= value+extent <= max</code>
100: */
101: public BoundedRangeAdapter(ValueModel subject, int extent, int min,
102: int max) {
103: this .subject = subject;
104: Object subjectValue = subject.getValue();
105: int initialValue = subjectValue == null ? min
106: : ((Integer) subjectValue).intValue();
107: initialize(initialValue, extent, min, max);
108: subject.addValueChangeListener(new SubjectValueChangeHandler());
109: }
110:
111: // ************************************************************************
112:
113: /**
114: * Returns this model's extent.
115: *
116: * @return the model's extent
117: * @see #setExtent(int)
118: * @see BoundedRangeModel#getExtent()
119: */
120: public int getExtent() {
121: return theExtent;
122: }
123:
124: /**
125: * Returns this model's upper bound, the maximum.
126: *
127: * @return the model's maximum
128: * @see #setMaximum(int)
129: * @see BoundedRangeModel#getMaximum()
130: */
131: public int getMaximum() {
132: return max;
133: }
134:
135: /**
136: * Returns this model's lower bound, the minimum.
137: *
138: * @return the model's minimum
139: * @see #setMinimum(int)
140: * @see BoundedRangeModel#getMinimum()
141: */
142: public int getMinimum() {
143: return min;
144: }
145:
146: /**
147: * Returns the current subject value, or the minimum if
148: * the subject value is <code>null</code>.
149: *
150: * @return the model's current value
151: * @see #setValue(int)
152: * @see BoundedRangeModel#getValue()
153: */
154: public int getValue() {
155: Object subjectValue = subject.getValue();
156: return subjectValue == null ? getMinimum()
157: : ((Integer) subjectValue).intValue();
158: }
159:
160: /**
161: * Returns true if the value is in the process of changing
162: * as a result of actions being taken by the user.
163: *
164: * @return the value of the valueIsAdjusting property
165: * @see #setValue(int)
166: * @see BoundedRangeModel#getValueIsAdjusting()
167: */
168: public boolean getValueIsAdjusting() {
169: return isAdjusting;
170: }
171:
172: /**
173: * Sets the extent to <I>n</I>. Ensures that <I>n</I>
174: * is greater than or equal to zero and falls within the adapter's
175: * constraints:
176: * <pre>
177: * minimum <= value <= value+extent <= maximum
178: * </pre>
179: *
180: * @param n the new extent before ensuring a non-negative number
181: * @see BoundedRangeModel#setExtent(int)
182: */
183: public void setExtent(int n) {
184: int newExtent = Math.max(0, n);
185: int value = getValue();
186: if (value + newExtent > max) {
187: newExtent = max - value;
188: }
189: setRangeProperties(value, newExtent, min, max, isAdjusting);
190: }
191:
192: /**
193: * Sets the maximum to <I>n</I>. Ensures that <I>n</I>
194: * and the other three properties obey this adapter's constraints:
195: * <pre>
196: * minimum <= value <= value+extent <= maximum
197: * </pre>
198: *
199: * @param n the new maximum before ensuring this adapter's constraints
200: * @see BoundedRangeModel#setMaximum(int)
201: */
202: public void setMaximum(int n) {
203: int newMin = Math.min(n, min);
204: int newValue = Math.min(n, getValue());
205: int newExtent = Math.min(n - newValue, theExtent);
206: setRangeProperties(newValue, newExtent, newMin, n, isAdjusting);
207: }
208:
209: /**
210: * Sets the minimum to <I>n</I>. Ensures that <I>n</I>
211: * and the other three properties obey this adapter's constraints:
212: * <pre>
213: * minimum <= value <= value+extent <= maximum
214: * </pre>
215: *
216: * @param n the new minimum before ensuring constraints
217: * @see #getMinimum()
218: * @see BoundedRangeModel#setMinimum(int)
219: */
220: public void setMinimum(int n) {
221: int newMax = Math.max(n, max);
222: int newValue = Math.max(n, getValue());
223: int newExtent = Math.min(newMax - newValue, theExtent);
224: setRangeProperties(newValue, newExtent, n, newMax, isAdjusting);
225: }
226:
227: /**
228: * Sets all of the BoundedRangeModel properties after forcing
229: * the arguments to obey the usual constraints:
230: * <pre>
231: * minimum <= value <= value+extent <= maximum
232: * </pre><p>
233: *
234: * At most, one ChangeEvent is generated.
235: *
236: * @param newValue the value to be set
237: * @param newExtent the extent to be set
238: * @param newMin the minimum to be set
239: * @param newMax the maximum to be set
240: * @param adjusting true if there are other pending changes
241: * @see BoundedRangeModel#setRangeProperties(int, int, int, int, boolean)
242: * @see #setValue(int)
243: * @see #setExtent(int)
244: * @see #setMinimum(int)
245: * @see #setMaximum(int)
246: * @see #setValueIsAdjusting(boolean)
247: */
248: public void setRangeProperties(int newValue, int newExtent,
249: int newMin, int newMax, boolean adjusting) {
250: if (newMin > newMax) {
251: newMin = newMax;
252: }
253: if (newValue > newMax) {
254: newMax = newValue;
255: }
256: if (newValue < newMin) {
257: newMin = newValue;
258: }
259:
260: /* Convert the addends to long so that extent can be
261: * Integer.MAX_VALUE without rolling over the sum.
262: * A JCK test covers this, see bug 4097718.
263: */
264: if (((long) newExtent + (long) newValue) > newMax) {
265: newExtent = newMax - newValue;
266: }
267: if (newExtent < 0) {
268: newExtent = 0;
269: }
270: boolean isChange = (newValue != getValue())
271: || (newExtent != theExtent) || (newMin != min)
272: || (newMax != max) || (adjusting != isAdjusting);
273: if (isChange) {
274: setValue0(newValue);
275: theExtent = newExtent;
276: min = newMin;
277: max = newMax;
278: isAdjusting = adjusting;
279: fireStateChanged();
280: }
281: }
282:
283: /**
284: * Sets the current value of the model. For a slider, that
285: * determines where the knob appears. Ensures that the new
286: * value, <I>n</I> falls within the model's constraints:
287: * <pre>
288: * minimum <= value <= value+extent <= maximum
289: * </pre>
290: *
291: * @param n the new value before ensuring constraints
292: * @see BoundedRangeModel#setValue(int)
293: */
294: public void setValue(int n) {
295: int newValue = Math.max(n, min);
296: if (newValue + theExtent > max) {
297: newValue = max - theExtent;
298: }
299: setRangeProperties(newValue, theExtent, min, max, isAdjusting);
300: }
301:
302: /**
303: * Sets the valueIsAdjusting property.
304: *
305: * @param b the new value
306: * @see #getValueIsAdjusting()
307: * @see #setValue(int)
308: * @see BoundedRangeModel#setValueIsAdjusting(boolean)
309: */
310: public void setValueIsAdjusting(boolean b) {
311: setRangeProperties(getValue(), theExtent, min, max, b);
312: }
313:
314: // Listeners and Events ***************************************************
315:
316: /**
317: * Adds a ChangeListener. The change listeners are run each
318: * time any one of the Bounded Range model properties changes.
319: *
320: * @param l the ChangeListener to add
321: * @see #removeChangeListener(ChangeListener)
322: * @see BoundedRangeModel#addChangeListener(ChangeListener)
323: */
324: public void addChangeListener(ChangeListener l) {
325: listenerList.add(ChangeListener.class, l);
326: }
327:
328: /**
329: * Removes a ChangeListener.
330: *
331: * @param l the ChangeListener to remove
332: * @see #addChangeListener(ChangeListener)
333: * @see BoundedRangeModel#removeChangeListener(ChangeListener)
334: */
335: public void removeChangeListener(ChangeListener l) {
336: listenerList.remove(ChangeListener.class, l);
337: }
338:
339: /**
340: * Runs each ChangeListeners stateChanged() method.
341: *
342: * @see #setRangeProperties(int, int, int, int, boolean)
343: * @see EventListenerList
344: */
345: protected void fireStateChanged() {
346: Object[] listeners = listenerList.getListenerList();
347: for (int i = listeners.length - 2; i >= 0; i -= 2) {
348: if (listeners[i] == ChangeListener.class) {
349: if (changeEvent == null) {
350: changeEvent = new ChangeEvent(this );
351: }
352: ((ChangeListener) listeners[i + 1])
353: .stateChanged(changeEvent);
354: }
355: }
356: }
357:
358: // Misc *******************************************************************
359:
360: /**
361: * Initializes value, extent, minimum and maximum. Adjusting is false.
362: *
363: * @param initialValue the initialValue
364: * @param extent the extent to be set
365: * @param minimum the minimum to be set
366: * @param maximum the maximum to be set
367: *
368: * @throws IllegalArgumentException if the following constraints
369: * aren't satisfied: <code>min <= value <= value+extent <= max</code>
370: */
371: private void initialize(int initialValue, int extent, int minimum,
372: int maximum) {
373: if ((maximum >= minimum) && (initialValue >= minimum)
374: && ((initialValue + extent) >= initialValue)
375: && ((initialValue + extent) <= maximum)) {
376: this .theExtent = extent;
377: this .min = minimum;
378: this .max = maximum;
379: } else {
380: throw new IllegalArgumentException(
381: "invalid range properties");
382: }
383: }
384:
385: private void setValue0(int newValue) {
386: subject.setValue(Integer.valueOf(newValue));
387: }
388:
389: /**
390: * Returns a string that displays all of the BoundedRangeModel properties.
391: *
392: * @return a string representation of the properties
393: */
394: @Override
395: public String toString() {
396: String modelString = "value=" + getValue() + ", " + "extent="
397: + getExtent() + ", " + "min=" + getMinimum() + ", "
398: + "max=" + getMaximum() + ", " + "adj="
399: + getValueIsAdjusting();
400:
401: return getClass().getName() + "[" + modelString + "]";
402: }
403:
404: /**
405: * Handles changes in the subject's value.
406: */
407: private final class SubjectValueChangeHandler implements
408: PropertyChangeListener {
409:
410: /**
411: * The subect's value has changed. Fires a state change so
412: * all registered listeners will be notified about the change.
413: *
414: * @param evt the property change event fired by the subject
415: */
416: public void propertyChange(PropertyChangeEvent evt) {
417: fireStateChanged();
418: }
419:
420: }
421:
422: }
|