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