001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.preferences.display;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Component;
007import java.awt.GridBagLayout;
008import java.text.DateFormat;
009import java.util.Date;
010
011import javax.swing.BorderFactory;
012import javax.swing.Box;
013import javax.swing.DefaultListCellRenderer;
014import javax.swing.JCheckBox;
015import javax.swing.JFormattedTextField;
016import javax.swing.JLabel;
017import javax.swing.JList;
018import javax.swing.JScrollPane;
019import javax.swing.JSpinner;
020import javax.swing.ListCellRenderer;
021import javax.swing.LookAndFeel;
022import javax.swing.SpinnerNumberModel;
023import javax.swing.UIManager;
024import javax.swing.UIManager.LookAndFeelInfo;
025
026import org.openstreetmap.josm.Main;
027import org.openstreetmap.josm.actions.ExpertToggleAction;
028import org.openstreetmap.josm.gui.MapMover;
029import org.openstreetmap.josm.gui.NavigatableComponent;
030import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
031import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
032import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory;
033import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
034import org.openstreetmap.josm.gui.preferences.SubPreferenceSetting;
035import org.openstreetmap.josm.gui.preferences.TabPreferenceSetting;
036import org.openstreetmap.josm.gui.widgets.FileChooserManager;
037import org.openstreetmap.josm.gui.widgets.JosmComboBox;
038import org.openstreetmap.josm.gui.widgets.VerticallyScrollablePanel;
039import org.openstreetmap.josm.tools.GBC;
040import org.openstreetmap.josm.tools.date.DateUtils;
041
042/**
043 * Look-and-feel preferences.
044 */
045public class LafPreference implements SubPreferenceSetting {
046
047    /**
048     * Factory used to create a new {@code LafPreference}.
049     */
050    public static class Factory implements PreferenceSettingFactory {
051        @Override
052        public PreferenceSetting createPreferenceSetting() {
053            return new LafPreference();
054        }
055    }
056
057    /**
058     * ComboBox with all look and feels.
059     */
060    private JosmComboBox<LookAndFeelInfo> lafCombo;
061    VerticallyScrollablePanel panel;
062    private final JCheckBox showSplashScreen = new JCheckBox(tr("Show splash screen at startup"));
063    private final JCheckBox showID = new JCheckBox(tr("Show object ID in selection lists"));
064    private final JCheckBox showLocalizedName = new JCheckBox(tr("Show localized name in selection lists"));
065    private final JCheckBox modeless = new JCheckBox(tr("Modeless working (Potlatch style)"));
066    private final JCheckBox dynamicButtons = new JCheckBox(tr("Dynamic buttons in side menus"));
067    private final JCheckBox isoDates = new JCheckBox(tr("Display ISO dates"));
068    private final JCheckBox nativeFileChoosers = new JCheckBox(tr("Use native file choosers (nicer, but do not support file filters)"));
069    private final JCheckBox zoomReverseWheel = new JCheckBox(tr("Reverse zoom with mouse wheel"));
070    private final JCheckBox zoomIntermediateSteps = new JCheckBox(tr("Intermediate steps between native resolutions"));
071    private JSpinner spinZoomRatio;
072
073    @Override
074    public void addGui(PreferenceTabbedPane gui) {
075        lafCombo = new JosmComboBox<>(UIManager.getInstalledLookAndFeels());
076
077        // let's try to load additional LookAndFeels and put them into the list
078        if (Main.isPlatformOsx()) {
079            try {
080                Class<?> cquaqua = Class.forName("ch.randelshofer.quaqua.QuaquaLookAndFeel");
081                Object oquaqua = cquaqua.getConstructor((Class[]) null).newInstance((Object[]) null);
082                // no exception? Then Go!
083                lafCombo.addItem(
084                        new UIManager.LookAndFeelInfo(((LookAndFeel) oquaqua).getName(), "ch.randelshofer.quaqua.QuaquaLookAndFeel")
085                );
086            } catch (ReflectiveOperationException ex) {
087                // just debug, Quaqua may not even be installed...
088                Main.debug(ex);
089            }
090        }
091
092        String laf = Main.pref.get("laf", Main.platform.getDefaultStyle());
093        for (int i = 0; i < lafCombo.getItemCount(); ++i) {
094            if (lafCombo.getItemAt(i).getClassName().equals(laf)) {
095                lafCombo.setSelectedIndex(i);
096                break;
097            }
098        }
099
100        lafCombo.setRenderer(new ListCellRenderer<LookAndFeelInfo>() {
101            private final DefaultListCellRenderer def = new DefaultListCellRenderer();
102            @Override
103            public Component getListCellRendererComponent(JList<? extends LookAndFeelInfo> list, LookAndFeelInfo value,
104                    int index, boolean isSelected, boolean cellHasFocus) {
105                return def.getListCellRendererComponent(list, value.getName(), index, isSelected, cellHasFocus);
106            }
107        });
108
109        panel = new VerticallyScrollablePanel(new GridBagLayout());
110        panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
111
112        // Show splash screen on startup
113        showSplashScreen.setToolTipText(tr("Show splash screen at startup"));
114        showSplashScreen.setSelected(Main.pref.getBoolean("draw.splashscreen", true));
115        panel.add(showSplashScreen, GBC.eop().insets(20, 0, 0, 0));
116
117        // Show ID in selection
118        showID.setToolTipText(tr("Show object ID in selection lists"));
119        showID.setSelected(Main.pref.getBoolean("osm-primitives.showid", false));
120
121        // Show localized names
122        showLocalizedName.setToolTipText(tr("Show localized name in selection lists, if available"));
123        showLocalizedName.setSelected(Main.pref.getBoolean("osm-primitives.localize-name", true));
124        ExpertToggleAction.addVisibilitySwitcher(showLocalizedName);
125
126        modeless.setToolTipText(tr("Do not require to switch modes (potlatch style workflow)"));
127        modeless.setSelected(Main.pref.getBoolean("modeless", false));
128        ExpertToggleAction.addVisibilitySwitcher(modeless);
129
130        panel.add(showID, GBC.eop().insets(20, 0, 0, 0));
131        panel.add(showLocalizedName, GBC.eop().insets(20, 0, 0, 0));
132        panel.add(modeless, GBC.eop().insets(20, 0, 0, 0));
133
134        dynamicButtons.setToolTipText(tr("Display buttons in right side menus only when mouse is inside the element"));
135        dynamicButtons.setSelected(ToggleDialog.PROP_DYNAMIC_BUTTONS.get());
136        panel.add(dynamicButtons, GBC.eop().insets(20, 0, 0, 0));
137
138        Date today = new Date();
139        isoDates.setToolTipText(tr("Format dates according to {0}. Today''s date will be displayed as {1} instead of {2}",
140                tr("ISO 8601"),
141                DateUtils.newIsoDateFormat().format(today),
142                DateFormat.getDateInstance(DateFormat.SHORT).format(today)));
143        isoDates.setSelected(DateUtils.PROP_ISO_DATES.get());
144        panel.add(isoDates, GBC.eop().insets(20, 0, 0, 0));
145
146        nativeFileChoosers.setToolTipText(
147                tr("Use file choosers that behave more like native ones. They look nicer but do not support some features like file filters"));
148        nativeFileChoosers.setSelected(FileChooserManager.PROP_USE_NATIVE_FILE_DIALOG.get());
149        panel.add(nativeFileChoosers, GBC.eop().insets(20, 0, 0, 0));
150
151        zoomReverseWheel.setToolTipText(
152                tr("Check if you feel opposite direction more convenient"));
153        zoomReverseWheel.setSelected(MapMover.PROP_ZOOM_REVERSE_WHEEL.get());
154        panel.add(zoomReverseWheel, GBC.eop().insets(20, 0, 0, 0));
155
156        zoomIntermediateSteps.setToolTipText(
157                tr("Divide intervals between native resolution levels to smaller steps if they are much larger than zoom ratio"));
158        zoomIntermediateSteps.setSelected(NavigatableComponent.PROP_ZOOM_INTERMEDIATE_STEPS.get());
159        ExpertToggleAction.addVisibilitySwitcher(zoomIntermediateSteps);
160        panel.add(zoomIntermediateSteps, GBC.eop().insets(20, 0, 0, 0));
161
162        panel.add(Box.createVerticalGlue(), GBC.eol().insets(0, 10, 0, 0));
163
164        double logZoomLevel = Math.log(2) / Math.log(NavigatableComponent.PROP_ZOOM_RATIO.get());
165        logZoomLevel = Math.max(1, logZoomLevel);
166        logZoomLevel = Math.min(5, logZoomLevel);
167        JLabel labelZoomRatio = new JLabel(tr("Zoom steps to get double scale"));
168        spinZoomRatio = new JSpinner(new SpinnerNumberModel(logZoomLevel, 1, 5, 1));
169        Component spinZoomRatioEditor = spinZoomRatio.getEditor();
170        JFormattedTextField jftf = ((JSpinner.DefaultEditor) spinZoomRatioEditor).getTextField();
171        jftf.setColumns(2);
172        String zoomRatioToolTipText = tr("Higher value means more steps needed, therefore zoom steps will be smaller");
173        spinZoomRatio.setToolTipText(zoomRatioToolTipText);
174        labelZoomRatio.setToolTipText(zoomRatioToolTipText);
175        labelZoomRatio.setLabelFor(spinZoomRatio);
176        panel.add(labelZoomRatio, GBC.std().insets(20, 0, 0, 0));
177        panel.add(GBC.glue(5, 0), GBC.std().fill(GBC.HORIZONTAL));
178        panel.add(spinZoomRatio, GBC.eol());
179
180        panel.add(new JLabel(tr("Look and Feel")), GBC.std().insets(20, 0, 0, 0));
181        panel.add(GBC.glue(5, 0), GBC.std().fill(GBC.HORIZONTAL));
182        panel.add(lafCombo, GBC.eol().fill(GBC.HORIZONTAL));
183
184        JScrollPane scrollpane = panel.getVerticalScrollPane();
185        scrollpane.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
186        gui.getDisplayPreference().addSubTab(this, tr("Look and Feel"), scrollpane);
187    }
188
189    @Override
190    public boolean ok() {
191        boolean mod = false;
192        Main.pref.put("draw.splashscreen", showSplashScreen.isSelected());
193        Main.pref.put("osm-primitives.showid", showID.isSelected());
194        Main.pref.put("osm-primitives.localize-name", showLocalizedName.isSelected());
195        Main.pref.put("modeless", modeless.isSelected());
196        Main.pref.put(ToggleDialog.PROP_DYNAMIC_BUTTONS.getKey(), dynamicButtons.isSelected());
197        Main.pref.put(DateUtils.PROP_ISO_DATES.getKey(), isoDates.isSelected());
198        Main.pref.put(FileChooserManager.PROP_USE_NATIVE_FILE_DIALOG.getKey(), nativeFileChoosers.isSelected());
199        MapMover.PROP_ZOOM_REVERSE_WHEEL.put(zoomReverseWheel.isSelected());
200        NavigatableComponent.PROP_ZOOM_INTERMEDIATE_STEPS.put(zoomIntermediateSteps.isSelected());
201        NavigatableComponent.PROP_ZOOM_RATIO.put(Math.pow(2, 1/(double) spinZoomRatio.getModel().getValue()));
202        mod |= Main.pref.put("laf", ((LookAndFeelInfo) lafCombo.getSelectedItem()).getClassName());
203        return mod;
204    }
205
206    @Override
207    public boolean isExpert() {
208        return false;
209    }
210
211    @Override
212    public TabPreferenceSetting getTabPreferenceSetting(final PreferenceTabbedPane gui) {
213        return gui.getDisplayPreference();
214    }
215}