001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.preferences;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.BorderLayout;
007import java.awt.Component;
008import java.awt.Container;
009import java.awt.Dimension;
010import java.awt.FlowLayout;
011import java.awt.GridBagLayout;
012import java.awt.Insets;
013import java.awt.event.ActionEvent;
014import java.awt.event.WindowAdapter;
015import java.awt.event.WindowEvent;
016
017import javax.swing.AbstractAction;
018import javax.swing.BorderFactory;
019import javax.swing.JButton;
020import javax.swing.JCheckBox;
021import javax.swing.JDialog;
022import javax.swing.JPanel;
023
024import org.openstreetmap.josm.actions.ExpertToggleAction;
025import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
026import org.openstreetmap.josm.gui.help.HelpUtil;
027import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane.ValidationListener;
028import org.openstreetmap.josm.gui.util.GuiHelper;
029import org.openstreetmap.josm.gui.util.WindowGeometry;
030import org.openstreetmap.josm.tools.GBC;
031import org.openstreetmap.josm.tools.ImageProvider;
032import org.openstreetmap.josm.tools.InputMapUtils;
033import org.openstreetmap.josm.tools.Pair;
034
035/**
036 * The main preferences dialog.
037 *
038 * Dialog window where the user can change various settings. Organized in main
039 * tabs to the left ({@link TabPreferenceSetting}) and (optional) sub-pages
040 * ({@link SubPreferenceSetting}).
041 */
042public class PreferenceDialog extends JDialog {
043
044    private final PreferenceTabbedPane tpPreferences = new PreferenceTabbedPane();
045    private final ContextSensitiveHelpAction helpAction = new ContextSensitiveHelpAction();
046    private final WindowEventHandler windowEventHandler = new WindowEventHandler();
047    private boolean canceled;
048    private static Pair<Class<? extends TabPreferenceSetting>, Class<? extends SubPreferenceSetting>> previouslySelected;
049
050    /**
051     * Constructs a new {@code PreferenceDialog}.
052     * @param parent parent component
053     */
054    public PreferenceDialog(Component parent) {
055        super(GuiHelper.getFrameForComponent(parent), tr("Preferences"), ModalityType.DOCUMENT_MODAL);
056        build();
057        this.setMinimumSize(new Dimension(800, 600));
058        // set the maximum width to the current screen. If the dialog is opened on a
059        // smaller screen than before, this will reset the stored preference.
060        this.setMaximumSize(GuiHelper.getScreenSize());
061    }
062
063    protected JPanel buildActionPanel() {
064        JPanel pnl = new JPanel(new GridBagLayout());
065
066        JCheckBox expert = new JCheckBox(tr("Expert Mode"));
067        expert.setSelected(ExpertToggleAction.isExpert());
068        expert.addActionListener(e -> ExpertToggleAction.getInstance().actionPerformed(null));
069
070        JPanel btns = new JPanel(new FlowLayout(FlowLayout.CENTER));
071        btns.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
072        OKAction okAction = new OKAction();
073        btns.add(new JButton(okAction));
074        btns.add(new JButton(new CancelAction()));
075        btns.add(new JButton(helpAction));
076        pnl.add(expert, GBC.std().insets(5, 0, 0, 0));
077        pnl.add(btns, GBC.std().fill(GBC.HORIZONTAL));
078        InputMapUtils.addCtrlEnterAction(pnl, okAction);
079        return pnl;
080    }
081
082    protected final void build() {
083        Container c = getContentPane();
084        c.setLayout(new BorderLayout());
085        c.add(tpPreferences, BorderLayout.CENTER);
086        tpPreferences.buildGui();
087        tpPreferences.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
088        c.add(buildActionPanel(), BorderLayout.SOUTH);
089        addWindowListener(windowEventHandler);
090
091        InputMapUtils.addEscapeAction(getRootPane(), new CancelAction());
092        setHelpContext(HelpUtil.ht("/Action/Preferences"));
093    }
094
095    /**
096     * Sets the help context of the preferences dialog.
097     * @param helpContext new help context
098     * @since 13431
099     */
100    public final void setHelpContext(String helpContext) {
101        helpAction.setHelpTopic(helpContext);
102        HelpUtil.setHelpContext(getRootPane(), helpContext);
103    }
104
105    /**
106     * Replies the preferences tabbed pane.
107     * @return The preferences tabbed pane, or null if the dialog is not yet initialized.
108     * @since 5604
109     */
110    public PreferenceTabbedPane getTabbedPane() {
111        return tpPreferences;
112    }
113
114    /**
115     * Determines if preferences changes have been canceled.
116     * @return {@code true} if preferences changes have been canceled
117     */
118    public boolean isCanceled() {
119        return canceled;
120    }
121
122    protected void setCanceled(boolean canceled) {
123        this.canceled = canceled;
124    }
125
126    @Override
127    public void setVisible(boolean visible) {
128        if (visible) {
129            // Make the pref window at most as large as the parent JOSM window
130            // Have to take window decorations into account or the windows will be too large
131            Insets i = this.getParent().getInsets();
132            Dimension p = this.getParent().getSize();
133            p = new Dimension(Math.min(p.width-i.left-i.right, 700),
134                              Math.min(p.height-i.top-i.bottom, 800));
135            new WindowGeometry(
136                    getClass().getName() + ".geometry",
137                    WindowGeometry.centerInWindow(
138                            getParent(),
139                            p
140                    )
141            ).applySafe(this);
142        } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775
143            new WindowGeometry(this).remember(getClass().getName() + ".geometry");
144        }
145        super.setVisible(visible);
146    }
147
148    /**
149     * Select preferences tab that was selected previously.
150     */
151    public void selectPreviouslySelectedPreferences() {
152        if (previouslySelected != null && previouslySelected.b != null) {
153            tpPreferences.selectSubTabByPref(previouslySelected.b);
154        } else if (previouslySelected != null && previouslySelected.a != null) {
155            tpPreferences.selectTabByPref(previouslySelected.a);
156        } else {
157            tpPreferences.setSelectedIndex(0);
158        }
159    }
160
161    /**
162     * Select preferences tab by name.
163     * @param name preferences tab name (icon)
164     */
165    public void selectPreferencesTabByName(String name) {
166        tpPreferences.selectTabByName(name);
167    }
168
169    /**
170     * Select preferences tab by class.
171     * @param clazz preferences tab class
172     */
173    public void selectPreferencesTabByClass(Class<? extends TabPreferenceSetting> clazz) {
174        tpPreferences.selectTabByPref(clazz);
175    }
176
177    /**
178     * Select preferences sub-tab by class.
179     * @param clazz preferences sub-tab class
180     */
181    public void selectSubPreferencesTabByClass(Class<? extends SubPreferenceSetting> clazz) {
182        tpPreferences.selectSubTabByPref(clazz);
183    }
184
185    class CancelAction extends AbstractAction {
186        CancelAction() {
187            putValue(NAME, tr("Cancel"));
188            new ImageProvider("cancel").getResource().attachImageIcon(this);
189            putValue(SHORT_DESCRIPTION, tr("Close the preferences dialog and discard preference updates"));
190        }
191
192        public void cancel() {
193            setCanceled(true);
194            dispose();
195        }
196
197        @Override
198        public void actionPerformed(ActionEvent evt) {
199            cancel();
200        }
201    }
202
203    class OKAction extends AbstractAction {
204        OKAction() {
205            putValue(NAME, tr("OK"));
206            new ImageProvider("ok").getResource().attachImageIcon(this);
207            putValue(SHORT_DESCRIPTION, tr("Save the preferences and close the dialog"));
208        }
209
210        @Override
211        public void actionPerformed(ActionEvent evt) {
212            for (ValidationListener listener: tpPreferences.validationListeners) {
213                if (!listener.validatePreferences())
214                    return;
215            }
216
217            tpPreferences.savePreferences();
218            setCanceled(false);
219            dispose();
220        }
221    }
222
223    class WindowEventHandler extends WindowAdapter {
224        @Override
225        public void windowClosing(WindowEvent arg0) {
226            new CancelAction().cancel();
227        }
228    }
229
230    @Override
231    public void dispose() {
232        previouslySelected = tpPreferences.getSelectedTab();
233        removeWindowListener(windowEventHandler);
234        setVisible(false); // save current geometry
235        super.dispose();
236    }
237}