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}