001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.actions; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.Component; 007import java.awt.event.ActionEvent; 008 009import org.openstreetmap.josm.data.preferences.BooleanProperty; 010import org.openstreetmap.josm.gui.MainApplication; 011import org.openstreetmap.josm.tools.ImageProvider; 012import org.openstreetmap.josm.tools.ListenerList; 013 014/** 015 * This action toggles the Expert mode. 016 * @since 4840 017 */ 018public class ExpertToggleAction extends ToggleAction { 019 020 /** 021 * This listener is notified whenever the expert mode setting changed. 022 */ 023 @FunctionalInterface 024 public interface ExpertModeChangeListener { 025 /** 026 * The expert mode changed. 027 * @param isExpert <code>true</code> if expert mode was enabled, false otherwise. 028 */ 029 void expertChanged(boolean isExpert); 030 } 031 032 // TODO: Switch to checked list. We can do this as soon as we do not see any more warnings. 033 private static final ListenerList<ExpertModeChangeListener> listeners = ListenerList.createUnchecked(); 034 private static final ListenerList<Component> visibilityToggleListeners = ListenerList.createUnchecked(); 035 036 private static final BooleanProperty PREF_EXPERT = new BooleanProperty("expert", false); 037 038 private static final ExpertToggleAction INSTANCE = new ExpertToggleAction(); 039 040 private static synchronized void fireExpertModeChanged(boolean isExpert) { 041 listeners.fireEvent(listener -> listener.expertChanged(isExpert)); 042 visibilityToggleListeners.fireEvent(c -> c.setVisible(isExpert)); 043 } 044 045 /** 046 * Register a expert mode change listener. 047 * 048 * @param listener the listener. Ignored if null. 049 */ 050 public static void addExpertModeChangeListener(ExpertModeChangeListener listener) { 051 addExpertModeChangeListener(listener, false); 052 } 053 054 /** 055 * Register a expert mode change listener, and optionally fires it. 056 * @param listener the listener. Ignored if null. 057 * @param fireWhenAdding if true, the listener will be fired immediately after added 058 */ 059 public static synchronized void addExpertModeChangeListener(ExpertModeChangeListener listener, boolean fireWhenAdding) { 060 if (listener == null) return; 061 listeners.addWeakListener(listener); 062 if (fireWhenAdding) { 063 listener.expertChanged(isExpert()); 064 } 065 } 066 067 /** 068 * Removes a expert mode change listener 069 * 070 * @param listener the listener. Ignored if null. 071 */ 072 public static synchronized void removeExpertModeChangeListener(ExpertModeChangeListener listener) { 073 if (listener == null) return; 074 listeners.removeListener(listener); 075 } 076 077 /** 078 * Marks a component to be only visible when expert mode is enabled. The visibility of the component is changed automatically. 079 * @param c The component. 080 */ 081 public static synchronized void addVisibilitySwitcher(Component c) { 082 if (c == null) return; 083 visibilityToggleListeners.addWeakListener(c); 084 c.setVisible(isExpert()); 085 } 086 087 /** 088 * Stops tracking visibility changes for the given component. 089 * @param c The component. 090 * @see #addVisibilitySwitcher(Component) 091 */ 092 public static synchronized void removeVisibilitySwitcher(Component c) { 093 if (c == null) return; 094 visibilityToggleListeners.removeListener(c); 095 } 096 097 /** 098 * Determines if the given component tracks visibility changes. 099 * @param c The component. 100 * @return {@code true} if the given component tracks visibility changes 101 * @since 15649 102 */ 103 public static synchronized boolean hasVisibilitySwitcher(Component c) { 104 if (c == null) return false; 105 return visibilityToggleListeners.containsListener(c); 106 } 107 108 /** 109 * Constructs a new {@code ExpertToggleAction}. 110 */ 111 public ExpertToggleAction() { 112 super(tr("Expert Mode"), 113 new ImageProvider("expert").setOptional(true), 114 tr("Enable/disable expert mode"), 115 null, 116 false /* register toolbar */, null, false 117 ); 118 setToolbarId("expertmode"); 119 if (MainApplication.getToolbar() != null) { 120 MainApplication.getToolbar().register(this); 121 } 122 setSelected(PREF_EXPERT.get()); 123 notifySelectedState(); 124 } 125 126 @Override 127 protected final void notifySelectedState() { 128 super.notifySelectedState(); 129 PREF_EXPERT.put(isSelected()); 130 fireExpertModeChanged(isSelected()); 131 } 132 133 /** 134 * Forces the expert mode state to the given state. 135 * @param isExpert if expert mode should be used. 136 * @since 11224 137 */ 138 public void setExpert(boolean isExpert) { 139 if (isSelected() != isExpert) { 140 setSelected(isExpert); 141 notifySelectedState(); 142 } 143 } 144 145 @Override 146 public void actionPerformed(ActionEvent e) { 147 toggleSelectedState(e); 148 notifySelectedState(); 149 } 150 151 /** 152 * Replies the unique instance of this action. 153 * @return The unique instance of this action 154 */ 155 public static ExpertToggleAction getInstance() { 156 return INSTANCE; 157 } 158 159 /** 160 * Determines if expert mode is enabled. 161 * @return {@code true} if expert mode is enabled, {@code false} otherwise. 162 */ 163 public static boolean isExpert() { 164 return INSTANCE.isSelected(); 165 } 166}