001 // License: GPL. For details, see LICENSE file.
002 package org.openstreetmap.josm.gui.history;
003
004 import static org.openstreetmap.josm.tools.I18n.tr;
005
006 import java.awt.Adjustable;
007 import java.awt.event.AdjustmentEvent;
008 import java.awt.event.AdjustmentListener;
009 import java.awt.event.ItemEvent;
010 import java.awt.event.ItemListener;
011 import java.util.ArrayList;
012 import java.util.HashMap;
013 import java.util.Observable;
014 import java.util.Observer;
015
016 import javax.swing.JCheckBox;
017
018 import org.openstreetmap.josm.tools.CheckParameterUtil;
019
020 /**
021 * Synchronizes scrollbar adjustments between a set of
022 * {@link Adjustable}s. Whenever the adjustment of one of
023 * the registerd Adjustables is updated the adjustment of
024 * the other registered Adjustables is adjusted too.
025 *
026 */
027 public class AdjustmentSynchronizer implements AdjustmentListener {
028
029 private final ArrayList<Adjustable> synchronizedAdjustables;
030 private final HashMap<Adjustable, Boolean> enabledMap;
031
032 private final Observable observable;
033
034 public AdjustmentSynchronizer() {
035 synchronizedAdjustables = new ArrayList<Adjustable>();
036 enabledMap = new HashMap<Adjustable, Boolean>();
037 observable = new Observable();
038 }
039
040 /**
041 * registers an {@link Adjustable} for participation in synchronized
042 * scrolling.
043 *
044 * @param adjustable the adjustable
045 */
046 public void participateInSynchronizedScrolling(Adjustable adjustable) {
047 if (adjustable == null)
048 return;
049 if (synchronizedAdjustables.contains(adjustable))
050 return;
051 synchronizedAdjustables.add(adjustable);
052 setParticipatingInSynchronizedScrolling(adjustable, true);
053 adjustable.addAdjustmentListener(this);
054 }
055
056 /**
057 * event handler for {@link AdjustmentEvent}s
058 *
059 */
060 public void adjustmentValueChanged(AdjustmentEvent e) {
061 if (! enabledMap.get(e.getAdjustable()))
062 return;
063 for (Adjustable a : synchronizedAdjustables) {
064 if (a != e.getAdjustable() && isParticipatingInSynchronizedScrolling(a)) {
065 a.setValue(e.getValue());
066 }
067 }
068 }
069
070 /**
071 * sets whether adjustable participates in adjustment synchronization
072 * or not
073 *
074 * @param adjustable the adjustable
075 */
076 protected void setParticipatingInSynchronizedScrolling(Adjustable adjustable, boolean isParticipating) {
077 CheckParameterUtil.ensureParameterNotNull(adjustable, "adjustable");
078 if (! synchronizedAdjustables.contains(adjustable))
079 throw new IllegalStateException(tr("Adjustable {0} not registered yet. Cannot set participation in synchronized adjustment.", adjustable));
080
081 enabledMap.put(adjustable, isParticipating);
082 observable.notifyObservers();
083 }
084
085 /**
086 * returns true if an adjustable is participating in synchronized scrolling
087 *
088 * @param adjustable the adjustable
089 * @return true, if the adjustable is participating in synchronized scrolling, false otherwise
090 * @throws IllegalStateException thrown, if adjustable is not registered for synchronized scrolling
091 */
092 protected boolean isParticipatingInSynchronizedScrolling(Adjustable adjustable) throws IllegalStateException {
093 if (! synchronizedAdjustables.contains(adjustable))
094 throw new IllegalStateException(tr("Adjustable {0} not registered yet.", adjustable));
095
096 return enabledMap.get(adjustable);
097 }
098
099 /**
100 * wires a {@link JCheckBox} to the adjustment synchronizer, in such a way that:
101 * <li>
102 * <ol>state changes in the checkbox control whether the adjustable participates
103 * in synchronized adjustment</ol>
104 * <ol>state changes in this {@link AdjustmentSynchronizer} are reflected in the
105 * {@link JCheckBox}</ol>
106 * </li>
107 *
108 *
109 * @param view the checkbox to control whether an adjustable participates in synchronized
110 * adjustment
111 * @param adjustable the adjustable
112 * @exception IllegalArgumentException thrown, if view is null
113 * @exception IllegalArgumentException thrown, if adjustable is null
114 */
115 protected void adapt(final JCheckBox view, final Adjustable adjustable) throws IllegalStateException {
116 CheckParameterUtil.ensureParameterNotNull(adjustable, "adjustable");
117 CheckParameterUtil.ensureParameterNotNull(view, "view");
118
119 if (! synchronizedAdjustables.contains(adjustable)) {
120 participateInSynchronizedScrolling(adjustable);
121 }
122
123 // register an item lister with the check box
124 //
125 view.addItemListener(new ItemListener() {
126 public void itemStateChanged(ItemEvent e) {
127 switch(e.getStateChange()) {
128 case ItemEvent.SELECTED:
129 if (!isParticipatingInSynchronizedScrolling(adjustable)) {
130 setParticipatingInSynchronizedScrolling(adjustable, true);
131 }
132 break;
133 case ItemEvent.DESELECTED:
134 if (isParticipatingInSynchronizedScrolling(adjustable)) {
135 setParticipatingInSynchronizedScrolling(adjustable, false);
136 }
137 break;
138 }
139 }
140 });
141
142 observable.addObserver(
143 new Observer() {
144 public void update(Observable o, Object arg) {
145 boolean sync = isParticipatingInSynchronizedScrolling(adjustable);
146 if (view.isSelected() != sync) {
147 view.setSelected(sync);
148 }
149 }
150 }
151 );
152 setParticipatingInSynchronizedScrolling(adjustable, true);
153 view.setSelected(true);
154 }
155 }