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