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    }