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