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