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