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}