001 // License: GPL. Copyright 2008 by Frederik Ramm and others 002 package org.openstreetmap.josm.gui; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 006 import java.awt.event.ActionEvent; 007 import java.awt.event.ActionListener; 008 import java.awt.event.ItemListener; 009 import java.awt.event.MouseAdapter; 010 import java.awt.event.MouseEvent; 011 import java.awt.event.MouseListener; 012 013 import javax.swing.AbstractAction; 014 import javax.swing.ActionMap; 015 import javax.swing.ButtonGroup; 016 import javax.swing.ButtonModel; 017 import javax.swing.Icon; 018 import javax.swing.JCheckBox; 019 import javax.swing.SwingUtilities; 020 import javax.swing.event.ChangeListener; 021 import javax.swing.plaf.ActionMapUIResource; 022 023 public class QuadStateCheckBox extends JCheckBox { 024 025 public enum State { NOT_SELECTED, SELECTED, UNSET, PARTIAL } 026 027 private final QuadStateDecorator model; 028 private State[] allowed; 029 030 public QuadStateCheckBox(String text, Icon icon, State initial, State[] allowed) { 031 super(text, icon); 032 this.allowed = allowed; 033 // Add a listener for when the mouse is pressed 034 super.addMouseListener(new MouseAdapter() { 035 @Override public void mousePressed(MouseEvent e) { 036 grabFocus(); 037 model.nextState(); 038 } 039 }); 040 // Reset the keyboard action map 041 ActionMap map = new ActionMapUIResource(); 042 map.put("pressed", new AbstractAction() { 043 public void actionPerformed(ActionEvent e) { 044 grabFocus(); 045 model.nextState(); 046 } 047 }); 048 map.put("released", null); 049 SwingUtilities.replaceUIActionMap(this, map); 050 // set the model to the adapted model 051 model = new QuadStateDecorator(getModel()); 052 setModel(model); 053 setState(initial); 054 } 055 public QuadStateCheckBox(String text, State initial, State[] allowed) { 056 this(text, null, initial, allowed); 057 } 058 059 /** Do not let anyone add mouse listeners */ 060 @Override public void addMouseListener(MouseListener l) { } 061 /** 062 * Set the new state. 063 */ 064 public void setState(State state) { model.setState(state); } 065 /** Return the current state, which is determined by the 066 * selection status of the model. */ 067 public State getState() { return model.getState(); } 068 @Override public void setSelected(boolean b) { 069 if (b) { 070 setState(State.SELECTED); 071 } else { 072 setState(State.NOT_SELECTED); 073 } 074 } 075 076 private class QuadStateDecorator implements ButtonModel { 077 private final ButtonModel other; 078 private QuadStateDecorator(ButtonModel other) { 079 this.other = other; 080 } 081 private void setState(State state) { 082 if (state == State.NOT_SELECTED) { 083 other.setArmed(false); 084 other.setPressed(false); 085 other.setSelected(false); 086 setToolTipText(tr("false: the property is explicitly switched off")); 087 } else if (state == State.SELECTED) { 088 other.setArmed(false); 089 other.setPressed(false); 090 other.setSelected(true); 091 setToolTipText(tr("true: the property is explicitly switched on")); 092 } else if (state == State.PARTIAL) { 093 other.setArmed(true); 094 other.setPressed(true); 095 other.setSelected(true); 096 setToolTipText(tr("partial: different selected objects have different values, do not change")); 097 } else { 098 other.setArmed(true); 099 other.setPressed(true); 100 other.setSelected(false); 101 setToolTipText(tr("unset: do not set this property on the selected objects")); 102 } 103 } 104 /** 105 * The current state is embedded in the selection / armed 106 * state of the model. 107 * 108 * We return the SELECTED state when the checkbox is selected 109 * but not armed, PARTIAL state when the checkbox is 110 * selected and armed (grey) and NOT_SELECTED when the 111 * checkbox is deselected. 112 */ 113 private State getState() { 114 if (isSelected() && !isArmed()) { 115 // normal black tick 116 return State.SELECTED; 117 } else if (isSelected() && isArmed()) { 118 // don't care grey tick 119 return State.PARTIAL; 120 } else if (!isSelected() && !isArmed()) { 121 return State.NOT_SELECTED; 122 } else { 123 return State.UNSET; 124 } 125 } 126 /** Rotate to the next allowed state.*/ 127 private void nextState() { 128 State current = getState(); 129 for (int i = 0; i < allowed.length; i++) { 130 if (allowed[i] == current) { 131 setState((i == allowed.length-1) ? allowed[0] : allowed[i+1]); 132 break; 133 } 134 } 135 } 136 /** Filter: No one may change the armed/selected/pressed status except us. */ 137 public void setArmed(boolean b) { } 138 public void setSelected(boolean b) { } 139 public void setPressed(boolean b) { } 140 /** We disable focusing on the component when it is not 141 * enabled. */ 142 public void setEnabled(boolean b) { 143 setFocusable(b); 144 other.setEnabled(b); 145 } 146 /** All these methods simply delegate to the "other" model 147 * that is being decorated. */ 148 public boolean isArmed() { return other.isArmed(); } 149 public boolean isSelected() { return other.isSelected(); } 150 public boolean isEnabled() { return other.isEnabled(); } 151 public boolean isPressed() { return other.isPressed(); } 152 public boolean isRollover() { return other.isRollover(); } 153 public void setRollover(boolean b) { other.setRollover(b); } 154 public void setMnemonic(int key) { other.setMnemonic(key); } 155 public int getMnemonic() { return other.getMnemonic(); } 156 public void setActionCommand(String s) { 157 other.setActionCommand(s); 158 } 159 public String getActionCommand() { 160 return other.getActionCommand(); 161 } 162 public void setGroup(ButtonGroup group) { 163 other.setGroup(group); 164 } 165 public void addActionListener(ActionListener l) { 166 other.addActionListener(l); 167 } 168 public void removeActionListener(ActionListener l) { 169 other.removeActionListener(l); 170 } 171 public void addItemListener(ItemListener l) { 172 other.addItemListener(l); 173 } 174 public void removeItemListener(ItemListener l) { 175 other.removeItemListener(l); 176 } 177 public void addChangeListener(ChangeListener l) { 178 other.addChangeListener(l); 179 } 180 public void removeChangeListener(ChangeListener l) { 181 other.removeChangeListener(l); 182 } 183 public Object[] getSelectedObjects() { 184 return other.getSelectedObjects(); 185 } 186 } 187 }