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