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 }