001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.preferences.display; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005import static org.openstreetmap.josm.tools.I18n.trc; 006 007import java.awt.Component; 008import java.awt.Dimension; 009import java.awt.GridBagLayout; 010import java.awt.event.ActionListener; 011import java.util.Collections; 012import java.util.Enumeration; 013import java.util.HashMap; 014import java.util.List; 015import java.util.Map; 016import java.util.Optional; 017 018import javax.swing.AbstractButton; 019import javax.swing.BorderFactory; 020import javax.swing.Box; 021import javax.swing.ButtonGroup; 022import javax.swing.JCheckBox; 023import javax.swing.JLabel; 024import javax.swing.JOptionPane; 025import javax.swing.JPanel; 026import javax.swing.JRadioButton; 027import javax.swing.JSlider; 028 029import org.apache.commons.jcs.access.exception.InvalidArgumentException; 030import org.openstreetmap.josm.actions.ExpertToggleAction; 031import org.openstreetmap.josm.data.gpx.GpxData; 032import org.openstreetmap.josm.gui.MainApplication; 033import org.openstreetmap.josm.gui.layer.GpxLayer; 034import org.openstreetmap.josm.gui.layer.gpx.GpxDrawHelper; 035import org.openstreetmap.josm.gui.layer.markerlayer.Marker; 036import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane.ValidationListener; 037import org.openstreetmap.josm.gui.widgets.JosmComboBox; 038import org.openstreetmap.josm.gui.widgets.JosmTextField; 039import org.openstreetmap.josm.spi.preferences.Config; 040import org.openstreetmap.josm.tools.GBC; 041import org.openstreetmap.josm.tools.Logging; 042import org.openstreetmap.josm.tools.template_engine.ParseError; 043import org.openstreetmap.josm.tools.template_engine.TemplateParser; 044 045/** 046 * Panel for GPX settings. 047 */ 048public class GPXSettingsPanel extends JPanel implements ValidationListener { 049 050 private static final int WAYPOINT_LABEL_CUSTOM = 6; 051 private static final String[] LABEL_PATTERN_TEMPLATE = {Marker.LABEL_PATTERN_AUTO, Marker.LABEL_PATTERN_NAME, 052 Marker.LABEL_PATTERN_DESC, "{special:everything}", "?{ '{name}' | '{desc}' | '{formattedWaypointOffset}' }", " "}; 053 private static final String[] LABEL_PATTERN_DESC = {tr("Auto"), /* gpx data field name */ trc("gpx_field", "Name"), 054 /* gpx data field name */ trc("gpx_field", "Desc(ription)"), tr("Everything"), tr("Name or offset"), tr("None"), tr("Custom")}; 055 056 private final JRadioButton drawRawGpsLinesGlobal = new JRadioButton(tr("Use global settings")); 057 private final JRadioButton drawRawGpsLinesAll = new JRadioButton(tr("All")); 058 private final JRadioButton drawRawGpsLinesLocal = new JRadioButton(tr("Local files")); 059 private final JRadioButton drawRawGpsLinesNone = new JRadioButton(tr("None")); 060 private transient ActionListener drawRawGpsLinesActionListener; 061 private final JosmTextField drawRawGpsMaxLineLength = new JosmTextField(8); 062 private final JosmTextField drawRawGpsMaxLineLengthLocal = new JosmTextField(8); 063 private final JosmTextField drawLineWidth = new JosmTextField(2); 064 private final JCheckBox forceRawGpsLines = new JCheckBox(tr("Force lines if no segments imported")); 065 private final JCheckBox largeGpsPoints = new JCheckBox(tr("Draw large GPS points")); 066 private final JCheckBox hdopCircleGpsPoints = new JCheckBox(tr("Draw a circle from HDOP value")); 067 private final JRadioButton colorTypeVelocity = new JRadioButton(tr("Velocity (red = slow, green = fast)")); 068 private final JRadioButton colorTypeDirection = new JRadioButton(tr("Direction (red = west, yellow = north, green = east, blue = south)")); 069 private final JRadioButton colorTypeDilution = new JRadioButton(tr("Dilution of Position (red = high, green = low, if available)")); 070 private final JRadioButton colorTypeQuality = new JRadioButton(tr("Quality (RTKLib only, if available)")); 071 private final JRadioButton colorTypeTime = new JRadioButton(tr("Track date")); 072 private final JRadioButton colorTypeHeatMap = new JRadioButton(tr("Heat Map (dark = few, bright = many)")); 073 private final JRadioButton colorTypeNone = new JRadioButton(tr("Single Color (can be customized in the layer manager)")); 074 private final JRadioButton colorTypeGlobal = new JRadioButton(tr("Use global settings")); 075 private final JosmComboBox<String> colorTypeVelocityTune = new JosmComboBox<>(new String[] {tr("Car"), tr("Bicycle"), tr("Foot")}); 076 private final JosmComboBox<String> colorTypeHeatMapTune = new JosmComboBox<>(new String[] { 077 trc("Heat map", "User Normal"), 078 trc("Heat map", "User Light"), 079 trc("Heat map", "Traffic Lights"), 080 trc("Heat map", "Inferno"), 081 trc("Heat map", "Viridis"), 082 trc("Heat map", "Wood"), 083 trc("Heat map", "Heat")}); 084 private final JCheckBox colorTypeHeatMapPoints = new JCheckBox(tr("Use points instead of lines for heat map")); 085 private final JSlider colorTypeHeatMapGain = new JSlider(); 086 private final JSlider colorTypeHeatMapLowerLimit = new JSlider(); 087 private final JCheckBox makeAutoMarkers = new JCheckBox(tr("Create markers when reading GPX")); 088 private final JCheckBox drawGpsArrows = new JCheckBox(tr("Draw Direction Arrows")); 089 private final JCheckBox drawGpsArrowsFast = new JCheckBox(tr("Fast drawing (looks uglier)")); 090 private final JosmTextField drawGpsArrowsMinDist = new JosmTextField(8); 091 private final JCheckBox colorDynamic = new JCheckBox(tr("Dynamic color range based on data limits")); 092 private final JosmComboBox<String> waypointLabel = new JosmComboBox<>(LABEL_PATTERN_DESC); 093 private final JosmTextField waypointLabelPattern = new JosmTextField(); 094 private final JosmComboBox<String> audioWaypointLabel = new JosmComboBox<>(LABEL_PATTERN_DESC); 095 private final JosmTextField audioWaypointLabelPattern = new JosmTextField(); 096 private final JCheckBox useGpsAntialiasing = new JCheckBox(tr("Smooth GPX graphics (antialiasing)")); 097 private final JCheckBox drawLineWithAlpha = new JCheckBox(tr("Draw with Opacity (alpha blending) ")); 098 099 private final List<GpxLayer> layers; 100 private final GpxLayer firstLayer; 101 private final boolean global; // global settings vs. layer specific settings 102 private final boolean hasLocalFile; // flag to display LocalOnly checkbooks 103 private final boolean hasNonLocalFile; // flag to display AllLines checkbox 104 105 private static final Map<String, Object> DEFAULT_PREFS = getDefaultPrefs(); 106 107 private static Map<String, Object> getDefaultPrefs() { 108 HashMap<String, Object> m = new HashMap<>(); 109 m.put("colormode", -1); 110 m.put("colormode.dynamic-range", false); 111 m.put("colormode.heatmap.colormap", 0); 112 m.put("colormode.heatmap.gain", 0); 113 m.put("colormode.heatmap.line-extra", false); //Einstein only 114 m.put("colormode.heatmap.lower-limit", 0); 115 m.put("colormode.heatmap.use-points", false); 116 m.put("colormode.time.min-distance", 60); //Einstein only 117 m.put("colormode.velocity.tune", 45); 118 m.put("lines", -1); 119 m.put("lines.alpha-blend", false); 120 m.put("lines.arrows", false); 121 m.put("lines.arrows.fast", false); 122 m.put("lines.arrows.min-distance", 40); 123 m.put("lines.force", false); 124 m.put("lines.max-length", 200); 125 m.put("lines.max-length.local", -1); 126 m.put("lines.width", 0); 127 m.put("markers.color", ""); 128 m.put("markers.show-text", true); 129 m.put("markers.pattern", Marker.LABEL_PATTERN_AUTO); 130 m.put("markers.audio.pattern", "?{ '{name}' | '{desc}' | '{" + Marker.MARKER_FORMATTED_OFFSET + "}' }"); 131 m.put("points.hdopcircle", false); 132 m.put("points.large", false); 133 m.put("points.large.alpha", -1); //Einstein only 134 m.put("points.large.size", 3); //Einstein only 135 return Collections.unmodifiableMap(m); 136 } 137 138 /** 139 * Constructs a new {@code GPXSettingsPanel} for the given layers. 140 * @param layers the GPX layers 141 */ 142 public GPXSettingsPanel(List<GpxLayer> layers) { 143 super(new GridBagLayout()); 144 this.layers = layers; 145 if (layers == null || layers.isEmpty()) { 146 throw new InvalidArgumentException("At least one layer required"); 147 } 148 firstLayer = layers.get(0); 149 global = false; 150 hasLocalFile = layers.stream().anyMatch(l -> !l.data.fromServer); 151 hasNonLocalFile = layers.stream().anyMatch(l -> l.data.fromServer); 152 initComponents(); 153 loadPreferences(); 154 } 155 156 /** 157 * Constructs a new {@code GPXSettingsPanel}. 158 */ 159 public GPXSettingsPanel() { 160 super(new GridBagLayout()); 161 layers = null; 162 firstLayer = null; 163 global = hasLocalFile = hasNonLocalFile = true; 164 initComponents(); 165 loadPreferences(); // preferences -> controls 166 } 167 168 /** 169 * Reads the preference for the given layer or the default preference if not available 170 * @param layer the GpxLayer. Can be <code>null</code>, default preference will be returned then 171 * @param key the drawing key to be read, without "draw.rawgps." 172 * @return the value 173 */ 174 public static String getLayerPref(GpxLayer layer, String key) { 175 Object d = DEFAULT_PREFS.get(key); 176 String ds; 177 if (d != null) { 178 ds = d.toString(); 179 } else { 180 Logging.warn("No default value found for layer preference \"" + key + "\"."); 181 ds = null; 182 } 183 return Optional.ofNullable(tryGetLayerPrefLocal(layer, key)).orElse(Config.getPref().get("draw.rawgps." + key, ds)); 184 } 185 186 /** 187 * Reads the integer preference for the given layer or the default preference if not available 188 * @param layer the GpxLayer. Can be <code>null</code>, default preference will be returned then 189 * @param key the drawing key to be read, without "draw.rawgps." 190 * @return the integer value 191 */ 192 public static int getLayerPrefInt(GpxLayer layer, String key) { 193 String s = getLayerPref(layer, key); 194 if (s != null) { 195 try { 196 return Integer.parseInt(s); 197 } catch (NumberFormatException ex) { 198 Object d = DEFAULT_PREFS.get(key); 199 if (d instanceof Integer) { 200 return (int) d; 201 } else { 202 Logging.warn("No valid default value found for layer preference \"" + key + "\"."); 203 } 204 } 205 } 206 return 0; 207 } 208 209 /** 210 * Try to read the preference for the given layer 211 * @param layer the GpxLayer 212 * @param key the drawing key to be read, without "draw.rawgps." 213 * @return the value or <code>null</code> if not found 214 */ 215 public static String tryGetLayerPrefLocal(GpxLayer layer, String key) { 216 return layer != null ? tryGetLayerPrefLocal(layer.data, key) : null; 217 } 218 219 /** 220 * Try to read the preference for the given GpxData 221 * @param data the GpxData 222 * @param key the drawing key to be read, without "draw.rawgps." 223 * @return the value or <code>null</code> if not found 224 */ 225 public static String tryGetLayerPrefLocal(GpxData data, String key) { 226 return data != null ? data.getLayerPrefs().get(key) : null; 227 } 228 229 /** 230 * Puts the preference for the given layers or the default preference if layers is <code>null</code> 231 * @param layers List of <code>GpxLayer</code> to put the drawingOptions 232 * @param key the drawing key to be written, without "draw.rawgps." 233 * @param value (can be <code>null</code> to remove option) 234 */ 235 public static void putLayerPref(List<GpxLayer> layers, String key, Object value) { 236 String v = value == null ? null : value.toString(); 237 if (layers != null) { 238 for (GpxLayer l : layers) { 239 putLayerPrefLocal(l.data, key, v); 240 } 241 } else { 242 Config.getPref().put("draw.rawgps." + key, v); 243 } 244 } 245 246 /** 247 * Puts the preference for the given layer 248 * @param layer <code>GpxLayer</code> to put the drawingOptions 249 * @param key the drawing key to be written, without "draw.rawgps." 250 * @param value the value or <code>null</code> to remove key 251 */ 252 public static void putLayerPrefLocal(GpxLayer layer, String key, String value) { 253 if (layer == null) return; 254 putLayerPrefLocal(layer.data, key, value); 255 } 256 257 /** 258 * Puts the preference for the given layer 259 * @param data <code>GpxData</code> to put the drawingOptions. Must not be <code>null</code> 260 * @param key the drawing key to be written, without "draw.rawgps." 261 * @param value the value or <code>null</code> to remove key 262 */ 263 public static void putLayerPrefLocal(GpxData data, String key, String value) { 264 if (value == null || value.trim().isEmpty() || 265 (getLayerPref(null, key).equals(value) && DEFAULT_PREFS.get(key) != null && DEFAULT_PREFS.get(key).toString().equals(value))) { 266 data.getLayerPrefs().remove(key); 267 } else { 268 data.getLayerPrefs().put(key, value); 269 } 270 } 271 272 private String pref(String key) { 273 return getLayerPref(firstLayer, key); 274 } 275 276 private boolean prefBool(String key) { 277 return Boolean.parseBoolean(pref(key)); 278 } 279 280 private int prefInt(String key) { 281 return getLayerPrefInt(firstLayer, key); 282 } 283 284 private int prefIntLocal(String key) { 285 try { 286 return Integer.parseInt(tryGetLayerPrefLocal(firstLayer, key)); 287 } catch (NumberFormatException ex) { 288 return -1; 289 } 290 291 } 292 293 private void putPref(String key, Object value) { 294 putLayerPref(layers, key, value); 295 } 296 297 // CHECKSTYLE.OFF: ExecutableStatementCountCheck 298 private void initComponents() { 299 setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); 300 301 if (global) { 302 // makeAutoMarkers 303 makeAutoMarkers.setToolTipText(tr("Automatically make a marker layer from any waypoints when opening a GPX layer.")); 304 ExpertToggleAction.addVisibilitySwitcher(makeAutoMarkers); 305 add(makeAutoMarkers, GBC.eol().insets(20, 0, 0, 5)); 306 } 307 308 // drawRawGpsLines 309 ButtonGroup gpsLinesGroup = new ButtonGroup(); 310 if (!global) { 311 gpsLinesGroup.add(drawRawGpsLinesGlobal); 312 } 313 gpsLinesGroup.add(drawRawGpsLinesNone); 314 gpsLinesGroup.add(drawRawGpsLinesLocal); 315 gpsLinesGroup.add(drawRawGpsLinesAll); 316 317 /* ensure that default is in data base */ 318 319 JLabel label = new JLabel(tr("Draw lines between raw GPS points")); 320 add(label, GBC.eol().insets(20, 0, 0, 0)); 321 if (!global) { 322 add(drawRawGpsLinesGlobal, GBC.eol().insets(40, 0, 0, 0)); 323 } 324 add(drawRawGpsLinesNone, GBC.eol().insets(40, 0, 0, 0)); 325 if (hasLocalFile) { 326 add(drawRawGpsLinesLocal, GBC.eol().insets(40, 0, 0, 0)); 327 } 328 if (hasNonLocalFile) { 329 add(drawRawGpsLinesAll, GBC.eol().insets(40, 0, 0, 0)); 330 } 331 ExpertToggleAction.addVisibilitySwitcher(label); 332 ExpertToggleAction.addVisibilitySwitcher(drawRawGpsLinesGlobal); 333 ExpertToggleAction.addVisibilitySwitcher(drawRawGpsLinesNone); 334 ExpertToggleAction.addVisibilitySwitcher(drawRawGpsLinesLocal); 335 ExpertToggleAction.addVisibilitySwitcher(drawRawGpsLinesAll); 336 337 drawRawGpsLinesActionListener = e -> { 338 boolean f = drawRawGpsLinesNone.isSelected() || drawRawGpsLinesGlobal.isSelected(); 339 forceRawGpsLines.setEnabled(!f); 340 drawRawGpsMaxLineLength.setEnabled(!(f || drawRawGpsLinesLocal.isSelected())); 341 drawRawGpsMaxLineLengthLocal.setEnabled(!f); 342 drawGpsArrows.setEnabled(!f); 343 drawGpsArrowsFast.setEnabled(drawGpsArrows.isSelected() && drawGpsArrows.isEnabled()); 344 drawGpsArrowsMinDist.setEnabled(drawGpsArrows.isSelected() && drawGpsArrows.isEnabled()); 345 }; 346 347 drawRawGpsLinesGlobal.addActionListener(drawRawGpsLinesActionListener); 348 drawRawGpsLinesNone.addActionListener(drawRawGpsLinesActionListener); 349 drawRawGpsLinesLocal.addActionListener(drawRawGpsLinesActionListener); 350 drawRawGpsLinesAll.addActionListener(drawRawGpsLinesActionListener); 351 352 // drawRawGpsMaxLineLengthLocal 353 drawRawGpsMaxLineLengthLocal.setToolTipText( 354 tr("Maximum length (in meters) to draw lines for local files. Set to ''-1'' to draw all lines.")); 355 label = new JLabel(tr("Maximum length for local files (meters)")); 356 add(label, GBC.std().insets(40, 0, 0, 0)); 357 add(drawRawGpsMaxLineLengthLocal, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5)); 358 ExpertToggleAction.addVisibilitySwitcher(label); 359 ExpertToggleAction.addVisibilitySwitcher(drawRawGpsMaxLineLengthLocal); 360 361 // drawRawGpsMaxLineLength 362 drawRawGpsMaxLineLength.setToolTipText(tr("Maximum length (in meters) to draw lines. Set to ''-1'' to draw all lines.")); 363 label = new JLabel(tr("Maximum length (meters)")); 364 add(label, GBC.std().insets(40, 0, 0, 0)); 365 add(drawRawGpsMaxLineLength, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5)); 366 ExpertToggleAction.addVisibilitySwitcher(label); 367 ExpertToggleAction.addVisibilitySwitcher(drawRawGpsMaxLineLength); 368 369 // forceRawGpsLines 370 forceRawGpsLines.setToolTipText(tr("Force drawing of lines if the imported data contain no line information.")); 371 add(forceRawGpsLines, GBC.eop().insets(40, 0, 0, 0)); 372 ExpertToggleAction.addVisibilitySwitcher(forceRawGpsLines); 373 374 // drawGpsArrows 375 drawGpsArrows.addActionListener(e -> { 376 drawGpsArrowsFast.setEnabled(drawGpsArrows.isSelected() && drawGpsArrows.isEnabled()); 377 drawGpsArrowsMinDist.setEnabled(drawGpsArrows.isSelected() && drawGpsArrows.isEnabled()); 378 }); 379 drawGpsArrows.setToolTipText(tr("Draw direction arrows for lines, connecting GPS points.")); 380 add(drawGpsArrows, GBC.eop().insets(20, 0, 0, 0)); 381 382 // drawGpsArrowsFast 383 drawGpsArrowsFast.setToolTipText(tr("Draw the direction arrows using table lookups instead of complex math.")); 384 add(drawGpsArrowsFast, GBC.eop().insets(40, 0, 0, 0)); 385 ExpertToggleAction.addVisibilitySwitcher(drawGpsArrowsFast); 386 387 // drawGpsArrowsMinDist 388 drawGpsArrowsMinDist.setToolTipText(tr("Do not draw arrows if they are not at least this distance away from the last one.")); 389 add(new JLabel(tr("Minimum distance (pixels)")), GBC.std().insets(40, 0, 0, 0)); 390 add(drawGpsArrowsMinDist, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5)); 391 392 // hdopCircleGpsPoints 393 hdopCircleGpsPoints.setToolTipText(tr("Draw a circle from HDOP value")); 394 add(hdopCircleGpsPoints, GBC.eop().insets(20, 0, 0, 0)); 395 ExpertToggleAction.addVisibilitySwitcher(hdopCircleGpsPoints); 396 397 // largeGpsPoints 398 largeGpsPoints.setToolTipText(tr("Draw larger dots for the GPS points.")); 399 add(largeGpsPoints, GBC.eop().insets(20, 0, 0, 0)); 400 401 // drawLineWidth 402 drawLineWidth.setToolTipText(tr("Width of drawn GPX line (0 for default)")); 403 add(new JLabel(tr("Drawing width of GPX lines")), GBC.std().insets(20, 0, 0, 0)); 404 add(drawLineWidth, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5)); 405 406 // antialiasing 407 useGpsAntialiasing.setToolTipText(tr("Apply antialiasing to the GPX lines resulting in a smoother appearance.")); 408 add(useGpsAntialiasing, GBC.eop().insets(20, 0, 0, 0)); 409 ExpertToggleAction.addVisibilitySwitcher(useGpsAntialiasing); 410 411 // alpha blending 412 drawLineWithAlpha.setToolTipText(tr("Apply dynamic alpha-blending and adjust width based on zoom level for all GPX lines.")); 413 add(drawLineWithAlpha, GBC.eop().insets(20, 0, 0, 0)); 414 ExpertToggleAction.addVisibilitySwitcher(drawLineWithAlpha); 415 416 // colorTracks 417 ButtonGroup colorGroup = new ButtonGroup(); 418 if (!global) { 419 colorGroup.add(colorTypeGlobal); 420 } 421 colorGroup.add(colorTypeNone); 422 colorGroup.add(colorTypeVelocity); 423 colorGroup.add(colorTypeDirection); 424 colorGroup.add(colorTypeDilution); 425 colorGroup.add(colorTypeQuality); 426 colorGroup.add(colorTypeTime); 427 colorGroup.add(colorTypeHeatMap); 428 429 colorTypeNone.setToolTipText(tr("All points and track segments will have their own color. Can be customized in Layer Manager.")); 430 colorTypeVelocity.setToolTipText(tr("Colors points and track segments by velocity.")); 431 colorTypeDirection.setToolTipText(tr("Colors points and track segments by direction.")); 432 colorTypeDilution.setToolTipText( 433 tr("Colors points and track segments by dilution of position (HDOP). Your capture device needs to log that information.")); 434 colorTypeQuality.setToolTipText( 435 tr("Colors points and track segments by RTKLib quality flag (Q). Your capture device needs to log that information.")); 436 colorTypeTime.setToolTipText(tr("Colors points and track segments by its timestamp.")); 437 colorTypeHeatMap.setToolTipText(tr("Collected points and track segments for a position and displayed as heat map.")); 438 439 // color Tracks by Velocity Tune 440 colorTypeVelocityTune.setToolTipText(tr("Allows to tune the track coloring for different average speeds.")); 441 442 colorTypeHeatMapTune.setToolTipText(tr("Selects the color schema for heat map.")); 443 JLabel colorTypeHeatIconLabel = new JLabel(); 444 445 add(Box.createVerticalGlue(), GBC.eol().insets(0, 20, 0, 0)); 446 447 add(new JLabel(tr("Track and Point Coloring")), GBC.eol().insets(20, 0, 0, 0)); 448 if (!global) { 449 add(colorTypeGlobal, GBC.eol().insets(40, 0, 0, 0)); 450 } 451 add(colorTypeNone, GBC.eol().insets(40, 0, 0, 0)); 452 add(colorTypeVelocity, GBC.std().insets(40, 0, 0, 0)); 453 add(colorTypeVelocityTune, GBC.eop().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5)); 454 add(colorTypeDirection, GBC.eol().insets(40, 0, 0, 0)); 455 add(colorTypeDilution, GBC.eol().insets(40, 0, 0, 0)); 456 add(colorTypeQuality, GBC.eol().insets(40, 0, 0, 0)); 457 add(colorTypeTime, GBC.eol().insets(40, 0, 0, 0)); 458 add(colorTypeHeatMap, GBC.std().insets(40, 0, 0, 0)); 459 add(colorTypeHeatIconLabel, GBC.std().insets(5, 0, 0, 5)); 460 add(colorTypeHeatMapTune, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5)); 461 462 JLabel colorTypeHeatMapGainLabel = new JLabel(tr("Overlay gain adjustment")); 463 JLabel colorTypeHeatMapLowerLimitLabel = new JLabel(tr("Lower limit of visibility")); 464 add(colorTypeHeatMapGainLabel, GBC.std().insets(80, 0, 0, 0)); 465 add(colorTypeHeatMapGain, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5)); 466 add(colorTypeHeatMapLowerLimitLabel, GBC.std().insets(80, 0, 0, 0)); 467 add(colorTypeHeatMapLowerLimit, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5)); 468 add(colorTypeHeatMapPoints, GBC.eol().insets(60, 0, 0, 0)); 469 470 colorTypeHeatMapGain.setToolTipText(tr("Adjust the gain of overlay blending.")); 471 colorTypeHeatMapGain.setOrientation(JSlider.HORIZONTAL); 472 colorTypeHeatMapGain.setPaintLabels(true); 473 colorTypeHeatMapGain.setMinimum(-10); 474 colorTypeHeatMapGain.setMaximum(+10); 475 colorTypeHeatMapGain.setMinorTickSpacing(1); 476 colorTypeHeatMapGain.setMajorTickSpacing(5); 477 478 colorTypeHeatMapLowerLimit.setToolTipText(tr("Draw all GPX traces that exceed this threshold.")); 479 colorTypeHeatMapLowerLimit.setOrientation(JSlider.HORIZONTAL); 480 colorTypeHeatMapLowerLimit.setMinimum(0); 481 colorTypeHeatMapLowerLimit.setMaximum(254); 482 colorTypeHeatMapLowerLimit.setPaintLabels(true); 483 colorTypeHeatMapLowerLimit.setMinorTickSpacing(10); 484 colorTypeHeatMapLowerLimit.setMajorTickSpacing(100); 485 486 colorTypeHeatMapPoints.setToolTipText(tr("Render engine uses points with simulated position error instead of lines. ")); 487 488 // iterate over the buttons, add change listener to any change event 489 for (Enumeration<AbstractButton> button = colorGroup.getElements(); button.hasMoreElements();) { 490 (button.nextElement()).addChangeListener(e -> { 491 colorTypeVelocityTune.setEnabled(colorTypeVelocity.isSelected()); 492 colorTypeHeatMapTune.setEnabled(colorTypeHeatMap.isSelected()); 493 colorTypeHeatMapPoints.setEnabled(colorTypeHeatMap.isSelected()); 494 colorTypeHeatMapGain.setEnabled(colorTypeHeatMap.isSelected()); 495 colorTypeHeatMapLowerLimit.setEnabled(colorTypeHeatMap.isSelected()); 496 colorTypeHeatMapGainLabel.setEnabled(colorTypeHeatMap.isSelected()); 497 colorTypeHeatMapLowerLimitLabel.setEnabled(colorTypeHeatMap.isSelected()); 498 colorDynamic.setEnabled(colorTypeVelocity.isSelected() || colorTypeDilution.isSelected()); 499 }); 500 } 501 502 colorTypeHeatMapTune.addActionListener(e -> { 503 final Dimension dim = colorTypeHeatMapTune.getPreferredSize(); 504 if (null != dim) { 505 // get image size of environment 506 final int iconSize = (int) dim.getHeight(); 507 colorTypeHeatIconLabel.setIcon(GpxDrawHelper.getColorMapImageIcon( 508 GpxDrawHelper.DEFAULT_COLOR_PROPERTY.get(), 509 colorTypeHeatMapTune.getSelectedIndex(), 510 iconSize)); 511 } 512 }); 513 514 ExpertToggleAction.addVisibilitySwitcher(colorTypeDirection); 515 ExpertToggleAction.addVisibilitySwitcher(colorTypeDilution); 516 ExpertToggleAction.addVisibilitySwitcher(colorTypeQuality); 517 ExpertToggleAction.addVisibilitySwitcher(colorTypeHeatMapLowerLimit); 518 ExpertToggleAction.addVisibilitySwitcher(colorTypeHeatMapLowerLimitLabel); 519 520 colorDynamic.setToolTipText(tr("Colors points and track segments by data limits.")); 521 add(colorDynamic, GBC.eop().insets(40, 0, 0, 0)); 522 ExpertToggleAction.addVisibilitySwitcher(colorDynamic); 523 524 if (global) { 525 // Setting waypoints for gpx layer doesn't make sense - waypoints are shown in marker layer that has different name - so show 526 // this only for global config 527 528 // waypointLabel 529 label = new JLabel(tr("Waypoint labelling")); 530 add(label, GBC.std().insets(20, 0, 0, 0)); 531 label.setLabelFor(waypointLabel); 532 add(waypointLabel, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5)); 533 waypointLabel.addActionListener(e -> updateWaypointPattern(waypointLabel, waypointLabelPattern)); 534 add(waypointLabelPattern, GBC.eol().fill(GBC.HORIZONTAL).insets(20, 0, 0, 5)); 535 ExpertToggleAction.addVisibilitySwitcher(label); 536 ExpertToggleAction.addVisibilitySwitcher(waypointLabel); 537 ExpertToggleAction.addVisibilitySwitcher(waypointLabelPattern); 538 539 // audioWaypointLabel 540 Component glue = Box.createVerticalGlue(); 541 add(glue, GBC.eol().insets(0, 20, 0, 0)); 542 ExpertToggleAction.addVisibilitySwitcher(glue); 543 544 label = new JLabel(tr("Audio waypoint labelling")); 545 add(label, GBC.std().insets(20, 0, 0, 0)); 546 label.setLabelFor(audioWaypointLabel); 547 add(audioWaypointLabel, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5)); 548 audioWaypointLabel.addActionListener(e -> updateWaypointPattern(audioWaypointLabel, audioWaypointLabelPattern)); 549 add(audioWaypointLabelPattern, GBC.eol().fill(GBC.HORIZONTAL).insets(20, 0, 0, 5)); 550 ExpertToggleAction.addVisibilitySwitcher(label); 551 ExpertToggleAction.addVisibilitySwitcher(audioWaypointLabel); 552 ExpertToggleAction.addVisibilitySwitcher(audioWaypointLabelPattern); 553 } 554 555 add(Box.createVerticalGlue(), GBC.eol().fill(GBC.BOTH)); 556 } 557 // CHECKSTYLE.ON: ExecutableStatementCountCheck 558 559 /** 560 * Loads preferences to UI controls 561 */ 562 public final void loadPreferences() { 563 makeAutoMarkers.setSelected(Config.getPref().getBoolean("marker.makeautomarkers", true)); 564 int lines = global ? prefInt("lines") : prefIntLocal("lines"); 565 // -1 = global (default: all) 566 // 0 = none 567 // 1 = local 568 // 2 = all 569 if ((lines == 2 && hasNonLocalFile) || (lines == -1 && global)) { 570 drawRawGpsLinesAll.setSelected(true); 571 } else if (lines == 1 && hasLocalFile) { 572 drawRawGpsLinesLocal.setSelected(true); 573 } else if (lines == 0) { 574 drawRawGpsLinesNone.setSelected(true); 575 } else if (lines == -1) { 576 drawRawGpsLinesGlobal.setSelected(true); 577 } else { 578 Logging.warn("Unknown line type: " + lines); 579 } 580 drawRawGpsMaxLineLengthLocal.setText(pref("lines.max-length.local")); 581 drawRawGpsMaxLineLength.setText(pref("lines.max-length")); 582 drawLineWidth.setText(pref("lines.width")); 583 drawLineWithAlpha.setSelected(prefBool("lines.alpha-blend")); 584 forceRawGpsLines.setSelected(prefBool("lines.force")); 585 drawGpsArrows.setSelected(prefBool("lines.arrows")); 586 drawGpsArrowsFast.setSelected(prefBool("lines.arrows.fast")); 587 drawGpsArrowsMinDist.setText(pref("lines.arrows.min-distance")); 588 hdopCircleGpsPoints.setSelected(prefBool("points.hdopcircle")); 589 largeGpsPoints.setSelected(prefBool("points.large")); 590 useGpsAntialiasing.setSelected(Config.getPref().getBoolean("mappaint.gpx.use-antialiasing", false)); 591 592 drawRawGpsLinesActionListener.actionPerformed(null); 593 if (!global && prefIntLocal("colormode") == -1) { 594 colorTypeGlobal.setSelected(true); 595 colorDynamic.setSelected(false); 596 colorDynamic.setEnabled(false); 597 colorTypeHeatMapPoints.setSelected(false); 598 colorTypeHeatMapGain.setValue(0); 599 colorTypeHeatMapLowerLimit.setValue(0); 600 } else { 601 int colorType = prefInt("colormode"); 602 switch (colorType) { 603 case -1: case 0: colorTypeNone.setSelected(true); break; 604 case 1: colorTypeVelocity.setSelected(true); break; 605 case 2: colorTypeDilution.setSelected(true); break; 606 case 3: colorTypeDirection.setSelected(true); break; 607 case 4: colorTypeTime.setSelected(true); break; 608 case 5: colorTypeHeatMap.setSelected(true); break; 609 case 6: colorTypeQuality.setSelected(true); break; 610 default: Logging.warn("Unknown color type: " + colorType); 611 } 612 int ccts = prefInt("colormode.velocity.tune"); 613 colorTypeVelocityTune.setSelectedIndex(ccts == 10 ? 2 : (ccts == 20 ? 1 : 0)); 614 colorTypeHeatMapTune.setSelectedIndex(prefInt("colormode.heatmap.colormap")); 615 colorDynamic.setSelected(prefBool("colormode.dynamic-range")); 616 colorTypeHeatMapPoints.setSelected(prefBool("colormode.heatmap.use-points")); 617 colorTypeHeatMapGain.setValue(prefInt("colormode.heatmap.gain")); 618 colorTypeHeatMapLowerLimit.setValue(prefInt("colormode.heatmap.lower-limit")); 619 } 620 updateWaypointLabelCombobox(waypointLabel, waypointLabelPattern, pref("markers.pattern")); 621 updateWaypointLabelCombobox(audioWaypointLabel, audioWaypointLabelPattern, pref("markers.audio.pattern")); 622 623 } 624 625 /** 626 * Save preferences from UI controls, globally or for the specified layers. 627 * @return {@code true} when restart is required, {@code false} otherwise 628 */ 629 public boolean savePreferences() { 630 if (global) { 631 Config.getPref().putBoolean("marker.makeautomarkers", makeAutoMarkers.isSelected()); 632 putPref("markers.pattern", waypointLabelPattern.getText()); 633 putPref("markers.audio.pattern", audioWaypointLabelPattern.getText()); 634 } 635 boolean g; 636 if (!global && ((g = drawRawGpsLinesGlobal.isSelected()) || drawRawGpsLinesNone.isSelected())) { 637 if (g) { 638 putPref("lines", null); 639 } else { 640 putPref("lines", 0); 641 } 642 putPref("lines.max-length", null); 643 putPref("lines.max-length.local", null); 644 putPref("lines.force", null); 645 putPref("lines.arrows", null); 646 putPref("lines.arrows.fast", null); 647 putPref("lines.arrows.min-distance", null); 648 } else { 649 if (drawRawGpsLinesLocal.isSelected()) { 650 putPref("lines", 1); 651 } else if (drawRawGpsLinesAll.isSelected()) { 652 putPref("lines", 2); 653 } 654 putPref("lines.max-length", drawRawGpsMaxLineLength.getText()); 655 putPref("lines.max-length.local", drawRawGpsMaxLineLengthLocal.getText()); 656 putPref("lines.force", forceRawGpsLines.isSelected()); 657 putPref("lines.arrows", drawGpsArrows.isSelected()); 658 putPref("lines.arrows.fast", drawGpsArrowsFast.isSelected()); 659 putPref("lines.arrows.min-distance", drawGpsArrowsMinDist.getText()); 660 } 661 662 putPref("points.hdopcircle", hdopCircleGpsPoints.isSelected()); 663 putPref("points.large", largeGpsPoints.isSelected()); 664 putPref("lines.width", drawLineWidth.getText()); 665 putPref("lines.alpha-blend", drawLineWithAlpha.isSelected()); 666 667 Config.getPref().putBoolean("mappaint.gpx.use-antialiasing", useGpsAntialiasing.isSelected()); 668 669 if (colorTypeGlobal.isSelected()) { 670 putPref("colormode", null); 671 putPref("colormode.dynamic-range", null); 672 putPref("colormode.velocity.tune", null); 673 return false; 674 } else if (colorTypeVelocity.isSelected()) { 675 putPref("colormode", 1); 676 } else if (colorTypeDilution.isSelected()) { 677 putPref("colormode", 2); 678 } else if (colorTypeDirection.isSelected()) { 679 putPref("colormode", 3); 680 } else if (colorTypeTime.isSelected()) { 681 putPref("colormode", 4); 682 } else if (colorTypeHeatMap.isSelected()) { 683 putPref("colormode", 5); 684 } else if (colorTypeQuality.isSelected()) { 685 putPref("colormode", 6); 686 } else { 687 putPref("colormode", 0); 688 } 689 putPref("colormode.dynamic-range", colorDynamic.isSelected()); 690 int ccti = colorTypeVelocityTune.getSelectedIndex(); 691 putPref("colormode.velocity.tune", ccti == 2 ? 10 : (ccti == 1 ? 20 : 45)); 692 putPref("colormode.heatmap.colormap", colorTypeHeatMapTune.getSelectedIndex()); 693 putPref("colormode.heatmap.use-points", colorTypeHeatMapPoints.isSelected()); 694 putPref("colormode.heatmap.gain", colorTypeHeatMapGain.getValue()); 695 putPref("colormode.heatmap.lower-limit", colorTypeHeatMapLowerLimit.getValue()); 696 697 if (!global && layers != null && !layers.isEmpty()) { 698 layers.forEach(l -> l.data.invalidate()); 699 } 700 701 return false; 702 } 703 704 private static void updateWaypointLabelCombobox(JosmComboBox<String> cb, JosmTextField tf, String labelPattern) { 705 boolean found = false; 706 for (int i = 0; i < LABEL_PATTERN_TEMPLATE.length; i++) { 707 if (LABEL_PATTERN_TEMPLATE[i].equals(labelPattern)) { 708 cb.setSelectedIndex(i); 709 found = true; 710 break; 711 } 712 } 713 if (!found) { 714 cb.setSelectedIndex(WAYPOINT_LABEL_CUSTOM); 715 tf.setEnabled(true); 716 tf.setText(labelPattern); 717 } 718 } 719 720 private static void updateWaypointPattern(JosmComboBox<String> cb, JosmTextField tf) { 721 if (cb.getSelectedIndex() == WAYPOINT_LABEL_CUSTOM) { 722 tf.setEnabled(true); 723 } else { 724 tf.setEnabled(false); 725 tf.setText(LABEL_PATTERN_TEMPLATE[cb.getSelectedIndex()]); 726 } 727 } 728 729 @Override 730 public boolean validatePreferences() { 731 TemplateParser parser = new TemplateParser(waypointLabelPattern.getText()); 732 try { 733 parser.parse(); 734 } catch (ParseError e) { 735 Logging.warn(e); 736 JOptionPane.showMessageDialog(MainApplication.getMainFrame(), 737 tr("Incorrect waypoint label pattern: {0}", e.getMessage()), tr("Incorrect pattern"), JOptionPane.ERROR_MESSAGE); 738 waypointLabelPattern.requestFocus(); 739 return false; 740 } 741 parser = new TemplateParser(audioWaypointLabelPattern.getText()); 742 try { 743 parser.parse(); 744 } catch (ParseError e) { 745 Logging.warn(e); 746 JOptionPane.showMessageDialog(MainApplication.getMainFrame(), 747 tr("Incorrect audio waypoint label pattern: {0}", e.getMessage()), tr("Incorrect pattern"), JOptionPane.ERROR_MESSAGE); 748 audioWaypointLabelPattern.requestFocus(); 749 return false; 750 } 751 return true; 752 } 753}