001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui; 003 004import java.beans.PropertyChangeEvent; 005import java.beans.PropertyChangeListener; 006import java.util.Optional; 007 008import javax.swing.Action; 009import javax.swing.Icon; 010import javax.swing.JToggleButton; 011 012import org.openstreetmap.josm.actions.ExpertToggleAction; 013import org.openstreetmap.josm.actions.ExpertToggleAction.ExpertModeChangeListener; 014import org.openstreetmap.josm.spi.preferences.Config; 015import org.openstreetmap.josm.tools.CheckParameterUtil; 016import org.openstreetmap.josm.tools.Destroyable; 017 018/** 019 * Just a toggle button, with smaller border and icon only to display in 020 * MapFrame toolbars. 021 * Also provides methods for storing hidden state in preferences 022 * @author imi, akks 023 */ 024public class IconToggleButton extends JToggleButton implements HideableButton, PropertyChangeListener, Destroyable, ExpertModeChangeListener { 025 026 private transient ShowHideButtonListener listener; 027 private boolean hideIfDisabled; 028 private final boolean isExpert; 029 030 /** 031 * Construct the toggle button with the given action. 032 * @param action associated action 033 */ 034 public IconToggleButton(Action action) { 035 this(action, false); 036 } 037 038 /** 039 * Construct the toggle button with the given action. 040 * @param action associated action 041 * @param isExpert {@code true} if it's reserved to expert mode 042 */ 043 public IconToggleButton(Action action, boolean isExpert) { 044 super(action); 045 CheckParameterUtil.ensureParameterNotNull(action, "action"); 046 this.isExpert = isExpert; 047 setText(null); 048 049 Object o = action.getValue(Action.SHORT_DESCRIPTION); 050 if (o != null) { 051 setToolTipText(o.toString()); 052 } 053 054 action.addPropertyChangeListener(this); 055 056 ExpertToggleAction.addExpertModeChangeListener(this); 057 } 058 059 @Override 060 public void propertyChange(PropertyChangeEvent evt) { 061 if ("active".equals(evt.getPropertyName())) { 062 setSelected((Boolean) evt.getNewValue()); 063 requestFocusInWindow(); 064 } else if ("selected".equals(evt.getPropertyName())) { 065 setSelected((Boolean) evt.getNewValue()); 066 } 067 } 068 069 @Override 070 public void destroy() { 071 Action action = getAction(); 072 if (action instanceof Destroyable) { 073 ((Destroyable) action).destroy(); 074 } 075 if (action != null) { 076 action.removePropertyChangeListener(this); 077 } 078 } 079 080 String getPreferenceKey() { 081 String s = (String) getSafeActionValue("toolbar"); 082 if (s == null && getAction() != null) { 083 s = getAction().getClass().getName(); 084 } 085 return "sidetoolbar.hidden."+s; 086 } 087 088 @Override 089 public void expertChanged(boolean isExpert) { 090 applyButtonHiddenPreferences(); 091 } 092 093 @Override 094 public void applyButtonHiddenPreferences() { 095 boolean alwaysHideDisabled = Config.getPref().getBoolean("sidetoolbar.hideDisabledButtons", false); 096 if (!isEnabled() && (hideIfDisabled || alwaysHideDisabled)) { 097 setVisible(false); // hide because of disabled button 098 } else { 099 boolean hiddenFlag = false; 100 String hiddenFlagStr = Config.getPref().get(getPreferenceKey(), null); 101 if (hiddenFlagStr == null) { 102 if (isExpert && !ExpertToggleAction.isExpert()) { 103 hiddenFlag = true; 104 } 105 } else { 106 hiddenFlag = Boolean.parseBoolean(hiddenFlagStr); 107 } 108 setVisible(!hiddenFlag); // show or hide, do what preferences say 109 } 110 } 111 112 @Override 113 public void setButtonHidden(boolean b) { 114 setVisible(!b); 115 if (listener != null) { // if someone wants to know about changes of visibility 116 if (!b) listener.buttonShown(); else listener.buttonHidden(); 117 } 118 if ((b && isExpert && !ExpertToggleAction.isExpert()) || 119 (!b && isExpert && ExpertToggleAction.isExpert())) { 120 Config.getPref().put(getPreferenceKey(), null); 121 } else { 122 Config.getPref().putBoolean(getPreferenceKey(), b); 123 } 124 } 125 126 /** 127 * This function should be called for plugins that want to enable auto-hiding 128 * custom buttons when they are disabled (because of incorrect layer, for example) 129 * @param b hide if disabled 130 */ 131 public void setAutoHideDisabledButton(boolean b) { 132 hideIfDisabled = b; 133 if (b && !isEnabled()) { 134 setVisible(false); 135 } 136 } 137 138 @Override 139 public void showButton() { 140 setButtonHidden(false); 141 } 142 143 @Override 144 public void hideButton() { 145 setButtonHidden(true); 146 } 147 148 @Override 149 public String getActionName() { 150 return (String) getSafeActionValue(Action.NAME); 151 } 152 153 @Override 154 public Icon getIcon() { 155 return (Icon) Optional.ofNullable(getSafeActionValue(Action.LARGE_ICON_KEY)).orElseGet(() -> getSafeActionValue(Action.SMALL_ICON)); 156 } 157 158 @Override 159 public boolean isButtonVisible() { 160 return isVisible(); 161 } 162 163 @Override 164 public boolean isExpert() { 165 return isExpert; 166 } 167 168 @Override 169 public void setShowHideButtonListener(ShowHideButtonListener l) { 170 listener = l; 171 } 172 173 protected final Object getSafeActionValue(String key) { 174 // Mac OS X Aqua L&F can call accessors from constructor, so getAction() can be null in those cases 175 return getAction() != null ? getAction().getValue(key) : null; 176 } 177}