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