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