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